Skip to content

Commit

Permalink
Implement git log --all
Browse files Browse the repository at this point in the history
Signed-off-by: kuba-- <[email protected]>
  • Loading branch information
kuba-- committed Jan 7, 2019
1 parent 791aea3 commit 3180dff
Show file tree
Hide file tree
Showing 5 changed files with 322 additions and 33 deletions.
5 changes: 5 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
126 changes: 126 additions & 0 deletions plumbing/object/commit_walker.go
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -181,3 +183,127 @@ func (w *commitPostIterator) ForEach(cb func(*Commit) error) error {
}

func (w *commitPostIterator) Close() {}

// commitAllIterator stands for commit iterator for all refs.
type commitAllIterator struct {
// el points to the current commit.
el *list.Element
}

// NewCommitAllIter returns a new commit iterator for all refs.
// s is a repo Storer used to get commits and references.
// fn is a commit iterator function, used to iterate through ref commits in chosen order
func NewCommitAllIter(s storage.Storer, fn func(*Commit) CommitIter) (CommitIter, error) {
l := list.New()
m := make(map[plumbing.Hash]*list.Element)

// ...along with the HEAD
head, err := storer.ResolveReference(s, plumbing.HEAD)
if err != nil {
return nil, err
}
headCommit, err := GetCommit(s, head.Hash())
if err != nil {
return nil, err
}
err = fn(headCommit).ForEach(func(c *Commit) error {
el := l.PushBack(c)
m[c.Hash] = el
return nil
})
if err != nil {
return nil, err
}

refIter, err := s.IterReferences()
if err != nil {
return nil, err
}
defer refIter.Close()
err = refIter.ForEach(func(r *plumbing.Reference) error {
if r.Hash() == head.Hash() {
// we already have the HEAD
return nil
}
c, _ := GetCommit(s, r.Hash())
// if it's not a commit - skip it.
if c == nil {
return nil
}

el, ok := m[c.Hash]
if ok {
return nil
}

var refCommits []*Commit
cit := fn(c)
for c, e := cit.Next(); e == nil; {
el, ok = m[c.Hash]
if ok {
break
}
refCommits = append(refCommits, c)
c, e = cit.Next()
}
cit.Close()

if el == nil {
// push back all commits from this ref.
for _, c := range refCommits {
el = l.PushBack(c)
m[c.Hash] = el
}
} else {
// insert ref's commits into the list
for i := len(refCommits) - 1; i >= 0; i-- {
c := refCommits[i]
el = l.InsertBefore(c, el)
m[c.Hash] = el
}
}
return nil
})
if err != nil {
return nil, err
}

return &commitAllIterator{l.Front()}, nil
}

func (it *commitAllIterator) Next() (*Commit, error) {
if it.el == nil {
return nil, io.EOF
}

c := it.el.Value.(*Commit)
it.el = it.el.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.el = nil
}
25 changes: 22 additions & 3 deletions plumbing/object/commit_walker_file.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
package object

import (
"gopkg.in/src-d/go-git.v4/plumbing/storer"
"io"

"gopkg.in/src-d/go-git.v4/plumbing/storer"
)

type commitFileIter struct {
fileName string
sourceIter CommitIter
currentCommit *Commit
all 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 {
func NewCommitFileIterFromIter(fileName string, commitIter CommitIter, all bool) CommitIter {
iterator := new(commitFileIter)
iterator.sourceIter = commitIter
iterator.fileName = fileName
iterator.all = all
return iterator
}

Expand Down Expand Up @@ -73,8 +76,24 @@ func (c *commitFileIter) getNextFileCommit() (*Commit, error) {

foundChangeForFile := false
for _, change := range changes {
if change.name() == c.fileName {
if change.name() != c.fileName {
continue
}

// filename matches, now check if source iterator contains all commits (from all refs)
if c.all {
// for `git log --all` also check if the next commit comes from the same parent
for _, h := range c.currentCommit.ParentHashes {
if h == parentCommit.Hash {
foundChangeForFile = true
break
}
}
} else {
foundChangeForFile = true
}

if foundChangeForFile {
break
}
}
Expand Down
71 changes: 47 additions & 24 deletions repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -1027,41 +1027,64 @@ 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 {
head, err := r.Head()
if err != nil {
return nil, err
}

h = head.Hash()
}

commit, err := r.CommitObject(h)
if err != nil {
return nil, err
}

var commitIter object.CommitIter
var (
err error
commitIterFunc func(*object.Commit) object.CommitIter
commitIter object.CommitIter
)
switch o.Order {
case LogOrderDefault:
commitIter = object.NewCommitPreorderIter(commit, nil, nil)
commitIterFunc = func(c *object.Commit) object.CommitIter {
return object.NewCommitPreorderIter(c, nil, nil)
}
case LogOrderDFS:
commitIter = object.NewCommitPreorderIter(commit, nil, nil)
commitIterFunc = func(c *object.Commit) object.CommitIter {
return object.NewCommitPreorderIter(c, nil, nil)
}
case LogOrderDFSPost:
commitIter = object.NewCommitPostorderIter(commit, nil)
commitIterFunc = func(c *object.Commit) object.CommitIter {
return object.NewCommitPostorderIter(c, nil)
}
case LogOrderBSF:
commitIter = object.NewCommitIterBSF(commit, nil, nil)
commitIterFunc = func(c *object.Commit) object.CommitIter {
return object.NewCommitIterBSF(c, nil, nil)
}
case LogOrderCommitterTime:
commitIter = object.NewCommitIterCTime(commit, nil, nil)
commitIterFunc = func(c *object.Commit) object.CommitIter {
return object.NewCommitIterCTime(c, nil, nil)
}
default:
return nil, fmt.Errorf("invalid Order=%v", o.Order)
}

if o.FileName == nil {
return commitIter, nil
if o.All {
commitIter, err = object.NewCommitAllIter(r.Storer, commitIterFunc)
if err != nil {
return nil, err
}
} else {
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
}
commitIter = commitIterFunc(commit)
}
return object.NewCommitFileIterFromIter(*o.FileName, commitIter), nil

if o.FileName != nil {
commitIter = object.NewCommitFileIterFromIter(*o.FileName, commitIter, o.All)
}

return commitIter, nil
}

// Tags returns all the tag References in a repository.
Expand Down
Loading

0 comments on commit 3180dff

Please sign in to comment.