From c9609eb875fea54ca9ffe409d1581ef94a38b6bc Mon Sep 17 00:00:00 2001 From: kuba-- Date: Wed, 9 Jan 2019 14:45:56 +0100 Subject: Refine Log. Signed-off-by: kuba-- --- plumbing/object/commit_walker.go | 136 ++++++++++++++++++---------------- plumbing/object/commit_walker_file.go | 65 +++++++++------- 2 files changed, 109 insertions(+), 92 deletions(-) (limited to 'plumbing/object') diff --git a/plumbing/object/commit_walker.go b/plumbing/object/commit_walker.go index 92c6572..8c76557 100644 --- a/plumbing/object/commit_walker.go +++ b/plumbing/object/commit_walker.go @@ -186,98 +186,104 @@ func (w *commitPostIterator) Close() {} // commitAllIterator stands for commit iterator for all refs. type commitAllIterator struct { - // el points to the current commit. - el *list.Element + // currCommit points to the current commit. + currCommit *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) +// repoStorer is a repo Storer used to get commits and references. +// commitIterFunc is a commit iterator function, used to iterate through ref commits in chosen order +func NewCommitAllIter(repoStorer storage.Storer, commitIterFunc func(*Commit) CommitIter) (CommitIter, error) { + commitsPath := list.New() + commitsLookup := make(map[plumbing.Hash]*list.Element) + head, err := storer.ResolveReference(repoStorer, plumbing.HEAD) if err != nil { return nil, err } - headCommit, err := GetCommit(s, head.Hash()) - if err != nil { + + // add all references along with the HEAD + if err = addReference(repoStorer, commitIterFunc, head, commitsPath, commitsLookup); err != nil { return nil, err } - err = fn(headCommit).ForEach(func(c *Commit) error { - el := l.PushBack(c) - m[c.Hash] = el - return nil - }) + refIter, err := repoStorer.IterReferences() if err != nil { return nil, err } - - refIter, err := s.IterReferences() + defer refIter.Close() + err = refIter.ForEach( + func(ref *plumbing.Reference) error { + return addReference(repoStorer, commitIterFunc, ref, commitsPath, commitsLookup) + }, + ) 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 - } - el, ok := m[r.Hash()] - if ok { - return nil - } + return &commitAllIterator{commitsPath.Front()}, nil +} + +func addReference( + repoStorer storage.Storer, + commitIterFunc func(*Commit) CommitIter, + ref *plumbing.Reference, + commitsPath *list.List, + commitsLookup map[plumbing.Hash]*list.Element) error { + + _, exists := commitsLookup[ref.Hash()] + if exists { + // we already have it - skip the reference. + return nil + } - var refCommits []*Commit - c, _ := GetCommit(s, r.Hash()) + refCommit, _ := GetCommit(repoStorer, ref.Hash()) + if refCommit == nil { // if it's not a commit - skip it. - if c == nil { - return nil + return nil + } + + var ( + refCommits []*Commit + parent *list.Element + ) + // collect all ref commits to add + commitIter := commitIterFunc(refCommit) + for c, e := commitIter.Next(); e == nil; { + parent, exists = commitsLookup[c.Hash] + if exists { + break } - 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() + refCommits = append(refCommits, c) + c, e = commitIter.Next() + } + commitIter.Close() + + if parent == nil { + // common parent - not found + // add all commits to the path from this ref (maybe it's a HEAD and we don't have anything, yet) + for _, c := range refCommits { + parent = commitsPath.PushBack(c) + commitsLookup[c.Hash] = parent } - 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 - } + } else { + // add ref's commits to the path in reverse order (from the latest) + for i := len(refCommits) - 1; i >= 0; i-- { + c := refCommits[i] + // insert before found common parent + parent = commitsPath.InsertBefore(c, parent) + commitsLookup[c.Hash] = parent } - return nil - }) - if err != nil { - return nil, err } - return &commitAllIterator{l.Front()}, nil + return nil } func (it *commitAllIterator) Next() (*Commit, error) { - if it.el == nil { + if it.currCommit == nil { return nil, io.EOF } - c := it.el.Value.(*Commit) - it.el = it.el.Next() + c := it.currCommit.Value.(*Commit) + it.currCommit = it.currCommit.Next() return c, nil } @@ -305,5 +311,5 @@ func (it *commitAllIterator) ForEach(cb func(*Commit) error) error { } func (it *commitAllIterator) Close() { - it.el = nil + it.currCommit = nil } diff --git a/plumbing/object/commit_walker_file.go b/plumbing/object/commit_walker_file.go index 1af9ec1..6f16e61 100644 --- a/plumbing/object/commit_walker_file.go +++ b/plumbing/object/commit_walker_file.go @@ -3,6 +3,8 @@ package object import ( "io" + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/storer" ) @@ -10,17 +12,19 @@ type commitFileIter struct { fileName string sourceIter CommitIter currentCommit *Commit - all bool + checkParent 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, all bool) CommitIter { +// If checkParent is true then the function double checks if potential parent (next commit in a path) +// is one of the parents in the tree (it's used by `git log --all`). +func NewCommitFileIterFromIter(fileName string, commitIter CommitIter, checkParent bool) CommitIter { iterator := new(commitFileIter) iterator.sourceIter = commitIter iterator.fileName = fileName - iterator.all = all + iterator.checkParent = checkParent return iterator } @@ -74,36 +78,14 @@ func (c *commitFileIter) getNextFileCommit() (*Commit, error) { return nil, diffErr } - foundChangeForFile := false - for _, change := range changes { - 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 - } - } + found := c.hasFileChange(changes, parentCommit) // Storing the current-commit in-case a change is found, and // Updating the current-commit for the next-iteration prevCommit := c.currentCommit c.currentCommit = parentCommit - if foundChangeForFile == true { + if found { return prevCommit, nil } @@ -114,6 +96,35 @@ func (c *commitFileIter) getNextFileCommit() (*Commit, error) { } } +func (c *commitFileIter) hasFileChange(changes Changes, parent *Commit) bool { + for _, change := range changes { + if change.name() != c.fileName { + continue + } + + // filename matches, now check if source iterator contains all commits (from all refs) + if c.checkParent { + if parent != nil && isParentHash(parent.Hash, c.currentCommit) { + return true + } + continue + } + + return true + } + + return false +} + +func isParentHash(hash plumbing.Hash, commit *Commit) bool { + for _, h := range commit.ParentHashes { + if h == hash { + return true + } + } + return false +} + func (c *commitFileIter) ForEach(cb func(*Commit) error) error { for { commit, nextErr := c.Next() -- cgit