diff options
author | kuba-- <kuba@sourced.tech> | 2019-01-03 22:47:04 +0100 |
---|---|---|
committer | kuba-- <kuba@sourced.tech> | 2019-01-07 08:52:54 +0100 |
commit | 3180dff8c5618b2e146a7fb4ac36be10851c86cc (patch) | |
tree | 88ded2ac78340fc8c4a022a90e6ea3d5de34259a | |
parent | 791aea319719ee757cb862e83ec43b25113de2c1 (diff) | |
download | go-git-3180dff8c5618b2e146a7fb4ac36be10851c86cc.tar.gz |
Implement git log --all
Signed-off-by: kuba-- <kuba@sourced.tech>
-rw-r--r-- | options.go | 5 | ||||
-rw-r--r-- | plumbing/object/commit_walker.go | 126 | ||||
-rw-r--r-- | plumbing/object/commit_walker_file.go | 25 | ||||
-rw-r--r-- | repository.go | 71 | ||||
-rw-r--r-- | repository_test.go | 128 |
5 files changed, 322 insertions, 33 deletions
@@ -335,6 +335,11 @@ type LogOptions struct { // Show only those commits in which the specified file was inserted/updated. // It is equivalent to running `git log -- <file-name>`. FileName *string + + // Pretend as if all the refs in refs/, along with HEAD, are listed on the command line as <commit>. + // It is equivalent to running `git log --all`. + // If set on true, the From option will be ignored. + All bool } var ( diff --git a/plumbing/object/commit_walker.go b/plumbing/object/commit_walker.go index 40ad258..ab06cb2 100644 --- a/plumbing/object/commit_walker.go +++ b/plumbing/object/commit_walker.go @@ -1,10 +1,12 @@ package object import ( + "container/list" "io" "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/storer" + "gopkg.in/src-d/go-git.v4/storage" ) type commitPreIterator struct { @@ -181,3 +183,127 @@ func (w *commitPostIterator) ForEach(cb func(*Commit) error) error { } func (w *commitPostIterator) Close() {} + +// commitAllIterator stands for commit iterator for all refs. +type commitAllIterator struct { + // el points to the current commit. + el *list.Element +} + +// NewCommitAllIter returns a new commit iterator for all refs. +// s is a repo Storer used to get commits and references. +// fn is a commit iterator function, used to iterate through ref commits in chosen order +func NewCommitAllIter(s storage.Storer, fn func(*Commit) CommitIter) (CommitIter, error) { + l := list.New() + m := make(map[plumbing.Hash]*list.Element) + + // ...along with the HEAD + head, err := storer.ResolveReference(s, plumbing.HEAD) + if err != nil { + return nil, err + } + headCommit, err := GetCommit(s, head.Hash()) + if err != nil { + return nil, err + } + err = fn(headCommit).ForEach(func(c *Commit) error { + el := l.PushBack(c) + m[c.Hash] = el + return nil + }) + if err != nil { + return nil, err + } + + refIter, err := s.IterReferences() + if err != nil { + return nil, err + } + defer refIter.Close() + err = refIter.ForEach(func(r *plumbing.Reference) error { + if r.Hash() == head.Hash() { + // we already have the HEAD + return nil + } + c, _ := GetCommit(s, r.Hash()) + // if it's not a commit - skip it. + if c == nil { + return nil + } + + el, ok := m[c.Hash] + if ok { + return nil + } + + var refCommits []*Commit + cit := fn(c) + for c, e := cit.Next(); e == nil; { + el, ok = m[c.Hash] + if ok { + break + } + refCommits = append(refCommits, c) + c, e = cit.Next() + } + cit.Close() + + if el == nil { + // push back all commits from this ref. + for _, c := range refCommits { + el = l.PushBack(c) + m[c.Hash] = el + } + } else { + // insert ref's commits into the list + for i := len(refCommits) - 1; i >= 0; i-- { + c := refCommits[i] + el = l.InsertBefore(c, el) + m[c.Hash] = el + } + } + return nil + }) + if err != nil { + return nil, err + } + + return &commitAllIterator{l.Front()}, nil +} + +func (it *commitAllIterator) Next() (*Commit, error) { + if it.el == nil { + return nil, io.EOF + } + + c := it.el.Value.(*Commit) + it.el = it.el.Next() + + return c, nil +} + +func (it *commitAllIterator) ForEach(cb func(*Commit) error) error { + for { + c, err := it.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + + err = cb(c) + if err == storer.ErrStop { + break + } + if err != nil { + return err + } + } + + return nil +} + +func (it *commitAllIterator) Close() { + it.el = nil +} diff --git a/plumbing/object/commit_walker_file.go b/plumbing/object/commit_walker_file.go index 84e738a..1af9ec1 100644 --- a/plumbing/object/commit_walker_file.go +++ b/plumbing/object/commit_walker_file.go @@ -1,23 +1,26 @@ package object import ( - "gopkg.in/src-d/go-git.v4/plumbing/storer" "io" + + "gopkg.in/src-d/go-git.v4/plumbing/storer" ) type commitFileIter struct { fileName string sourceIter CommitIter currentCommit *Commit + all bool } // NewCommitFileIterFromIter returns a commit iterator which performs diffTree between // successive trees returned from the commit iterator from the argument. The purpose of this is // to find the commits that explain how the files that match the path came to be. -func NewCommitFileIterFromIter(fileName string, commitIter CommitIter) CommitIter { +func NewCommitFileIterFromIter(fileName string, commitIter CommitIter, all bool) CommitIter { iterator := new(commitFileIter) iterator.sourceIter = commitIter iterator.fileName = fileName + iterator.all = all return iterator } @@ -73,8 +76,24 @@ func (c *commitFileIter) getNextFileCommit() (*Commit, error) { foundChangeForFile := false for _, change := range changes { - if change.name() == c.fileName { + if change.name() != c.fileName { + continue + } + + // filename matches, now check if source iterator contains all commits (from all refs) + if c.all { + // for `git log --all` also check if the next commit comes from the same parent + for _, h := range c.currentCommit.ParentHashes { + if h == parentCommit.Hash { + foundChangeForFile = true + break + } + } + } else { foundChangeForFile = true + } + + if foundChangeForFile { break } } diff --git a/repository.go b/repository.go index 1f64b9f..6be07c2 100644 --- a/repository.go +++ b/repository.go @@ -1027,41 +1027,64 @@ func (r *Repository) PushContext(ctx context.Context, o *PushOptions) error { // Log returns the commit history from the given LogOptions. func (r *Repository) Log(o *LogOptions) (object.CommitIter, error) { - h := o.From - if o.From == plumbing.ZeroHash { - head, err := r.Head() - if err != nil { - return nil, err - } - - h = head.Hash() - } - - commit, err := r.CommitObject(h) - if err != nil { - return nil, err - } - - var commitIter object.CommitIter + var ( + err error + commitIterFunc func(*object.Commit) object.CommitIter + commitIter object.CommitIter + ) switch o.Order { case LogOrderDefault: - commitIter = object.NewCommitPreorderIter(commit, nil, nil) + commitIterFunc = func(c *object.Commit) object.CommitIter { + return object.NewCommitPreorderIter(c, nil, nil) + } case LogOrderDFS: - commitIter = object.NewCommitPreorderIter(commit, nil, nil) + commitIterFunc = func(c *object.Commit) object.CommitIter { + return object.NewCommitPreorderIter(c, nil, nil) + } case LogOrderDFSPost: - commitIter = object.NewCommitPostorderIter(commit, nil) + commitIterFunc = func(c *object.Commit) object.CommitIter { + return object.NewCommitPostorderIter(c, nil) + } case LogOrderBSF: - commitIter = object.NewCommitIterBSF(commit, nil, nil) + commitIterFunc = func(c *object.Commit) object.CommitIter { + return object.NewCommitIterBSF(c, nil, nil) + } case LogOrderCommitterTime: - commitIter = object.NewCommitIterCTime(commit, nil, nil) + commitIterFunc = func(c *object.Commit) object.CommitIter { + return object.NewCommitIterCTime(c, nil, nil) + } default: return nil, fmt.Errorf("invalid Order=%v", o.Order) } - if o.FileName == nil { - return commitIter, nil + if o.All { + commitIter, err = object.NewCommitAllIter(r.Storer, commitIterFunc) + if err != nil { + return nil, err + } + } else { + h := o.From + if o.From == plumbing.ZeroHash { + head, err := r.Head() + if err != nil { + return nil, err + } + + h = head.Hash() + } + + commit, err := r.CommitObject(h) + if err != nil { + return nil, err + } + commitIter = commitIterFunc(commit) } - return object.NewCommitFileIterFromIter(*o.FileName, commitIter), nil + + if o.FileName != nil { + commitIter = object.NewCommitFileIterFromIter(*o.FileName, commitIter, o.All) + } + + return commitIter, nil } // Tags returns all the tag References in a repository. diff --git a/repository_test.go b/repository_test.go index 70e344e..2a56dd2 100644 --- a/repository_test.go +++ b/repository_test.go @@ -1251,6 +1251,77 @@ func (s *RepositorySuite) TestLog(c *C) { c.Assert(err, Equals, io.EOF) } +func (s *RepositorySuite) TestLogAll(c *C) { + r, _ := Init(memory.NewStorage(), nil) + err := r.clone(context.Background(), &CloneOptions{ + URL: s.GetBasicLocalRepositoryURL(), + }) + + c.Assert(err, IsNil) + + cIter, err := r.Log(&LogOptions{ + All: true, + }) + c.Assert(err, IsNil) + + commitOrder := []plumbing.Hash{ + plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"), + plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881"), + plumbing.NewHash("918c48b83bd081e863dbe1b80f8998f058cd8294"), + plumbing.NewHash("af2d6a6954d532f8ffb47615169c8fdf9d383a1a"), + plumbing.NewHash("1669dce138d9b841a518c64b10914d88f5e488ea"), + plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"), + plumbing.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d"), + plumbing.NewHash("a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69"), + plumbing.NewHash("b8e471f58bcbca63b07bda20e428190409c2db47"), + } + + for _, o := range commitOrder { + commit, err := cIter.Next() + c.Assert(err, IsNil) + c.Assert(commit.Hash, Equals, o) + } + _, err = cIter.Next() + c.Assert(err, Equals, io.EOF) + cIter.Close() +} + +func (s *RepositorySuite) TestLogAllOrderByTime(c *C) { + r, _ := Init(memory.NewStorage(), nil) + err := r.clone(context.Background(), &CloneOptions{ + URL: s.GetBasicLocalRepositoryURL(), + }) + + c.Assert(err, IsNil) + + cIter, err := r.Log(&LogOptions{ + Order: LogOrderCommitterTime, + All: true, + }) + c.Assert(err, IsNil) + + commitOrder := []plumbing.Hash{ + plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"), + plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881"), + plumbing.NewHash("918c48b83bd081e863dbe1b80f8998f058cd8294"), + plumbing.NewHash("af2d6a6954d532f8ffb47615169c8fdf9d383a1a"), + plumbing.NewHash("1669dce138d9b841a518c64b10914d88f5e488ea"), + plumbing.NewHash("a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69"), + plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"), + plumbing.NewHash("b8e471f58bcbca63b07bda20e428190409c2db47"), + plumbing.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d"), + } + + for _, o := range commitOrder { + commit, err := cIter.Next() + c.Assert(err, IsNil) + c.Assert(commit.Hash, Equals, o) + } + _, err = cIter.Next() + c.Assert(err, Equals, io.EOF) + cIter.Close() +} + func (s *RepositorySuite) TestLogHead(c *C) { r, _ := Init(memory.NewStorage(), nil) err := r.clone(context.Background(), &CloneOptions{ @@ -1333,8 +1404,8 @@ func (s *RepositorySuite) TestLogFileForEach(c *C) { fileName := "php/crappy.php" cIter, err := r.Log(&LogOptions{FileName: &fileName}) - c.Assert(err, IsNil) + defer cIter.Close() commitOrder := []plumbing.Hash{ plumbing.NewHash("918c48b83bd081e863dbe1b80f8998f058cd8294"), @@ -1344,7 +1415,51 @@ func (s *RepositorySuite) TestLogFileForEach(c *C) { cIter.ForEach(func(commit *object.Commit) error { expectedCommitHash := commitOrder[expectedIndex] c.Assert(commit.Hash.String(), Equals, expectedCommitHash.String()) - expectedIndex += 1 + expectedIndex++ + return nil + }) + c.Assert(expectedIndex, Equals, 1) +} + +func (s *RepositorySuite) TestLogNonHeadFile(c *C) { + r, _ := Init(memory.NewStorage(), nil) + err := r.clone(context.Background(), &CloneOptions{ + URL: s.GetBasicLocalRepositoryURL(), + }) + + c.Assert(err, IsNil) + + fileName := "README" + cIter, err := r.Log(&LogOptions{FileName: &fileName}) + c.Assert(err, IsNil) + defer cIter.Close() + + _, err = cIter.Next() + c.Assert(err, Equals, io.EOF) +} + +func (s *RepositorySuite) TestLogAllFileForEach(c *C) { + r, _ := Init(memory.NewStorage(), nil) + err := r.clone(context.Background(), &CloneOptions{ + URL: s.GetBasicLocalRepositoryURL(), + }) + + c.Assert(err, IsNil) + + fileName := "README" + cIter, err := r.Log(&LogOptions{FileName: &fileName, All: true}) + c.Assert(err, IsNil) + defer cIter.Close() + + commitOrder := []plumbing.Hash{ + plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881"), + } + + expectedIndex := 0 + cIter.ForEach(func(commit *object.Commit) error { + expectedCommitHash := commitOrder[expectedIndex] + c.Assert(commit.Hash.String(), Equals, expectedCommitHash.String()) + expectedIndex++ return nil }) c.Assert(expectedIndex, Equals, 1) @@ -1362,6 +1477,7 @@ func (s *RepositorySuite) TestLogInvalidFile(c *C) { cIter, err := r.Log(&LogOptions{FileName: &fileName}) // Not raising an error since `git log -- vendor/foo12.go` responds silently c.Assert(err, IsNil) + defer cIter.Close() _, err = cIter.Next() c.Assert(err, Equals, io.EOF) @@ -1379,8 +1495,8 @@ func (s *RepositorySuite) TestLogFileInitialCommit(c *C) { Order: LogOrderCommitterTime, FileName: &fileName, }) - c.Assert(err, IsNil) + defer cIter.Close() commitOrder := []plumbing.Hash{ plumbing.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d"), @@ -1390,7 +1506,7 @@ func (s *RepositorySuite) TestLogFileInitialCommit(c *C) { cIter.ForEach(func(commit *object.Commit) error { expectedCommitHash := commitOrder[expectedIndex] c.Assert(commit.Hash.String(), Equals, expectedCommitHash.String()) - expectedIndex += 1 + expectedIndex++ return nil }) c.Assert(expectedIndex, Equals, 1) @@ -1410,6 +1526,8 @@ func (s *RepositorySuite) TestLogFileWithOtherParamsFail(c *C) { From: plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"), }) c.Assert(err, IsNil) + defer cIter.Close() + _, iterErr := cIter.Next() c.Assert(iterErr, Equals, io.EOF) } @@ -2343,8 +2461,6 @@ func executeOnPath(path, cmd string) error { c.Stderr = buf c.Stdout = buf - //defer func() { fmt.Println(buf.String()) }() - return c.Run() } |