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

Commit 434611b

Browse files
authored
Merge pull request #1045 from kuba--/enh-1024/log-all
Implement git log --all
2 parents 791aea3 + c9609eb commit 434611b

File tree

5 files changed

+357
-32
lines changed

5 files changed

+357
-32
lines changed

options.go

+5
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,11 @@ type LogOptions struct {
335335
// Show only those commits in which the specified file was inserted/updated.
336336
// It is equivalent to running `git log -- <file-name>`.
337337
FileName *string
338+
339+
// Pretend as if all the refs in refs/, along with HEAD, are listed on the command line as <commit>.
340+
// It is equivalent to running `git log --all`.
341+
// If set on true, the From option will be ignored.
342+
All bool
338343
}
339344

340345
var (

plumbing/object/commit_walker.go

+132
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package object
22

33
import (
4+
"container/list"
45
"io"
56

67
"gopkg.in/src-d/go-git.v4/plumbing"
78
"gopkg.in/src-d/go-git.v4/plumbing/storer"
9+
"gopkg.in/src-d/go-git.v4/storage"
810
)
911

1012
type commitPreIterator struct {
@@ -181,3 +183,133 @@ func (w *commitPostIterator) ForEach(cb func(*Commit) error) error {
181183
}
182184

183185
func (w *commitPostIterator) Close() {}
186+
187+
// commitAllIterator stands for commit iterator for all refs.
188+
type commitAllIterator struct {
189+
// currCommit points to the current commit.
190+
currCommit *list.Element
191+
}
192+
193+
// NewCommitAllIter returns a new commit iterator for all refs.
194+
// repoStorer is a repo Storer used to get commits and references.
195+
// commitIterFunc is a commit iterator function, used to iterate through ref commits in chosen order
196+
func NewCommitAllIter(repoStorer storage.Storer, commitIterFunc func(*Commit) CommitIter) (CommitIter, error) {
197+
commitsPath := list.New()
198+
commitsLookup := make(map[plumbing.Hash]*list.Element)
199+
head, err := storer.ResolveReference(repoStorer, plumbing.HEAD)
200+
if err != nil {
201+
return nil, err
202+
}
203+
204+
// add all references along with the HEAD
205+
if err = addReference(repoStorer, commitIterFunc, head, commitsPath, commitsLookup); err != nil {
206+
return nil, err
207+
}
208+
refIter, err := repoStorer.IterReferences()
209+
if err != nil {
210+
return nil, err
211+
}
212+
defer refIter.Close()
213+
err = refIter.ForEach(
214+
func(ref *plumbing.Reference) error {
215+
return addReference(repoStorer, commitIterFunc, ref, commitsPath, commitsLookup)
216+
},
217+
)
218+
if err != nil {
219+
return nil, err
220+
}
221+
222+
return &commitAllIterator{commitsPath.Front()}, nil
223+
}
224+
225+
func addReference(
226+
repoStorer storage.Storer,
227+
commitIterFunc func(*Commit) CommitIter,
228+
ref *plumbing.Reference,
229+
commitsPath *list.List,
230+
commitsLookup map[plumbing.Hash]*list.Element) error {
231+
232+
_, exists := commitsLookup[ref.Hash()]
233+
if exists {
234+
// we already have it - skip the reference.
235+
return nil
236+
}
237+
238+
refCommit, _ := GetCommit(repoStorer, ref.Hash())
239+
if refCommit == nil {
240+
// if it's not a commit - skip it.
241+
return nil
242+
}
243+
244+
var (
245+
refCommits []*Commit
246+
parent *list.Element
247+
)
248+
// collect all ref commits to add
249+
commitIter := commitIterFunc(refCommit)
250+
for c, e := commitIter.Next(); e == nil; {
251+
parent, exists = commitsLookup[c.Hash]
252+
if exists {
253+
break
254+
}
255+
refCommits = append(refCommits, c)
256+
c, e = commitIter.Next()
257+
}
258+
commitIter.Close()
259+
260+
if parent == nil {
261+
// common parent - not found
262+
// add all commits to the path from this ref (maybe it's a HEAD and we don't have anything, yet)
263+
for _, c := range refCommits {
264+
parent = commitsPath.PushBack(c)
265+
commitsLookup[c.Hash] = parent
266+
}
267+
} else {
268+
// add ref's commits to the path in reverse order (from the latest)
269+
for i := len(refCommits) - 1; i >= 0; i-- {
270+
c := refCommits[i]
271+
// insert before found common parent
272+
parent = commitsPath.InsertBefore(c, parent)
273+
commitsLookup[c.Hash] = parent
274+
}
275+
}
276+
277+
return nil
278+
}
279+
280+
func (it *commitAllIterator) Next() (*Commit, error) {
281+
if it.currCommit == nil {
282+
return nil, io.EOF
283+
}
284+
285+
c := it.currCommit.Value.(*Commit)
286+
it.currCommit = it.currCommit.Next()
287+
288+
return c, nil
289+
}
290+
291+
func (it *commitAllIterator) ForEach(cb func(*Commit) error) error {
292+
for {
293+
c, err := it.Next()
294+
if err == io.EOF {
295+
break
296+
}
297+
if err != nil {
298+
return err
299+
}
300+
301+
err = cb(c)
302+
if err == storer.ErrStop {
303+
break
304+
}
305+
if err != nil {
306+
return err
307+
}
308+
}
309+
310+
return nil
311+
}
312+
313+
func (it *commitAllIterator) Close() {
314+
it.currCommit = nil
315+
}

plumbing/object/commit_walker_file.go

+40-10
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
11
package object
22

33
import (
4-
"gopkg.in/src-d/go-git.v4/plumbing/storer"
54
"io"
5+
6+
"gopkg.in/src-d/go-git.v4/plumbing"
7+
8+
"gopkg.in/src-d/go-git.v4/plumbing/storer"
69
)
710

811
type commitFileIter struct {
912
fileName string
1013
sourceIter CommitIter
1114
currentCommit *Commit
15+
checkParent bool
1216
}
1317

1418
// NewCommitFileIterFromIter returns a commit iterator which performs diffTree between
1519
// successive trees returned from the commit iterator from the argument. The purpose of this is
1620
// to find the commits that explain how the files that match the path came to be.
17-
func NewCommitFileIterFromIter(fileName string, commitIter CommitIter) CommitIter {
21+
// If checkParent is true then the function double checks if potential parent (next commit in a path)
22+
// is one of the parents in the tree (it's used by `git log --all`).
23+
func NewCommitFileIterFromIter(fileName string, commitIter CommitIter, checkParent bool) CommitIter {
1824
iterator := new(commitFileIter)
1925
iterator.sourceIter = commitIter
2026
iterator.fileName = fileName
27+
iterator.checkParent = checkParent
2128
return iterator
2229
}
2330

@@ -71,20 +78,14 @@ func (c *commitFileIter) getNextFileCommit() (*Commit, error) {
7178
return nil, diffErr
7279
}
7380

74-
foundChangeForFile := false
75-
for _, change := range changes {
76-
if change.name() == c.fileName {
77-
foundChangeForFile = true
78-
break
79-
}
80-
}
81+
found := c.hasFileChange(changes, parentCommit)
8182

8283
// Storing the current-commit in-case a change is found, and
8384
// Updating the current-commit for the next-iteration
8485
prevCommit := c.currentCommit
8586
c.currentCommit = parentCommit
8687

87-
if foundChangeForFile == true {
88+
if found {
8889
return prevCommit, nil
8990
}
9091

@@ -95,6 +96,35 @@ func (c *commitFileIter) getNextFileCommit() (*Commit, error) {
9596
}
9697
}
9798

99+
func (c *commitFileIter) hasFileChange(changes Changes, parent *Commit) bool {
100+
for _, change := range changes {
101+
if change.name() != c.fileName {
102+
continue
103+
}
104+
105+
// filename matches, now check if source iterator contains all commits (from all refs)
106+
if c.checkParent {
107+
if parent != nil && isParentHash(parent.Hash, c.currentCommit) {
108+
return true
109+
}
110+
continue
111+
}
112+
113+
return true
114+
}
115+
116+
return false
117+
}
118+
119+
func isParentHash(hash plumbing.Hash, commit *Commit) bool {
120+
for _, h := range commit.ParentHashes {
121+
if h == hash {
122+
return true
123+
}
124+
}
125+
return false
126+
}
127+
98128
func (c *commitFileIter) ForEach(cb func(*Commit) error) error {
99129
for {
100130
commit, nextErr := c.Next()

repository.go

+58-16
Original file line numberDiff line numberDiff line change
@@ -1027,8 +1027,36 @@ func (r *Repository) PushContext(ctx context.Context, o *PushOptions) error {
10271027

10281028
// Log returns the commit history from the given LogOptions.
10291029
func (r *Repository) Log(o *LogOptions) (object.CommitIter, error) {
1030-
h := o.From
1031-
if o.From == plumbing.ZeroHash {
1030+
fn := commitIterFunc(o.Order)
1031+
if fn == nil {
1032+
return nil, fmt.Errorf("invalid Order=%v", o.Order)
1033+
}
1034+
1035+
var (
1036+
it object.CommitIter
1037+
err error
1038+
)
1039+
if o.All {
1040+
it, err = r.logAll(fn)
1041+
} else {
1042+
it, err = r.log(o.From, fn)
1043+
}
1044+
1045+
if err != nil {
1046+
return nil, err
1047+
}
1048+
1049+
if o.FileName != nil {
1050+
// for `git log --all` also check parent (if the next commit comes from the real parent)
1051+
it = r.logWithFile(*o.FileName, it, o.All)
1052+
}
1053+
1054+
return it, nil
1055+
}
1056+
1057+
func (r *Repository) log(from plumbing.Hash, commitIterFunc func(*object.Commit) object.CommitIter) (object.CommitIter, error) {
1058+
h := from
1059+
if from == plumbing.ZeroHash {
10321060
head, err := r.Head()
10331061
if err != nil {
10341062
return nil, err
@@ -1041,27 +1069,41 @@ func (r *Repository) Log(o *LogOptions) (object.CommitIter, error) {
10411069
if err != nil {
10421070
return nil, err
10431071
}
1072+
return commitIterFunc(commit), nil
1073+
}
10441074

1045-
var commitIter object.CommitIter
1046-
switch o.Order {
1075+
func (r *Repository) logAll(commitIterFunc func(*object.Commit) object.CommitIter) (object.CommitIter, error) {
1076+
return object.NewCommitAllIter(r.Storer, commitIterFunc)
1077+
}
1078+
1079+
func (*Repository) logWithFile(fileName string, commitIter object.CommitIter, checkParent bool) object.CommitIter {
1080+
return object.NewCommitFileIterFromIter(fileName, commitIter, checkParent)
1081+
}
1082+
1083+
func commitIterFunc(order LogOrder) func(c *object.Commit) object.CommitIter {
1084+
switch order {
10471085
case LogOrderDefault:
1048-
commitIter = object.NewCommitPreorderIter(commit, nil, nil)
1086+
return func(c *object.Commit) object.CommitIter {
1087+
return object.NewCommitPreorderIter(c, nil, nil)
1088+
}
10491089
case LogOrderDFS:
1050-
commitIter = object.NewCommitPreorderIter(commit, nil, nil)
1090+
return func(c *object.Commit) object.CommitIter {
1091+
return object.NewCommitPreorderIter(c, nil, nil)
1092+
}
10511093
case LogOrderDFSPost:
1052-
commitIter = object.NewCommitPostorderIter(commit, nil)
1094+
return func(c *object.Commit) object.CommitIter {
1095+
return object.NewCommitPostorderIter(c, nil)
1096+
}
10531097
case LogOrderBSF:
1054-
commitIter = object.NewCommitIterBSF(commit, nil, nil)
1098+
return func(c *object.Commit) object.CommitIter {
1099+
return object.NewCommitIterBSF(c, nil, nil)
1100+
}
10551101
case LogOrderCommitterTime:
1056-
commitIter = object.NewCommitIterCTime(commit, nil, nil)
1057-
default:
1058-
return nil, fmt.Errorf("invalid Order=%v", o.Order)
1059-
}
1060-
1061-
if o.FileName == nil {
1062-
return commitIter, nil
1102+
return func(c *object.Commit) object.CommitIter {
1103+
return object.NewCommitIterCTime(c, nil, nil)
1104+
}
10631105
}
1064-
return object.NewCommitFileIterFromIter(*o.FileName, commitIter), nil
1106+
return nil
10651107
}
10661108

10671109
// Tags returns all the tag References in a repository.

0 commit comments

Comments
 (0)