Merge pull request #1045 from kuba--/enh-1024/log-all

Implement git log --all
diff --git a/options.go b/options.go
index 5d10a88..ed7689a 100644
--- a/options.go
+++ b/options.go
@@ -335,6 +335,11 @@
 	// 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) 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 @@
 			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) 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 @@
 
 // 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 @@
 	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)
+		return func(c *object.Commit) object.CommitIter {
+			return object.NewCommitIterCTime(c, nil, nil)
+		}
 	}
-
-	if o.FileName == nil {
-		return commitIter, 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 @@
 	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 @@
 
 	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 @@
 	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 @@
 	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 @@
 		Order:    LogOrderCommitterTime,
 		FileName: &fileName,
 	})
-
 	c.Assert(err, IsNil)
+	defer cIter.Close()
 
 	commitOrder := []plumbing.Hash{
 		plumbing.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d"),
@@ -1390,7 +1506,7 @@
 	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 @@
 		From:     plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"),
 	})
 	c.Assert(err, IsNil)
+	defer cIter.Close()
+
 	_, iterErr := cIter.Next()
 	c.Assert(iterErr, Equals, io.EOF)
 }
@@ -2343,8 +2461,6 @@
 	c.Stderr = buf
 	c.Stdout = buf
 
-	//defer func() { fmt.Println(buf.String()) }()
-
 	return c.Run()
 }