aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMáximo Cuadros <mcuadros@gmail.com>2019-01-11 02:22:06 +0100
committerGitHub <noreply@github.com>2019-01-11 02:22:06 +0100
commit434611b74cb54538088c6aeed4ed27d3044064fa (patch)
tree19161a599c8f9bdfbfefa88ae02ee9381bf57fcc
parent791aea319719ee757cb862e83ec43b25113de2c1 (diff)
parentc9609eb875fea54ca9ffe409d1581ef94a38b6bc (diff)
downloadgo-git-434611b74cb54538088c6aeed4ed27d3044064fa.tar.gz
Merge pull request #1045 from kuba--/enh-1024/log-allv4.9.0
Implement git log --all
-rw-r--r--options.go5
-rw-r--r--plumbing/object/commit_walker.go132
-rw-r--r--plumbing/object/commit_walker_file.go50
-rw-r--r--repository.go74
-rw-r--r--repository_test.go128
5 files changed, 357 insertions, 32 deletions
diff --git a/options.go b/options.go
index 5d10a88..ed7689a 100644
--- a/options.go
+++ b/options.go
@@ -335,6 +335,11 @@ type LogOptions struct {
// Show only those commits in which the specified file was inserted/updated.
// It is equivalent to running `git log -- <file-name>`.
FileName *string
+
+ // Pretend as if all the refs in refs/, along with HEAD, are listed on the command line as <commit>.
+ // It is equivalent to running `git log --all`.
+ // If set on true, the From option will be ignored.
+ All bool
}
var (
diff --git a/plumbing/object/commit_walker.go b/plumbing/object/commit_walker.go
index 40ad258..8c76557 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,133 @@ func (w *commitPostIterator) ForEach(cb func(*Commit) error) error {
}
func (w *commitPostIterator) Close() {}
+
+// commitAllIterator stands for commit iterator for all refs.
+type commitAllIterator struct {
+ // currCommit points to the current commit.
+ currCommit *list.Element
+}
+
+// NewCommitAllIter returns a new commit iterator for all refs.
+// 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
+ }
+
+ // add all references along with the HEAD
+ if err = addReference(repoStorer, commitIterFunc, head, commitsPath, commitsLookup); err != nil {
+ return nil, err
+ }
+ refIter, err := repoStorer.IterReferences()
+ if err != nil {
+ return nil, err
+ }
+ defer refIter.Close()
+ err = refIter.ForEach(
+ func(ref *plumbing.Reference) error {
+ return addReference(repoStorer, commitIterFunc, ref, commitsPath, commitsLookup)
+ },
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ 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
+ }
+
+ refCommit, _ := GetCommit(repoStorer, ref.Hash())
+ if refCommit == nil {
+ // if it's not a commit - skip it.
+ 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
+ }
+ 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
+ }
+ } 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
+}
+
+func (it *commitAllIterator) Next() (*Commit, error) {
+ if it.currCommit == nil {
+ return nil, io.EOF
+ }
+
+ c := it.currCommit.Value.(*Commit)
+ it.currCommit = it.currCommit.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.currCommit = nil
+}
diff --git a/plumbing/object/commit_walker_file.go b/plumbing/object/commit_walker_file.go
index 84e738a..6f16e61 100644
--- a/plumbing/object/commit_walker_file.go
+++ b/plumbing/object/commit_walker_file.go
@@ -1,23 +1,30 @@
package object
import (
- "gopkg.in/src-d/go-git.v4/plumbing/storer"
"io"
+
+ "gopkg.in/src-d/go-git.v4/plumbing"
+
+ "gopkg.in/src-d/go-git.v4/plumbing/storer"
)
type commitFileIter struct {
fileName string
sourceIter CommitIter
currentCommit *Commit
+ 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) 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.checkParent = checkParent
return iterator
}
@@ -71,20 +78,14 @@ func (c *commitFileIter) getNextFileCommit() (*Commit, error) {
return nil, diffErr
}
- foundChangeForFile := false
- for _, change := range changes {
- if change.name() == c.fileName {
- foundChangeForFile = true
- 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
}
@@ -95,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()
diff --git a/repository.go b/repository.go
index 1f64b9f..de92d64 100644
--- a/repository.go
+++ b/repository.go
@@ -1027,8 +1027,36 @@ func (r *Repository) PushContext(ctx context.Context, o *PushOptions) error {
// 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 {
+ fn := commitIterFunc(o.Order)
+ if fn == nil {
+ return nil, fmt.Errorf("invalid Order=%v", o.Order)
+ }
+
+ var (
+ it object.CommitIter
+ err error
+ )
+ if o.All {
+ it, err = r.logAll(fn)
+ } else {
+ it, err = r.log(o.From, fn)
+ }
+
+ if err != nil {
+ return nil, err
+ }
+
+ if o.FileName != nil {
+ // for `git log --all` also check parent (if the next commit comes from the real parent)
+ it = r.logWithFile(*o.FileName, it, o.All)
+ }
+
+ return it, nil
+}
+
+func (r *Repository) log(from plumbing.Hash, commitIterFunc func(*object.Commit) object.CommitIter) (object.CommitIter, error) {
+ h := from
+ if from == plumbing.ZeroHash {
head, err := r.Head()
if err != nil {
return nil, err
@@ -1041,27 +1069,41 @@ func (r *Repository) Log(o *LogOptions) (object.CommitIter, error) {
if err != nil {
return nil, err
}
+ return commitIterFunc(commit), nil
+}
- var commitIter object.CommitIter
- switch o.Order {
+func (r *Repository) logAll(commitIterFunc func(*object.Commit) object.CommitIter) (object.CommitIter, error) {
+ return object.NewCommitAllIter(r.Storer, commitIterFunc)
+}
+
+func (*Repository) logWithFile(fileName string, commitIter object.CommitIter, checkParent bool) object.CommitIter {
+ return object.NewCommitFileIterFromIter(fileName, commitIter, checkParent)
+}
+
+func commitIterFunc(order LogOrder) func(c *object.Commit) object.CommitIter {
+ switch order {
case LogOrderDefault:
- commitIter = object.NewCommitPreorderIter(commit, nil, nil)
+ return func(c *object.Commit) object.CommitIter {
+ return object.NewCommitPreorderIter(c, nil, nil)
+ }
case LogOrderDFS:
- commitIter = object.NewCommitPreorderIter(commit, nil, nil)
+ return func(c *object.Commit) object.CommitIter {
+ return object.NewCommitPreorderIter(c, nil, nil)
+ }
case LogOrderDFSPost:
- commitIter = object.NewCommitPostorderIter(commit, nil)
+ return func(c *object.Commit) object.CommitIter {
+ return object.NewCommitPostorderIter(c, nil)
+ }
case LogOrderBSF:
- commitIter = object.NewCommitIterBSF(commit, nil, nil)
+ return func(c *object.Commit) object.CommitIter {
+ return object.NewCommitIterBSF(c, nil, nil)
+ }
case LogOrderCommitterTime:
- commitIter = object.NewCommitIterCTime(commit, nil, nil)
- default:
- return nil, fmt.Errorf("invalid Order=%v", o.Order)
- }
-
- if o.FileName == nil {
- return commitIter, nil
+ return func(c *object.Commit) object.CommitIter {
+ return object.NewCommitIterCTime(c, nil, nil)
+ }
}
- return object.NewCommitFileIterFromIter(*o.FileName, commitIter), nil
+ return nil
}
// Tags returns all the tag References in a repository.
diff --git a/repository_test.go b/repository_test.go
index 70e344e..2a56dd2 100644
--- a/repository_test.go
+++ b/repository_test.go
@@ -1251,6 +1251,77 @@ func (s *RepositorySuite) TestLog(c *C) {
c.Assert(err, Equals, io.EOF)
}
+func (s *RepositorySuite) TestLogAll(c *C) {
+ r, _ := Init(memory.NewStorage(), nil)
+ err := r.clone(context.Background(), &CloneOptions{
+ URL: s.GetBasicLocalRepositoryURL(),
+ })
+
+ c.Assert(err, IsNil)
+
+ cIter, err := r.Log(&LogOptions{
+ All: true,
+ })
+ c.Assert(err, IsNil)
+
+ commitOrder := []plumbing.Hash{
+ plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"),
+ plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881"),
+ 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)
+ cIter.Close()
+}
+
+func (s *RepositorySuite) TestLogAllOrderByTime(c *C) {
+ r, _ := Init(memory.NewStorage(), nil)
+ err := r.clone(context.Background(), &CloneOptions{
+ URL: s.GetBasicLocalRepositoryURL(),
+ })
+
+ c.Assert(err, IsNil)
+
+ cIter, err := r.Log(&LogOptions{
+ Order: LogOrderCommitterTime,
+ All: true,
+ })
+ c.Assert(err, IsNil)
+
+ commitOrder := []plumbing.Hash{
+ plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"),
+ plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881"),
+ plumbing.NewHash("918c48b83bd081e863dbe1b80f8998f058cd8294"),
+ plumbing.NewHash("af2d6a6954d532f8ffb47615169c8fdf9d383a1a"),
+ plumbing.NewHash("1669dce138d9b841a518c64b10914d88f5e488ea"),
+ plumbing.NewHash("a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69"),
+ plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"),
+ 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)
+ cIter.Close()
+}
+
func (s *RepositorySuite) TestLogHead(c *C) {
r, _ := Init(memory.NewStorage(), nil)
err := r.clone(context.Background(), &CloneOptions{
@@ -1333,8 +1404,8 @@ func (s *RepositorySuite) TestLogFileForEach(c *C) {
fileName := "php/crappy.php"
cIter, err := r.Log(&LogOptions{FileName: &fileName})
-
c.Assert(err, IsNil)
+ defer cIter.Close()
commitOrder := []plumbing.Hash{
plumbing.NewHash("918c48b83bd081e863dbe1b80f8998f058cd8294"),
@@ -1344,7 +1415,51 @@ func (s *RepositorySuite) TestLogFileForEach(c *C) {
cIter.ForEach(func(commit *object.Commit) error {
expectedCommitHash := commitOrder[expectedIndex]
c.Assert(commit.Hash.String(), Equals, expectedCommitHash.String())
- expectedIndex += 1
+ expectedIndex++
+ return nil
+ })
+ c.Assert(expectedIndex, Equals, 1)
+}
+
+func (s *RepositorySuite) TestLogNonHeadFile(c *C) {
+ r, _ := Init(memory.NewStorage(), nil)
+ err := r.clone(context.Background(), &CloneOptions{
+ URL: s.GetBasicLocalRepositoryURL(),
+ })
+
+ c.Assert(err, IsNil)
+
+ fileName := "README"
+ cIter, err := r.Log(&LogOptions{FileName: &fileName})
+ c.Assert(err, IsNil)
+ defer cIter.Close()
+
+ _, err = cIter.Next()
+ c.Assert(err, Equals, io.EOF)
+}
+
+func (s *RepositorySuite) TestLogAllFileForEach(c *C) {
+ r, _ := Init(memory.NewStorage(), nil)
+ err := r.clone(context.Background(), &CloneOptions{
+ URL: s.GetBasicLocalRepositoryURL(),
+ })
+
+ c.Assert(err, IsNil)
+
+ fileName := "README"
+ cIter, err := r.Log(&LogOptions{FileName: &fileName, All: true})
+ c.Assert(err, IsNil)
+ defer cIter.Close()
+
+ commitOrder := []plumbing.Hash{
+ plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881"),
+ }
+
+ expectedIndex := 0
+ cIter.ForEach(func(commit *object.Commit) error {
+ expectedCommitHash := commitOrder[expectedIndex]
+ c.Assert(commit.Hash.String(), Equals, expectedCommitHash.String())
+ expectedIndex++
return nil
})
c.Assert(expectedIndex, Equals, 1)
@@ -1362,6 +1477,7 @@ func (s *RepositorySuite) TestLogInvalidFile(c *C) {
cIter, err := r.Log(&LogOptions{FileName: &fileName})
// Not raising an error since `git log -- vendor/foo12.go` responds silently
c.Assert(err, IsNil)
+ defer cIter.Close()
_, err = cIter.Next()
c.Assert(err, Equals, io.EOF)
@@ -1379,8 +1495,8 @@ func (s *RepositorySuite) TestLogFileInitialCommit(c *C) {
Order: LogOrderCommitterTime,
FileName: &fileName,
})
-
c.Assert(err, IsNil)
+ defer cIter.Close()
commitOrder := []plumbing.Hash{
plumbing.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d"),
@@ -1390,7 +1506,7 @@ func (s *RepositorySuite) TestLogFileInitialCommit(c *C) {
cIter.ForEach(func(commit *object.Commit) error {
expectedCommitHash := commitOrder[expectedIndex]
c.Assert(commit.Hash.String(), Equals, expectedCommitHash.String())
- expectedIndex += 1
+ expectedIndex++
return nil
})
c.Assert(expectedIndex, Equals, 1)
@@ -1410,6 +1526,8 @@ func (s *RepositorySuite) TestLogFileWithOtherParamsFail(c *C) {
From: plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"),
})
c.Assert(err, IsNil)
+ defer cIter.Close()
+
_, iterErr := cIter.Next()
c.Assert(iterErr, Equals, io.EOF)
}
@@ -2343,8 +2461,6 @@ func executeOnPath(path, cmd string) error {
c.Stderr = buf
c.Stdout = buf
- //defer func() { fmt.Println(buf.String()) }()
-
return c.Run()
}