aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/vcs/git.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/vcs/git.go')
-rw-r--r--pkg/vcs/git.go396
1 files changed, 216 insertions, 180 deletions
diff --git a/pkg/vcs/git.go b/pkg/vcs/git.go
index d8a4967ba..fd71b8d36 100644
--- a/pkg/vcs/git.go
+++ b/pkg/vcs/git.go
@@ -22,25 +22,25 @@ import (
"github.com/google/syzkaller/pkg/osutil"
)
-type git struct {
- dir string
- ignoreCC map[string]bool
- precious bool
- sandbox bool
+type gitRepo struct {
+ *Git
}
-func newGit(dir string, ignoreCC map[string]bool, opts []RepoOpt) *git {
- git := &git{
- dir: dir,
- ignoreCC: ignoreCC,
- sandbox: true,
+func newGitRepo(dir string, ignoreCC map[string]bool, opts []RepoOpt) *gitRepo {
+ git := &gitRepo{
+ Git: &Git{
+ Dir: dir,
+ Sandbox: true,
+ Env: filterEnv(),
+ ignoreCC: ignoreCC,
+ },
}
for _, opt := range opts {
switch opt {
case OptPrecious:
git.precious = true
case OptDontSandbox:
- git.sandbox = false
+ git.Sandbox = false
}
}
return git
@@ -66,9 +66,9 @@ func filterEnv() []string {
return env
}
-func (git *git) Poll(repo, branch string) (*Commit, error) {
- git.reset()
- origin, err := git.git("remote", "get-url", "origin")
+func (git *gitRepo) Poll(repo, branch string) (*Commit, error) {
+ git.Reset()
+ origin, err := git.Run("remote", "get-url", "origin")
if err != nil || strings.TrimSpace(string(origin)) != repo {
// The repo is here, but it has wrong origin (e.g. repo in config has changed), re-clone.
if err := git.clone(repo, branch); err != nil {
@@ -78,28 +78,28 @@ func (git *git) Poll(repo, branch string) (*Commit, error) {
// Use origin/branch for the case the branch was force-pushed,
// in such case branch is not the same is origin/branch and we will
// stuck with the local version forever (git checkout won't fail).
- if _, err := git.git("checkout", "origin/"+branch); err != nil {
+ if _, err := git.Run("checkout", "origin/"+branch); err != nil {
// No such branch (e.g. branch in config has changed), re-clone.
if err := git.clone(repo, branch); err != nil {
return nil, err
}
}
- if _, err := git.git("fetch", "--force"); err != nil {
+ if _, err := git.Run("fetch", "--force"); err != nil {
// Something else is wrong, re-clone.
if err := git.clone(repo, branch); err != nil {
return nil, err
}
}
- if _, err := git.git("checkout", "origin/"+branch); err != nil {
+ if _, err := git.Run("checkout", "origin/"+branch); err != nil {
return nil, err
}
- if _, err := git.git("submodule", "update", "--init"); err != nil {
+ if _, err := git.Run("submodule", "update", "--init"); err != nil {
return nil, err
}
return git.Commit(HEAD)
}
-func (git *git) CheckoutBranch(repo, branch string) (*Commit, error) {
+func (git *gitRepo) CheckoutBranch(repo, branch string) (*Commit, error) {
if err := git.repair(); err != nil {
return nil, err
}
@@ -108,17 +108,17 @@ func (git *git) CheckoutBranch(repo, branch string) (*Commit, error) {
// remote when initializing.
// This sets "origin" to be the current remote.
// Ignore errors as we can double add or remove the same remote and that will fail.
- git.git("remote", "rm", "origin")
- git.git("remote", "add", "origin", repo)
- git.git("remote", "add", repoHash, repo)
- _, err := git.git("fetch", "--force", repoHash, branch)
+ git.Run("remote", "rm", "origin")
+ git.Run("remote", "add", "origin", repo)
+ git.Run("remote", "add", repoHash, repo)
+ _, err := git.Run("fetch", "--force", repoHash, branch)
if err != nil {
return nil, err
}
- if _, err := git.git("checkout", "FETCH_HEAD", "--force"); err != nil {
+ if _, err := git.Run("checkout", "FETCH_HEAD", "--force"); err != nil {
return nil, err
}
- if _, err := git.git("submodule", "update", "--init"); err != nil {
+ if _, err := git.Run("submodule", "update", "--init"); err != nil {
return nil, err
}
// If the branch checkout had to be "forced" the directory may
@@ -130,7 +130,7 @@ func (git *git) CheckoutBranch(repo, branch string) (*Commit, error) {
return git.Commit(HEAD)
}
-func (git *git) CheckoutCommit(repo, commit string) (*Commit, error) {
+func (git *gitRepo) CheckoutCommit(repo, commit string) (*Commit, error) {
if err := git.repair(); err != nil {
return nil, err
}
@@ -140,16 +140,16 @@ func (git *git) CheckoutCommit(repo, commit string) (*Commit, error) {
return git.SwitchCommit(commit)
}
-func (git *git) fetchRemote(repo, commit string) error {
+func (git *gitRepo) fetchRemote(repo, commit string) error {
repoHash := hash.String([]byte(repo))
// Ignore error as we can double add the same remote and that will fail.
- git.git("remote", "add", repoHash, repo)
+ git.Run("remote", "add", repoHash, repo)
fetchArgs := []string{"fetch", "--force", "--tags", repoHash}
if commit != "" && gitFullHashRe.MatchString(commit) {
// This trick only works with full commit hashes.
fetchArgs = append(fetchArgs, commit)
}
- _, err := git.git(fetchArgs...)
+ _, err := git.Run(fetchArgs...)
if err != nil {
var verbose *osutil.VerboseError
if errors.As(err, &verbose) &&
@@ -159,110 +159,75 @@ func (git *git) fetchRemote(repo, commit string) error {
// Try to fetch more, but this time prune tags, it should help.
// The --prune-tags option will remove all tags that are not present
// in this remote repo, so don't do it always. Only when necessary.
- _, err = git.git("fetch", "--force", "--tags", "--prune", "--prune-tags", repoHash)
+ _, err = git.Run("fetch", "--force", "--tags", "--prune", "--prune-tags", repoHash)
}
}
return err
}
-func (git *git) SwitchCommit(commit string) (*Commit, error) {
+func (git *gitRepo) SwitchCommit(commit string) (*Commit, error) {
if !git.precious {
- git.git("reset", "--hard")
- git.git("clean", "-fdx")
+ git.Run("reset", "--hard")
+ git.Run("clean", "-fdx")
}
- if _, err := git.git("checkout", commit); err != nil {
+ if _, err := git.Run("checkout", commit); err != nil {
return nil, err
}
- if _, err := git.git("submodule", "update", "--init"); err != nil {
+ if _, err := git.Run("submodule", "update", "--init"); err != nil {
return nil, err
}
return git.Commit(HEAD)
}
-func (git *git) clone(repo, branch string) error {
+func (git *gitRepo) clone(repo, branch string) error {
if git.precious {
return fmt.Errorf("won't reinit precious repo")
}
if err := git.initRepo(nil); err != nil {
return err
}
- if _, err := git.git("remote", "add", "origin", repo); err != nil {
+ if _, err := git.Run("remote", "add", "origin", repo); err != nil {
return err
}
- if _, err := git.git("fetch", "origin", branch); err != nil {
+ if _, err := git.Run("fetch", "origin", branch); err != nil {
return err
}
return nil
}
-func (git *git) reset() error {
- // This function tries to reset git repo state to a known clean state.
- if git.precious {
- return nil
- }
- git.git("reset", "--hard", "--recurse-submodules")
- git.git("clean", "-xfdf")
- git.git("submodule", "foreach", "--recursive", "git", "clean", "-xfdf")
- git.git("bisect", "reset")
- _, err := git.git("reset", "--hard", "--recurse-submodules")
- return err
-}
-
-func (git *git) repair() error {
- if err := git.reset(); err != nil {
+func (git *gitRepo) repair() error {
+ if err := git.Reset(); err != nil {
return git.initRepo(err)
}
return nil
}
-func (git *git) initRepo(reason error) error {
+func (git *gitRepo) initRepo(reason error) error {
if reason != nil {
- log.Logf(1, "git: initializing repo at %v: %v", git.dir, reason)
+ log.Logf(1, "git: initializing repo at %v: %v", git.Dir, reason)
}
- if err := os.RemoveAll(git.dir); err != nil {
+ if err := os.RemoveAll(git.Dir); err != nil {
return fmt.Errorf("failed to remove repo dir: %w", err)
}
- if err := osutil.MkdirAll(git.dir); err != nil {
+ if err := osutil.MkdirAll(git.Dir); err != nil {
return fmt.Errorf("failed to create repo dir: %w", err)
}
- if git.sandbox {
- if err := osutil.SandboxChown(git.dir); err != nil {
+ if git.Sandbox {
+ if err := osutil.SandboxChown(git.Dir); err != nil {
return err
}
}
- if _, err := git.git("init"); err != nil {
+ if _, err := git.Run("init"); err != nil {
return err
}
return nil
}
-func (git *git) Contains(commit string) (bool, error) {
- _, err := git.git("merge-base", "--is-ancestor", commit, HEAD)
+func (git *gitRepo) Contains(commit string) (bool, error) {
+ _, err := git.Run("merge-base", "--is-ancestor", commit, HEAD)
return err == nil, nil
}
-func (git *git) Commit(com string) (*Commit, error) {
- const patchSeparator = "---===syzkaller-patch-separator===---"
- output, err := git.git("log", "--format=%H%n%s%n%ae%n%an%n%ad%n%P%n%cd%n%b"+patchSeparator,
- "-n", "1", "-p", "-U0", com)
- if err != nil {
- return nil, err
- }
- pos := bytes.Index(output, []byte(patchSeparator))
- if pos == -1 {
- return nil, fmt.Errorf("git log output does not contain patch separator")
- }
- commit, err := gitParseCommit(output[:pos], nil, nil, git.ignoreCC)
- if err != nil {
- return nil, err
- }
- commit.Patch = output[pos+len(patchSeparator):]
- for len(commit.Patch) != 0 && commit.Patch[0] == '\n' {
- commit.Patch = commit.Patch[1:]
- }
- return commit, nil
-}
-
func gitParseCommit(output, user, domain []byte, ignoreCC map[string]bool) (*Commit, error) {
lines := bytes.Split(output, []byte{'\n'})
if len(lines) < 8 || len(lines[0]) != 40 {
@@ -340,7 +305,7 @@ func gitParseCommit(output, user, domain []byte, ignoreCC map[string]bool) (*Com
return com, nil
}
-func (git *git) GetCommitByTitle(title string) (*Commit, error) {
+func (git *gitRepo) GetCommitByTitle(title string) (*Commit, error) {
commits, _, err := git.GetCommitsByTitles([]string{title})
if err != nil || len(commits) == 0 {
return nil, err
@@ -352,7 +317,7 @@ const (
fetchCommitsMaxAgeInYears = 5
)
-func (git *git) GetCommitsByTitles(titles []string) ([]*Commit, []string, error) {
+func (git *gitRepo) GetCommitsByTitles(titles []string) ([]*Commit, []string, error) {
var greps []string
m := make(map[string]string)
for _, title := range titles {
@@ -381,12 +346,12 @@ func (git *git) GetCommitsByTitles(titles []string) ([]*Commit, []string, error)
return results, missing, nil
}
-func (git *git) ListCommitHashes(baseCommit string, from time.Time) ([]string, error) {
+func (git *gitRepo) ListCommitHashes(baseCommit string, from time.Time) ([]string, error) {
args := []string{"log", "--pretty=format:%h"}
if !from.IsZero() {
args = append(args, "--since", from.Format(time.RFC3339))
}
- output, err := git.git(append(args, baseCommit)...)
+ output, err := git.Run(append(args, baseCommit)...)
if err != nil {
return nil, err
}
@@ -396,7 +361,7 @@ func (git *git) ListCommitHashes(baseCommit string, from time.Time) ([]string, e
return strings.Split(string(output), "\n"), nil
}
-func (git *git) ExtractFixTagsFromCommits(baseCommit, email string) ([]*Commit, error) {
+func (git *gitRepo) ExtractFixTagsFromCommits(baseCommit, email string) ([]*Commit, error) {
user, domain, err := splitEmail(email)
if err != nil {
return nil, fmt.Errorf("failed to parse email %q: %w", email, err)
@@ -406,76 +371,6 @@ func (git *git) ExtractFixTagsFromCommits(baseCommit, email string) ([]*Commit,
return git.fetchCommits(since, baseCommit, user, domain, []string{grep}, false)
}
-func (git *git) fetchCommits(since, base, user, domain string, greps []string, fixedStrings bool) ([]*Commit, error) {
- const commitSeparator = "---===syzkaller-commit-separator===---"
- args := []string{"log", "--since", since, "--format=%H%n%s%n%ae%n%an%n%ad%n%P%n%cd%n%b%n" + commitSeparator}
- if fixedStrings {
- args = append(args, "--fixed-strings")
- }
- for _, grep := range greps {
- args = append(args, "--grep", grep)
- }
- args = append(args, base)
- cmd := exec.Command("git", args...)
- cmd.Dir = git.dir
- cmd.Env = filterEnv()
- if git.sandbox {
- if err := osutil.Sandbox(cmd, true, false); err != nil {
- return nil, err
- }
- }
- stdout, err := cmd.StdoutPipe()
- if err != nil {
- return nil, err
- }
- if err := cmd.Start(); err != nil {
- return nil, err
- }
- defer cmd.Wait()
- defer cmd.Process.Kill()
- var (
- s = bufio.NewScanner(stdout)
- buf = new(bytes.Buffer)
- separator = []byte(commitSeparator)
- commits []*Commit
- userBytes []byte
- domainBytes []byte
- )
- if user != "" {
- userBytes = []byte(user + "+")
- domainBytes = []byte(domain)
- }
- for s.Scan() {
- ln := s.Bytes()
- if !bytes.Equal(ln, separator) {
- buf.Write(ln)
- buf.WriteByte('\n')
- continue
- }
- com, err := gitParseCommit(buf.Bytes(), userBytes, domainBytes, git.ignoreCC)
- if err != nil {
- return nil, err
- }
- if user == "" || len(com.Tags) != 0 {
- commits = append(commits, com)
- }
- buf.Reset()
- }
- return commits, s.Err()
-}
-
-func (git *git) git(args ...string) ([]byte, error) {
- cmd := osutil.Command("git", args...)
- cmd.Dir = git.dir
- cmd.Env = filterEnv()
- if git.sandbox {
- if err := osutil.Sandbox(cmd, true, false); err != nil {
- return nil, err
- }
- }
- return osutil.Run(3*time.Hour, cmd)
-}
-
func splitEmail(email string) (user, domain string, err error) {
addr, err := mail.ParseAddress(email)
if err != nil {
@@ -493,18 +388,18 @@ func splitEmail(email string) (user, domain string, err error) {
return
}
-func (git *git) Bisect(bad, good string, dt debugtracer.DebugTracer, pred func() (BisectResult,
+func (git *gitRepo) Bisect(bad, good string, dt debugtracer.DebugTracer, pred func() (BisectResult,
error)) ([]*Commit, error) {
- git.reset()
+ git.Reset()
firstBad, err := git.Commit(bad)
if err != nil {
return nil, err
}
- output, err := git.git("bisect", "start", bad, good)
+ output, err := git.Run("bisect", "start", bad, good)
if err != nil {
return nil, err
}
- defer git.reset()
+ defer git.Reset()
dt.Log("# git bisect start %v %v\n%s", bad, good, output)
current, err := git.Commit(HEAD)
if err != nil {
@@ -518,14 +413,14 @@ func (git *git) Bisect(bad, good string, dt debugtracer.DebugTracer, pred func()
for {
res, err := pred()
// Linux EnvForCommit may cherry-pick some fixes, reset these before the next step.
- git.git("reset", "--hard")
+ git.Run("reset", "--hard")
if err != nil {
return nil, err
}
if res == BisectBad {
firstBad = current
}
- output, err = git.git("bisect", bisectTerms[res])
+ output, err = git.Run("bisect", bisectTerms[res])
dt.Log("# git bisect %v %v\n%s", bisectTerms[res], current.Hash, output)
if err != nil {
if bytes.Contains(output, []byte("There are only 'skip'ped commits left to test")) {
@@ -546,7 +441,7 @@ func (git *git) Bisect(bad, good string, dt debugtracer.DebugTracer, pred func()
var gitFullHashRe = regexp.MustCompile("[a-f0-9]{40}")
-func (git *git) bisectInconclusive(output []byte) ([]*Commit, error) {
+func (git *gitRepo) bisectInconclusive(output []byte) ([]*Commit, error) {
// For inconclusive bisection git prints the following message:
//
// There are only 'skip'ped commits left to test.
@@ -570,7 +465,7 @@ func (git *git) bisectInconclusive(output []byte) ([]*Commit, error) {
return commits, nil
}
-func (git *git) ReleaseTag(commit string) (string, error) {
+func (git *gitRepo) ReleaseTag(commit string) (string, error) {
tags, err := git.previousReleaseTags(commit, true, true, true)
if err != nil {
return "", err
@@ -581,10 +476,10 @@ func (git *git) ReleaseTag(commit string) (string, error) {
return tags[0], nil
}
-func (git *git) previousReleaseTags(commit string, self, onlyTop, includeRC bool) ([]string, error) {
+func (git *gitRepo) previousReleaseTags(commit string, self, onlyTop, includeRC bool) ([]string, error) {
var tags []string
if self {
- output, err := git.git("tag", "--list", "--points-at", commit, "--merged", commit, "v*.*")
+ output, err := git.Run("tag", "--list", "--points-at", commit, "--merged", commit, "v*.*")
if err != nil {
return nil, err
}
@@ -593,7 +488,7 @@ func (git *git) previousReleaseTags(commit string, self, onlyTop, includeRC bool
return tags, nil
}
}
- output, err := git.git("tag", "--no-contains", commit, "--merged", commit, "v*.*")
+ output, err := git.Run("tag", "--no-contains", commit, "--merged", commit, "v*.*")
if err != nil {
return nil, err
}
@@ -605,7 +500,7 @@ func (git *git) previousReleaseTags(commit string, self, onlyTop, includeRC bool
return tags, nil
}
-func (git *git) IsRelease(commit string) (bool, error) {
+func (git *gitRepo) IsRelease(commit string) (bool, error) {
tags1, err := git.previousReleaseTags(commit, true, false, false)
if err != nil {
return false, err
@@ -617,12 +512,12 @@ func (git *git) IsRelease(commit string) (bool, error) {
return len(tags1) != len(tags2), nil
}
-func (git *git) Object(name, commit string) ([]byte, error) {
- return git.git("show", fmt.Sprintf("%s:%s", commit, name))
+func (git *gitRepo) Object(name, commit string) ([]byte, error) {
+ return git.Run("show", fmt.Sprintf("%s:%s", commit, name))
}
-func (git *git) MergeBases(firstCommit, secondCommit string) ([]*Commit, error) {
- output, err := git.git("merge-base", firstCommit, secondCommit)
+func (git *gitRepo) MergeBases(firstCommit, secondCommit string) ([]*Commit, error) {
+ output, err := git.Run("merge-base", firstCommit, secondCommit)
if err != nil {
return nil, err
}
@@ -641,8 +536,8 @@ func (git *git) MergeBases(firstCommit, secondCommit string) ([]*Commit, error)
// If object exists its exit status is 0.
// If object doesn't exist its exit status is 1 (not documented).
// Otherwise, the exit status is 128 (not documented).
-func (git *git) CommitExists(commit string) (bool, error) {
- _, err := git.git("cat-file", "-e", commit)
+func (git *gitRepo) CommitExists(commit string) (bool, error) {
+ _, err := git.Run("cat-file", "-e", commit)
var vErr *osutil.VerboseError
if errors.As(err, &vErr) && vErr.ExitCode == 1 {
return false, nil
@@ -653,10 +548,10 @@ func (git *git) CommitExists(commit string) (bool, error) {
return true, nil
}
-func (git *git) PushCommit(repo, commit string) error {
+func (git *gitRepo) PushCommit(repo, commit string) error {
tagName := "tag-" + commit // assign tag to guarantee remote persistence
- git.git("tag", tagName) // ignore errors on re-tagging
- if _, err := git.git("push", repo, "tag", tagName); err != nil {
+ git.Run("tag", tagName) // ignore errors on re-tagging
+ if _, err := git.Run("push", repo, "tag", tagName); err != nil {
return fmt.Errorf("git push %s tag %s: %w", repo, tagName, err)
}
return nil
@@ -672,3 +567,144 @@ func ParseGitDiff(patch []byte) []string {
}
return files
}
+
+type Git struct {
+ Dir string
+ Sandbox bool
+ Env []string
+ precious bool
+ ignoreCC map[string]bool
+}
+
+func (git Git) Run(args ...string) ([]byte, error) {
+ cmd, err := git.command(args...)
+ if err != nil {
+ return nil, err
+ }
+ return osutil.Run(3*time.Hour, cmd)
+}
+
+func (git Git) command(args ...string) (*exec.Cmd, error) {
+ cmd := osutil.Command("git", args...)
+ cmd.Dir = git.Dir
+ cmd.Env = git.Env
+ if git.Sandbox {
+ if err := osutil.Sandbox(cmd, true, false); err != nil {
+ return nil, err
+ }
+ }
+ return cmd, nil
+}
+
+// Apply invokes git apply for a series of git patches.
+// It is different from Patch() in that it normally handles raw patch emails.
+func (git Git) Apply(patch []byte) error {
+ cmd, err := git.command("apply", "-")
+ if err != nil {
+ return err
+ }
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ return err
+ }
+ go func() {
+ stdin.Write(patch)
+ stdin.Close()
+ }()
+ _, err = osutil.Run(3*time.Hour, cmd)
+ return err
+}
+
+// Reset resets the git repo to a known clean state.
+func (git Git) Reset() error {
+ if git.precious {
+ return nil
+ }
+ git.Run("reset", "--hard", "--recurse-submodules")
+ git.Run("clean", "-xfdf")
+ git.Run("submodule", "foreach", "--recursive", "git", "clean", "-xfdf")
+ git.Run("bisect", "reset")
+ _, err := git.Run("reset", "--hard", "--recurse-submodules")
+ return err
+}
+
+// Commit extracts the information about the particular git commit.
+func (git Git) Commit(hash string) (*Commit, error) {
+ const patchSeparator = "---===syzkaller-patch-separator===---"
+ output, err := git.Run("log", "--format=%H%n%s%n%ae%n%an%n%ad%n%P%n%cd%n%b"+patchSeparator,
+ "-n", "1", "-p", "-U0", hash)
+ if err != nil {
+ return nil, err
+ }
+ pos := bytes.Index(output, []byte(patchSeparator))
+ if pos == -1 {
+ return nil, fmt.Errorf("git log output does not contain patch separator")
+ }
+ commit, err := gitParseCommit(output[:pos], nil, nil, git.ignoreCC)
+ if err != nil {
+ return nil, err
+ }
+ commit.Patch = output[pos+len(patchSeparator):]
+ for len(commit.Patch) != 0 && commit.Patch[0] == '\n' {
+ commit.Patch = commit.Patch[1:]
+ }
+ return commit, nil
+}
+
+func (git Git) fetchCommits(since, base, user, domain string, greps []string, fixedStrings bool) ([]*Commit, error) {
+ const commitSeparator = "---===syzkaller-commit-separator===---"
+ args := []string{"log", "--since", since, "--format=%H%n%s%n%ae%n%an%n%ad%n%P%n%cd%n%b%n" + commitSeparator}
+ if fixedStrings {
+ args = append(args, "--fixed-strings")
+ }
+ for _, grep := range greps {
+ args = append(args, "--grep", grep)
+ }
+ args = append(args, base)
+ cmd := exec.Command("git", args...)
+ cmd.Dir = git.Dir
+ cmd.Env = filterEnv()
+ if git.Sandbox {
+ if err := osutil.Sandbox(cmd, true, false); err != nil {
+ return nil, err
+ }
+ }
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ return nil, err
+ }
+ if err := cmd.Start(); err != nil {
+ return nil, err
+ }
+ defer cmd.Wait()
+ defer cmd.Process.Kill()
+ var (
+ s = bufio.NewScanner(stdout)
+ buf = new(bytes.Buffer)
+ separator = []byte(commitSeparator)
+ commits []*Commit
+ userBytes []byte
+ domainBytes []byte
+ )
+ if user != "" {
+ userBytes = []byte(user + "+")
+ domainBytes = []byte(domain)
+ }
+ for s.Scan() {
+ ln := s.Bytes()
+ if !bytes.Equal(ln, separator) {
+ buf.Write(ln)
+ buf.WriteByte('\n')
+ continue
+ }
+ com, err := gitParseCommit(buf.Bytes(), userBytes, domainBytes, git.ignoreCC)
+ if err != nil {
+ return nil, err
+ }
+ if user == "" || len(com.Tags) != 0 {
+ commits = append(commits, com)
+ }
+ buf.Reset()
+ }
+ return commits, s.Err()
+}