Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit 1d28459

Browse files
authored
Merge pull request #771 from ilius/PR-log-order
repository.Log: add alternatives for commit traversal order
2 parents ecda5c1 + 3b75e0c commit 1d28459

File tree

6 files changed

+329
-3
lines changed

6 files changed

+329
-3
lines changed

options.go

+15
Original file line numberDiff line numberDiff line change
@@ -307,12 +307,27 @@ func (o *ResetOptions) Validate(r *Repository) error {
307307
return nil
308308
}
309309

310+
type LogOrder int8
311+
312+
const (
313+
LogOrderDefault LogOrder = iota
314+
LogOrderDFS
315+
LogOrderDFSPost
316+
LogOrderBSF
317+
LogOrderCommitterTime
318+
)
319+
310320
// LogOptions describes how a log action should be performed.
311321
type LogOptions struct {
312322
// When the From option is set the log will only contain commits
313323
// reachable from it. If this option is not set, HEAD will be used as
314324
// the default From.
315325
From plumbing.Hash
326+
327+
// The default traversal algorithm is Depth-first search
328+
// set Order=LogOrderCommitterTime for ordering by committer time (more compatible with `git log`)
329+
// set Order=LogOrderBSF for Breadth-first search
330+
Order LogOrder
316331
}
317332

318333
var (

plumbing/object/commit_walker_bfs.go

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package object
2+
3+
import (
4+
"io"
5+
6+
"gopkg.in/src-d/go-git.v4/plumbing"
7+
"gopkg.in/src-d/go-git.v4/plumbing/storer"
8+
)
9+
10+
type bfsCommitIterator struct {
11+
seenExternal map[plumbing.Hash]bool
12+
seen map[plumbing.Hash]bool
13+
queue []*Commit
14+
}
15+
16+
// NewCommitIterBSF returns a CommitIter that walks the commit history,
17+
// starting at the given commit and visiting its parents in pre-order.
18+
// The given callback will be called for each visited commit. Each commit will
19+
// be visited only once. If the callback returns an error, walking will stop
20+
// and will return the error. Other errors might be returned if the history
21+
// cannot be traversed (e.g. missing objects). Ignore allows to skip some
22+
// commits from being iterated.
23+
func NewCommitIterBSF(
24+
c *Commit,
25+
seenExternal map[plumbing.Hash]bool,
26+
ignore []plumbing.Hash,
27+
) CommitIter {
28+
seen := make(map[plumbing.Hash]bool)
29+
for _, h := range ignore {
30+
seen[h] = true
31+
}
32+
33+
return &bfsCommitIterator{
34+
seenExternal: seenExternal,
35+
seen: seen,
36+
queue: []*Commit{c},
37+
}
38+
}
39+
40+
func (w *bfsCommitIterator) appendHash(store storer.EncodedObjectStorer, h plumbing.Hash) error {
41+
if w.seen[h] || w.seenExternal[h] {
42+
return nil
43+
}
44+
c, err := GetCommit(store, h)
45+
if err != nil {
46+
return err
47+
}
48+
w.queue = append(w.queue, c)
49+
return nil
50+
}
51+
52+
func (w *bfsCommitIterator) Next() (*Commit, error) {
53+
var c *Commit
54+
for {
55+
if len(w.queue) == 0 {
56+
return nil, io.EOF
57+
}
58+
c = w.queue[0]
59+
w.queue = w.queue[1:]
60+
61+
if w.seen[c.Hash] || w.seenExternal[c.Hash] {
62+
continue
63+
}
64+
65+
w.seen[c.Hash] = true
66+
67+
for _, h := range c.ParentHashes {
68+
err := w.appendHash(c.s, h)
69+
if err != nil {
70+
return nil, nil
71+
}
72+
}
73+
74+
return c, nil
75+
}
76+
}
77+
78+
func (w *bfsCommitIterator) ForEach(cb func(*Commit) error) error {
79+
for {
80+
c, err := w.Next()
81+
if err == io.EOF {
82+
break
83+
}
84+
if err != nil {
85+
return err
86+
}
87+
88+
err = cb(c)
89+
if err == storer.ErrStop {
90+
break
91+
}
92+
if err != nil {
93+
return err
94+
}
95+
}
96+
97+
return nil
98+
}
99+
100+
func (w *bfsCommitIterator) Close() {}
+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package object
2+
3+
import (
4+
"io"
5+
6+
"github.com/emirpasic/gods/trees/binaryheap"
7+
8+
"gopkg.in/src-d/go-git.v4/plumbing"
9+
"gopkg.in/src-d/go-git.v4/plumbing/storer"
10+
)
11+
12+
type commitIteratorByCTime struct {
13+
seenExternal map[plumbing.Hash]bool
14+
seen map[plumbing.Hash]bool
15+
heap *binaryheap.Heap
16+
}
17+
18+
// NewCommitIterCTime returns a CommitIter that walks the commit history,
19+
// starting at the given commit and visiting its parents while preserving Committer Time order.
20+
// this appears to be the closest order to `git log`
21+
// The given callback will be called for each visited commit. Each commit will
22+
// be visited only once. If the callback returns an error, walking will stop
23+
// and will return the error. Other errors might be returned if the history
24+
// cannot be traversed (e.g. missing objects). Ignore allows to skip some
25+
// commits from being iterated.
26+
func NewCommitIterCTime(
27+
c *Commit,
28+
seenExternal map[plumbing.Hash]bool,
29+
ignore []plumbing.Hash,
30+
) CommitIter {
31+
seen := make(map[plumbing.Hash]bool)
32+
for _, h := range ignore {
33+
seen[h] = true
34+
}
35+
36+
heap := binaryheap.NewWith(func(a, b interface{}) int {
37+
if a.(*Commit).Committer.When.Before(b.(*Commit).Committer.When) {
38+
return 1
39+
}
40+
return -1
41+
})
42+
heap.Push(c)
43+
44+
return &commitIteratorByCTime{
45+
seenExternal: seenExternal,
46+
seen: seen,
47+
heap: heap,
48+
}
49+
}
50+
51+
func (w *commitIteratorByCTime) Next() (*Commit, error) {
52+
var c *Commit
53+
for {
54+
cIn, ok := w.heap.Pop()
55+
if !ok {
56+
return nil, io.EOF
57+
}
58+
c = cIn.(*Commit)
59+
60+
if w.seen[c.Hash] || w.seenExternal[c.Hash] {
61+
continue
62+
}
63+
64+
w.seen[c.Hash] = true
65+
66+
for _, h := range c.ParentHashes {
67+
if w.seen[h] || w.seenExternal[h] {
68+
continue
69+
}
70+
pc, err := GetCommit(c.s, h)
71+
if err != nil {
72+
return nil, err
73+
}
74+
w.heap.Push(pc)
75+
}
76+
77+
return c, nil
78+
}
79+
}
80+
81+
func (w *commitIteratorByCTime) ForEach(cb func(*Commit) error) error {
82+
for {
83+
c, err := w.Next()
84+
if err == io.EOF {
85+
break
86+
}
87+
if err != nil {
88+
return err
89+
}
90+
91+
err = cb(c)
92+
if err == storer.ErrStop {
93+
break
94+
}
95+
if err != nil {
96+
return err
97+
}
98+
}
99+
100+
return nil
101+
}
102+
103+
func (w *commitIteratorByCTime) Close() {}

plumbing/object/commit_walker_test.go

+96
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,99 @@ func (s *CommitWalkerSuite) TestCommitPostIteratorWithIgnore(c *C) {
132132
c.Assert(commit.Hash.String(), Equals, expected[i])
133133
}
134134
}
135+
136+
func (s *CommitWalkerSuite) TestCommitCTimeIterator(c *C) {
137+
commit := s.commit(c, s.Fixture.Head)
138+
139+
var commits []*Commit
140+
NewCommitIterCTime(commit, nil, nil).ForEach(func(c *Commit) error {
141+
commits = append(commits, c)
142+
return nil
143+
})
144+
145+
c.Assert(commits, HasLen, 8)
146+
147+
expected := []string{
148+
"6ecf0ef2c2dffb796033e5a02219af86ec6584e5", // 2015-04-05T23:30:47+02:00
149+
"918c48b83bd081e863dbe1b80f8998f058cd8294", // 2015-03-31T13:56:18+02:00
150+
"af2d6a6954d532f8ffb47615169c8fdf9d383a1a", // 2015-03-31T13:51:51+02:00
151+
"1669dce138d9b841a518c64b10914d88f5e488ea", // 2015-03-31T13:48:14+02:00
152+
"a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69", // 2015-03-31T13:47:14+02:00
153+
"35e85108805c84807bc66a02d91535e1e24b38b9", // 2015-03-31T13:46:24+02:00
154+
"b8e471f58bcbca63b07bda20e428190409c2db47", // 2015-03-31T13:44:52+02:00
155+
"b029517f6300c2da0f4b651b8642506cd6aaf45d", // 2015-03-31T13:42:21+02:00
156+
}
157+
for i, commit := range commits {
158+
c.Assert(commit.Hash.String(), Equals, expected[i])
159+
}
160+
}
161+
162+
func (s *CommitWalkerSuite) TestCommitCTimeIteratorWithIgnore(c *C) {
163+
commit := s.commit(c, s.Fixture.Head)
164+
165+
var commits []*Commit
166+
NewCommitIterCTime(commit, nil, []plumbing.Hash{
167+
plumbing.NewHash("af2d6a6954d532f8ffb47615169c8fdf9d383a1a"),
168+
}).ForEach(func(c *Commit) error {
169+
commits = append(commits, c)
170+
return nil
171+
})
172+
173+
c.Assert(commits, HasLen, 2)
174+
175+
expected := []string{
176+
"6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
177+
"918c48b83bd081e863dbe1b80f8998f058cd8294",
178+
}
179+
for i, commit := range commits {
180+
c.Assert(commit.Hash.String(), Equals, expected[i])
181+
}
182+
}
183+
184+
func (s *CommitWalkerSuite) TestCommitBSFIterator(c *C) {
185+
commit := s.commit(c, s.Fixture.Head)
186+
187+
var commits []*Commit
188+
NewCommitIterBSF(commit, nil, nil).ForEach(func(c *Commit) error {
189+
commits = append(commits, c)
190+
return nil
191+
})
192+
193+
c.Assert(commits, HasLen, 8)
194+
195+
expected := []string{
196+
"6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
197+
"918c48b83bd081e863dbe1b80f8998f058cd8294",
198+
"af2d6a6954d532f8ffb47615169c8fdf9d383a1a",
199+
"1669dce138d9b841a518c64b10914d88f5e488ea",
200+
"35e85108805c84807bc66a02d91535e1e24b38b9",
201+
"a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69",
202+
"b029517f6300c2da0f4b651b8642506cd6aaf45d",
203+
"b8e471f58bcbca63b07bda20e428190409c2db47",
204+
}
205+
for i, commit := range commits {
206+
c.Assert(commit.Hash.String(), Equals, expected[i])
207+
}
208+
}
209+
210+
func (s *CommitWalkerSuite) TestCommitBSFIteratorWithIgnore(c *C) {
211+
commit := s.commit(c, s.Fixture.Head)
212+
213+
var commits []*Commit
214+
NewCommitIterBSF(commit, nil, []plumbing.Hash{
215+
plumbing.NewHash("af2d6a6954d532f8ffb47615169c8fdf9d383a1a"),
216+
}).ForEach(func(c *Commit) error {
217+
commits = append(commits, c)
218+
return nil
219+
})
220+
221+
c.Assert(commits, HasLen, 2)
222+
223+
expected := []string{
224+
"6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
225+
"918c48b83bd081e863dbe1b80f8998f058cd8294",
226+
}
227+
for i, commit := range commits {
228+
c.Assert(commit.Hash.String(), Equals, expected[i])
229+
}
230+
}

repository.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,19 @@ func (r *Repository) Log(o *LogOptions) (object.CommitIter, error) {
728728
return nil, err
729729
}
730730

731-
return object.NewCommitPreorderIter(commit, nil, nil), nil
731+
switch o.Order {
732+
case LogOrderDefault:
733+
return object.NewCommitPreorderIter(commit, nil, nil), nil
734+
case LogOrderDFS:
735+
return object.NewCommitPreorderIter(commit, nil, nil), nil
736+
case LogOrderDFSPost:
737+
return object.NewCommitPostorderIter(commit, nil), nil
738+
case LogOrderBSF:
739+
return object.NewCommitIterBSF(commit, nil, nil), nil
740+
case LogOrderCommitterTime:
741+
return object.NewCommitIterCTime(commit, nil, nil), nil
742+
}
743+
return nil, fmt.Errorf("invalid Order=%v", o.Order)
732744
}
733745

734746
// Tags returns all the References from Tags. This method returns all the tag

repository_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -870,7 +870,7 @@ func (s *RepositorySuite) TestLog(c *C) {
870870
c.Assert(err, IsNil)
871871

872872
cIter, err := r.Log(&LogOptions{
873-
plumbing.NewHash("b8e471f58bcbca63b07bda20e428190409c2db47"),
873+
From: plumbing.NewHash("b8e471f58bcbca63b07bda20e428190409c2db47"),
874874
})
875875

876876
c.Assert(err, IsNil)
@@ -930,7 +930,7 @@ func (s *RepositorySuite) TestLogError(c *C) {
930930
c.Assert(err, IsNil)
931931

932932
_, err = r.Log(&LogOptions{
933-
plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
933+
From: plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
934934
})
935935
c.Assert(err, NotNil)
936936
}

0 commit comments

Comments
 (0)