From 3180dff8c5618b2e146a7fb4ac36be10851c86cc Mon Sep 17 00:00:00 2001 From: kuba-- Date: Thu, 3 Jan 2019 22:47:04 +0100 Subject: Implement git log --all Signed-off-by: kuba-- --- plumbing/object/commit_walker.go | 126 ++++++++++++++++++++++++++++++++++ plumbing/object/commit_walker_file.go | 25 ++++++- 2 files changed, 148 insertions(+), 3 deletions(-) (limited to 'plumbing') 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 } } -- cgit