diff options
author | Antonio Jesus Navarro Perez <antonio@sourced.tech> | 2017-04-10 16:48:40 +0200 |
---|---|---|
committer | Antonio Jesus Navarro Perez <antonio@sourced.tech> | 2017-04-11 11:22:45 +0200 |
commit | be8d19438ada078a8598e366ab74aa09e4c521cd (patch) | |
tree | d9edb36326016ea35d38f68810e9ce70e538e7a0 | |
parent | 3daede53835e8572b2957a016f068781db646567 (diff) | |
download | go-git-be8d19438ada078a8598e366ab74aa09e4c521cd.tar.gz |
Add Repository.Log() method (fix #298)
- CommitIter is now an interface
- The old CommitIter implementation is now called StorerCommitIter
- CommitWalker and CommitWalkerPost are now iterators (CommitPreIterator and CommitPostIterator).
- Remove Commit.History() method. There are so many ways to iterate a commit history, depending of the use case. Now, instead of use the History() method, you must use CommitPreIterator or CommitPostIterator.
- Move commitSorterer to references.go because is the only place that it is used, and it must not be used into another place.
- Make References method private, it must only be used into blame logic.
- Added a TODO into references method, where the sortCommits is used to remove it in a near future.
-rw-r--r-- | blame.go | 4 | ||||
-rw-r--r-- | options.go | 8 | ||||
-rw-r--r-- | plumbing/object/commit.go | 65 | ||||
-rw-r--r-- | plumbing/object/commit_test.go | 10 | ||||
-rw-r--r-- | plumbing/object/commit_walker.go | 169 | ||||
-rw-r--r-- | plumbing/object/commit_walker_test.go | 10 | ||||
-rw-r--r-- | plumbing/revlist/revlist.go | 23 | ||||
-rw-r--r-- | references.go | 28 | ||||
-rw-r--r-- | references_test.go | 2 | ||||
-rw-r--r-- | repository.go | 71 | ||||
-rw-r--r-- | repository_test.go | 75 |
11 files changed, 307 insertions, 158 deletions
@@ -142,11 +142,11 @@ type blame struct { graph [][]*object.Commit } -// calculte the history of a file "path", starting from commit "from", sorted by commit date. +// calculate the history of a file "path", starting from commit "from", sorted by commit date. func (b *blame) fillRevs() error { var err error - b.revs, err = References(b.fRev, b.path) + b.revs, err = references(b.fRev, b.path) if err != nil { return err } @@ -177,3 +177,11 @@ type SubmoduleUpdateOptions struct { // submodules (and so on). Until the SubmoduleRescursivity is reached. RecurseSubmodules SubmoduleRescursivity } + +// LogOptions describes how a log action should be performed. +type LogOptions struct { + // When the From option is set the log will only contain commits + // reachable from it. If this option is not set, HEAD will be used as + // the default From. + From plumbing.Hash +} diff --git a/plumbing/object/commit.go b/plumbing/object/commit.go index 7507bc7..ffbb9f9 100644 --- a/plumbing/object/commit.go +++ b/plumbing/object/commit.go @@ -5,7 +5,6 @@ import ( "bytes" "fmt" "io" - "sort" "strings" "gopkg.in/src-d/go-git.v4/plumbing" @@ -64,7 +63,7 @@ func (c *Commit) Tree() (*Tree, error) { } // Parents return a CommitIter to the parent Commits. -func (c *Commit) Parents() *CommitIter { +func (c *Commit) Parents() CommitIter { return NewCommitIter(c.s, storer.NewEncodedObjectLookupIter(c.s, plumbing.CommitObject, c.parents), ) @@ -163,19 +162,6 @@ func (c *Commit) Decode(o plumbing.EncodedObject) (err error) { } } -// History returns a slice with the previous commits in the history of this -// commit, sorted in reverse chronological order. -func (c *Commit) History() ([]*Commit, error) { - var commits []*Commit - err := WalkCommitHistory(c, func(commit *Commit) error { - commits = append(commits, commit) - return nil - }) - - ReverseSortCommits(commits) - return commits, err -} - // Encode transforms a Commit into a plumbing.EncodedObject. func (b *Commit) Encode(o plumbing.EncodedObject) error { o.SetType(plumbing.CommitObject) @@ -231,24 +217,31 @@ func indent(t string) string { return strings.Join(output, "\n") } -// CommitIter provides an iterator for a set of commits. -type CommitIter struct { +// CommitIter is a generic closable interface for iterating over commits. +type CommitIter interface { + Next() (*Commit, error) + ForEach(func(*Commit) error) error + Close() +} + +// storerCommitIter provides an iterator from commits in an EncodedObjectStorer. +type storerCommitIter struct { storer.EncodedObjectIter s storer.EncodedObjectStorer } // NewCommitIter takes a storer.EncodedObjectStorer and a -// storer.EncodedObjectIter and returns a *CommitIter that iterates over all +// storer.EncodedObjectIter and returns a CommitIter that iterates over all // commits contained in the storer.EncodedObjectIter. // // Any non-commit object returned by the storer.EncodedObjectIter is skipped. -func NewCommitIter(s storer.EncodedObjectStorer, iter storer.EncodedObjectIter) *CommitIter { - return &CommitIter{iter, s} +func NewCommitIter(s storer.EncodedObjectStorer, iter storer.EncodedObjectIter) CommitIter { + return &storerCommitIter{iter, s} } // Next moves the iterator to the next commit and returns a pointer to it. If // there are no more commits, it returns io.EOF. -func (iter *CommitIter) Next() (*Commit, error) { +func (iter *storerCommitIter) Next() (*Commit, error) { obj, err := iter.EncodedObjectIter.Next() if err != nil { return nil, err @@ -260,7 +253,7 @@ func (iter *CommitIter) Next() (*Commit, error) { // ForEach call the cb function for each commit contained on this iter until // an error appends or the end of the iter is reached. If ErrStop is sent // the iteration is stop but no error is returned. The iterator is closed. -func (iter *CommitIter) ForEach(cb func(*Commit) error) error { +func (iter *storerCommitIter) ForEach(cb func(*Commit) error) error { return iter.EncodedObjectIter.ForEach(func(obj plumbing.EncodedObject) error { c, err := DecodeCommit(iter.s, obj) if err != nil { @@ -271,30 +264,6 @@ func (iter *CommitIter) ForEach(cb func(*Commit) error) error { }) } -type commitSorterer struct { - l []*Commit -} - -func (s commitSorterer) Len() int { - return len(s.l) -} - -func (s commitSorterer) Less(i, j int) bool { - return s.l[i].Committer.When.Before(s.l[j].Committer.When) -} - -func (s commitSorterer) Swap(i, j int) { - s.l[i], s.l[j] = s.l[j], s.l[i] -} - -// SortCommits sorts a commit list by commit date, from older to newer. -func SortCommits(l []*Commit) { - s := &commitSorterer{l} - sort.Sort(s) -} - -// ReverseSortCommits sorts a commit list by commit date, from newer to older. -func ReverseSortCommits(l []*Commit) { - s := &commitSorterer{l} - sort.Sort(sort.Reverse(s)) +func (iter *storerCommitIter) Close() { + iter.EncodedObjectIter.Close() } diff --git a/plumbing/object/commit_test.go b/plumbing/object/commit_test.go index 8b4ee2a..c1f49db 100644 --- a/plumbing/object/commit_test.go +++ b/plumbing/object/commit_test.go @@ -62,6 +62,8 @@ func (s *SuiteCommit) TestParents(c *C) { c.Assert(err, IsNil) c.Assert(output, DeepEquals, expected) + + i.Close() } func (s *SuiteCommit) TestCommitEncodeDecodeIdempotent(c *C) { @@ -110,14 +112,6 @@ func (s *SuiteCommit) TestNumParents(c *C) { c.Assert(s.Commit.NumParents(), Equals, 2) } -func (s *SuiteCommit) TestHistory(c *C) { - commits, err := s.Commit.History() - c.Assert(err, IsNil) - c.Assert(commits, HasLen, 5) - c.Assert(commits[0].Hash.String(), Equals, s.Commit.Hash.String()) - c.Assert(commits[len(commits)-1].Hash.String(), Equals, "b029517f6300c2da0f4b651b8642506cd6aaf45d") -} - func (s *SuiteCommit) TestString(c *C) { c.Assert(s.Commit.String(), Equals, ""+ "commit 1669dce138d9b841a518c64b10914d88f5e488ea\n"+ diff --git a/plumbing/object/commit_walker.go b/plumbing/object/commit_walker.go index 681ea5e..3316fab 100644 --- a/plumbing/object/commit_walker.go +++ b/plumbing/object/commit_walker.go @@ -4,93 +4,150 @@ import ( "io" "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/storer" ) -type commitWalker struct { +type commitPreIterator struct { seen map[plumbing.Hash]bool - stack []*CommitIter + stack []CommitIter start *Commit - cb func(*Commit) error } -// WalkCommitHistory walks the commit history, starting at the given commit and -// visiting its parents in pre-order. The given callback will be called for each -// visited commit. Each commit will be visited only once. If the callback returns -// an error, walking will stop and will return the error. Other errors might be -// returned if the history cannot be traversed (e.g. missing objects). -func WalkCommitHistory(c *Commit, cb func(*Commit) error) error { - w := &commitWalker{ +// NewCommitPreIterator returns a CommitIter that walks the commit history, +// starting at the given commit and visiting its parents in pre-order. +// The given callback will be called for each visited commit. Each commit will +// be visited only once. If the callback returns an error, walking will stop +// and will return the error. Other errors might be returned if the history +// cannot be traversed (e.g. missing objects). +func NewCommitPreIterator(c *Commit) CommitIter { + return &commitPreIterator{ seen: make(map[plumbing.Hash]bool), - stack: make([]*CommitIter, 0), + stack: make([]CommitIter, 0), start: c, - cb: cb, } - - return w.walk() } -func (w *commitWalker) walk() error { - var commit *Commit +func (w *commitPreIterator) Next() (*Commit, error) { + var c *Commit + for { + if w.start != nil { + c = w.start + w.start = nil + } else { + current := len(w.stack) - 1 + if current < 0 { + return nil, io.EOF + } - if w.start != nil { - commit = w.start - w.start = nil - } else { - current := len(w.stack) - 1 - if current < 0 { - return nil + var err error + c, err = w.stack[current].Next() + if err == io.EOF { + w.stack = w.stack[:current] + continue + } + + if err != nil { + return nil, err + } + } + + // check and update seen + if w.seen[c.Hash] { + continue } - var err error - commit, err = w.stack[current].Next() + w.seen[c.Hash] = true + if c.NumParents() > 0 { + w.stack = append(w.stack, c.Parents()) + } + + return c, nil + } +} + +func (w *commitPreIterator) ForEach(cb func(*Commit) error) error { + for { + c, err := w.Next() if err == io.EOF { - w.stack = w.stack[:current] - return w.walk() + break + } + if err != nil { + return err } + err = cb(c) + if err == storer.ErrStop { + break + } if err != nil { return err } } - // check and update seen - if w.seen[commit.Hash] { - return w.walk() - } + return nil +} - w.seen[commit.Hash] = true - if commit.NumParents() > 0 { - w.stack = append(w.stack, commit.Parents()) - } +func (w *commitPreIterator) Close() {} - if err := w.cb(commit); err != nil { - return err - } +type commitPostIterator struct { + stack []*Commit + seen map[plumbing.Hash]bool +} - return w.walk() +// NewCommitPostIterator returns a CommitIter that walks the commit +// history like WalkCommitHistory but in post-order. This means that after +// walking a merge commit, the merged commit will be walked before the base +// it was merged on. This can be useful if you wish to see the history in +// chronological order. +func NewCommitPostIterator(c *Commit) CommitIter { + return &commitPostIterator{ + stack: []*Commit{c}, + seen: make(map[plumbing.Hash]bool), + } } -// WalkCommitHistoryPost walks the commit history like WalkCommitHistory -// but in post-order. This means that after walking a merge commit, the -// merged commit will be walked before the base it was merged on. This -// can be useful if you wish to see the history in chronological order. -func WalkCommitHistoryPost(c *Commit, cb func(*Commit) error) error { - stack := []*Commit{c} - seen := make(map[plumbing.Hash]bool) - for len(stack) > 0 { - c := stack[len(stack)-1] - stack = stack[:len(stack)-1] - if seen[c.Hash] { - continue +func (w *commitPostIterator) Next() (*Commit, error) { + for { + if len(w.stack) == 0 { + return nil, io.EOF } - seen[c.Hash] = true - if err := cb(c); err != nil { - return err + + c := w.stack[len(w.stack)-1] + w.stack = w.stack[:len(w.stack)-1] + if w.seen[c.Hash] { + continue } - c.Parents().ForEach(func(pcm *Commit) error { - stack = append(stack, pcm) + w.seen[c.Hash] = true + + err := c.Parents().ForEach(func(pcm *Commit) error { + w.stack = append(w.stack, pcm) return nil }) + + return c, err + } +} + +func (w *commitPostIterator) ForEach(cb func(*Commit) error) error { + for { + c, err := w.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 (w *commitPostIterator) Close() {} diff --git a/plumbing/object/commit_walker_test.go b/plumbing/object/commit_walker_test.go index aa54de0..7d04bce 100644 --- a/plumbing/object/commit_walker_test.go +++ b/plumbing/object/commit_walker_test.go @@ -8,11 +8,12 @@ type CommitWalkerSuite struct { var _ = Suite(&CommitWalkerSuite{}) -func (s *CommitWalkerSuite) TestWalkHistory(c *C) { +func (s *CommitWalkerSuite) TestCommitPreIterator(c *C) { commit := s.commit(c, s.Fixture.Head) var commits []*Commit - WalkCommitHistory(commit, func(c *Commit) error { + wIter := NewCommitPreIterator(commit) + wIter.ForEach(func(c *Commit) error { commits = append(commits, c) return nil }) @@ -34,11 +35,12 @@ func (s *CommitWalkerSuite) TestWalkHistory(c *C) { } } -func (s *CommitWalkerSuite) TestWalkHistoryPost(c *C) { +func (s *CommitWalkerSuite) TestCommitPostIterator(c *C) { commit := s.commit(c, s.Fixture.Head) var commits []*Commit - WalkCommitHistoryPost(commit, func(c *Commit) error { + wIter := NewCommitPostIterator(commit) + wIter.ForEach(func(c *Commit) error { commits = append(commits, c) return nil }) diff --git a/plumbing/revlist/revlist.go b/plumbing/revlist/revlist.go index eb9afaf..fbd1bd9 100644 --- a/plumbing/revlist/revlist.go +++ b/plumbing/revlist/revlist.go @@ -82,20 +82,21 @@ func reachableObjects( commit *object.Commit, seen map[plumbing.Hash]bool, cb func(h plumbing.Hash)) error { - return object.WalkCommitHistory(commit, func(commit *object.Commit) error { - if seen[commit.Hash] { - return nil - } + return object.NewCommitPreIterator(commit). + ForEach(func(commit *object.Commit) error { + if seen[commit.Hash] { + return nil + } - cb(commit.Hash) + cb(commit.Hash) - tree, err := commit.Tree() - if err != nil { - return err - } + tree, err := commit.Tree() + if err != nil { + return err + } - return iterateCommitTrees(seen, tree, cb) - }) + return iterateCommitTrees(seen, tree, cb) + }) } // iterateCommitTrees iterate all reachable trees from the given commit diff --git a/references.go b/references.go index 957d741..fc81103 100644 --- a/references.go +++ b/references.go @@ -2,6 +2,7 @@ package git import ( "io" + "sort" "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/object" @@ -23,19 +24,42 @@ import ( // - Cherry-picks are not detected unless there are no commits between them and // therefore can appear repeated in the list. (see git path-id for hints on how // to fix this). -func References(c *object.Commit, path string) ([]*object.Commit, error) { +func references(c *object.Commit, path string) ([]*object.Commit, error) { var result []*object.Commit seen := make(map[plumbing.Hash]struct{}, 0) if err := walkGraph(&result, &seen, c, path); err != nil { return nil, err } - object.SortCommits(result) + // TODO result should be returned without ordering + sortCommits(result) // for merges of identical cherry-picks return removeComp(path, result, equivalent) } +type commitSorterer struct { + l []*object.Commit +} + +func (s commitSorterer) Len() int { + return len(s.l) +} + +func (s commitSorterer) Less(i, j int) bool { + return s.l[i].Committer.When.Before(s.l[j].Committer.When) +} + +func (s commitSorterer) Swap(i, j int) { + s.l[i], s.l[j] = s.l[j], s.l[i] +} + +// SortCommits sorts a commit list by commit date, from older to newer. +func sortCommits(l []*object.Commit) { + s := &commitSorterer{l} + sort.Sort(s) +} + // Recursive traversal of the commit graph, generating a linear history of the // path. func walkGraph(result *[]*object.Commit, seen *map[plumbing.Hash]struct{}, current *object.Commit, path string) error { diff --git a/references_test.go b/references_test.go index e901ec0..22efc4d 100644 --- a/references_test.go +++ b/references_test.go @@ -294,7 +294,7 @@ func (s *ReferencesSuite) TestRevList(c *C) { commit, err := r.CommitObject(plumbing.NewHash(t.commit)) c.Assert(err, IsNil) - revs, err := References(commit, t.path) + revs, err := references(commit, t.path) c.Assert(err, IsNil) c.Assert(len(revs), Equals, len(t.revs)) diff --git a/repository.go b/repository.go index 9e325e4..d9a1d7e 100644 --- a/repository.go +++ b/repository.go @@ -608,6 +608,26 @@ func (r *Repository) Push(o *PushOptions) error { return remote.Push(o) } +// 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 + } + + return object.NewCommitPreIterator(commit), nil +} + // Tags returns all the References from Tags. This method returns all the tag // types, lightweight, and annotated ones. func (r *Repository) Tags() (storer.ReferenceIter, error) { @@ -671,7 +691,7 @@ func (r *Repository) CommitObject(h plumbing.Hash) (*object.Commit, error) { } // CommitObjects returns an unsorted CommitIter with all the commits in the repository. -func (r *Repository) CommitObjects() (*object.CommitIter, error) { +func (r *Repository) CommitObjects() (object.CommitIter, error) { iter, err := r.Storer.IterEncodedObjects(plumbing.CommitObject) if err != nil { return nil, err @@ -838,29 +858,28 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err commit = c } case revision.CaretReg: - history, err := commit.History() - - if err != nil { - return &plumbing.ZeroHash, err - } + history := object.NewCommitPreIterator(commit) re := item.(revision.CaretReg).Regexp negate := item.(revision.CaretReg).Negate var c *object.Commit - for i := 0; i < len(history); i++ { - if !negate && re.MatchString(history[i].Message) { - c = history[i] - - break + err := history.ForEach(func(hc *object.Commit) error { + if !negate && re.MatchString(hc.Message) { + c = hc + return storer.ErrStop } - if negate && !re.MatchString(history[i].Message) { - c = history[i] - - break + if negate && !re.MatchString(hc.Message) { + c = hc + return storer.ErrStop } + + return nil + }) + if err != nil { + return &plumbing.ZeroHash, err } if c == nil { @@ -869,21 +888,21 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err commit = c case revision.AtDate: - history, err := commit.History() - - if err != nil { - return &plumbing.ZeroHash, err - } + history := object.NewCommitPreIterator(commit) date := item.(revision.AtDate).Date - var c *object.Commit - for i := 0; i < len(history); i++ { - if date.Equal(history[i].Committer.When.UTC()) || history[i].Committer.When.UTC().Before(date) { - c = history[i] - - break + var c *object.Commit + err := history.ForEach(func(hc *object.Commit) error { + if date.Equal(hc.Committer.When.UTC()) || hc.Committer.When.UTC().Before(date) { + c = hc + return storer.ErrStop } + + return nil + }) + if err != nil { + return &plumbing.ZeroHash, err } if c == nil { diff --git a/repository_test.go b/repository_test.go index 352ff21..77bfde2 100644 --- a/repository_test.go +++ b/repository_test.go @@ -3,6 +3,7 @@ package git import ( "bytes" "fmt" + "io" "io/ioutil" "os" "os/exec" @@ -688,6 +689,80 @@ func (s *RepositorySuite) TestPushNonExistentRemote(c *C) { c.Assert(err, ErrorMatches, ".*remote not found.*") } +func (s *RepositorySuite) TestLog(c *C) { + r, _ := Init(memory.NewStorage(), nil) + err := r.clone(&CloneOptions{ + URL: s.GetBasicLocalRepositoryURL(), + }) + + c.Assert(err, IsNil) + + cIter, err := r.Log(&LogOptions{ + plumbing.NewHash("b8e471f58bcbca63b07bda20e428190409c2db47"), + }) + + c.Assert(err, IsNil) + + commitOrder := []plumbing.Hash{ + 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) +} + +func (s *RepositorySuite) TestLogHead(c *C) { + r, _ := Init(memory.NewStorage(), nil) + err := r.clone(&CloneOptions{ + URL: s.GetBasicLocalRepositoryURL(), + }) + + c.Assert(err, IsNil) + + cIter, err := r.Log(&LogOptions{}) + + c.Assert(err, IsNil) + + commitOrder := []plumbing.Hash{ + plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"), + 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) +} + +func (s *RepositorySuite) TestLogError(c *C) { + r, _ := Init(memory.NewStorage(), nil) + err := r.clone(&CloneOptions{ + URL: s.GetBasicLocalRepositoryURL(), + }) + + c.Assert(err, IsNil) + + _, err = r.Log(&LogOptions{ + plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + }) + c.Assert(err, NotNil) +} + func (s *RepositorySuite) TestCommit(c *C) { r, _ := Init(memory.NewStorage(), nil) err := r.clone(&CloneOptions{ |