Skip to content

Commit

Permalink
plumbing: object, Add support for Log with filenames. Fixes src-d#826
Browse files Browse the repository at this point in the history
Signed-off-by: Nithin Gangadharan <nithin.linkin@gmail.com>
  • Loading branch information
gnithin committed Oct 8, 2018
1 parent 1fdd36c commit b2b4209
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 6 deletions.
2 changes: 2 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,8 @@ type LogOptions struct {
// set Order=LogOrderCommitterTime for ordering by committer time (more compatible with `git log`)
// set Order=LogOrderBSF for Breadth-first search
Order LogOrder

FileName *string
}

var (
Expand Down
114 changes: 114 additions & 0 deletions plumbing/object/commit_walker_file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package object

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

type commitFileIter struct {
fileName string
sourceIter CommitIter
currentCommit *Commit
}

// 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 {
iterator := new(commitFileIter)
iterator.sourceIter = commitIter
iterator.fileName = fileName
return iterator
}

func (c *commitFileIter) Next() (*Commit, error) {
var err error
if c.currentCommit == nil {
c.currentCommit, err = c.sourceIter.Next()
if err != nil {
return nil, err
}
}

for {
// Parent-commit can be nil if the current-commit is the initial commit
parentCommit, parentCommitErr := c.sourceIter.Next()
if parentCommitErr != nil {
if parentCommitErr != io.EOF {
err = parentCommitErr
break
}
parentCommit = nil
}

// Fetch the trees of the current and parent commits
currentTree, currTreeErr := c.currentCommit.Tree()
if currTreeErr != nil {
err = currTreeErr
break
}

var parentTree *Tree
if parentCommit != nil {
var parentTreeErr error
parentTree, parentTreeErr = parentCommit.Tree()
if parentTreeErr != nil {
err = parentTreeErr
break
}
}

// Find diff between current and parent trees
changes, diffErr := DiffTree(currentTree, parentTree)
if diffErr != nil {
err = diffErr
break
}

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

// 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 {
return prevCommit, nil
}

// If there are no more commits to be found, then return with EOF
if parentCommit == nil {
err = io.EOF
break
}
}

// Setting current-commit to nil to prevent unwanted states when errors are raised
c.currentCommit = nil
return nil, err
}

func (c *commitFileIter) ForEach(cb func(*Commit) error) error {
for {
commit, nextErr := c.Next()
if nextErr != nil {
return nextErr
}
err := cb(commit)
if err == storer.ErrStop {
return nil
} else if err != nil {
return err
}
}
}

func (c *commitFileIter) Close() {
c.sourceIter.Close()
}
19 changes: 13 additions & 6 deletions repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -965,19 +965,26 @@ func (r *Repository) Log(o *LogOptions) (object.CommitIter, error) {
return nil, err
}

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

if o.FileName == nil {
return commitIter, nil
}
return nil, fmt.Errorf("invalid Order=%v", o.Order)
return object.NewCommitFileIterFromIter(*o.FileName, commitIter), nil
}

// Tags returns all the tag References in a repository.
Expand Down
139 changes: 139 additions & 0 deletions repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1143,6 +1143,145 @@ func (s *RepositorySuite) TestLogError(c *C) {
c.Assert(err, NotNil)
}

func (s *RepositorySuite) TestLogFileNext(c *C) {
r, _ := Init(memory.NewStorage(), nil)
err := r.clone(context.Background(), &CloneOptions{
URL: s.GetBasicLocalRepositoryURL(),
})

c.Assert(err, IsNil)

fileName := "vendor/foo.go"
cIter, err := r.Log(&LogOptions{FileName: &fileName})

c.Assert(err, IsNil)

commitOrder := []plumbing.Hash{
plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"),
}

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)
}

func (s *RepositorySuite) TestLogFileForEach(c *C) {
r, _ := Init(memory.NewStorage(), nil)
err := r.clone(context.Background(), &CloneOptions{
URL: s.GetBasicLocalRepositoryURL(),
})

c.Assert(err, IsNil)

fileName := "php/crappy.php"
cIter, err := r.Log(&LogOptions{FileName: &fileName})

c.Assert(err, IsNil)

commitOrder := []plumbing.Hash{
plumbing.NewHash("918c48b83bd081e863dbe1b80f8998f058cd8294"),
}

expectedIndex := 0
cIter.ForEach(func(commit *object.Commit) error {
expectedCommitHash := commitOrder[expectedIndex]
c.Assert(commit.Hash.String(), Equals, expectedCommitHash.String())
expectedIndex += 1
return nil
})
c.Assert(expectedIndex, Equals, 1)
}

func (s *RepositorySuite) TestLogInvalidFile(c *C) {
r, _ := Init(memory.NewStorage(), nil)
err := r.clone(context.Background(), &CloneOptions{
URL: s.GetBasicLocalRepositoryURL(),
})
c.Assert(err, IsNil)

// Throwing in a file that does not exist
fileName := "vendor/foo12.go"
cIter, err := r.Log(&LogOptions{FileName: &fileName})
// Not raising an error since `git log -- vendor/foo12.go` responds silently
c.Assert(err, IsNil)

_, err = cIter.Next()
c.Assert(err, Equals, io.EOF)
}

func (s *RepositorySuite) TestLogFileInitialCommit(c *C) {
r, _ := Init(memory.NewStorage(), nil)
err := r.clone(context.Background(), &CloneOptions{
URL: s.GetBasicLocalRepositoryURL(),
})
c.Assert(err, IsNil)

fileName := "LICENSE"
cIter, err := r.Log(&LogOptions{
Order: LogOrderCommitterTime,
FileName: &fileName,
})

c.Assert(err, IsNil)

commitOrder := []plumbing.Hash{
plumbing.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d"),
}

expectedIndex := 0
cIter.ForEach(func(commit *object.Commit) error {
expectedCommitHash := commitOrder[expectedIndex]
c.Assert(commit.Hash.String(), Equals, expectedCommitHash.String())
expectedIndex += 1
return nil
})
c.Assert(expectedIndex, Equals, 1)
}

func (s *RepositorySuite) TestLogFileWithOtherParamsFail(c *C) {
r, _ := Init(memory.NewStorage(), nil)
err := r.clone(context.Background(), &CloneOptions{
URL: s.GetBasicLocalRepositoryURL(),
})
c.Assert(err, IsNil)

fileName := "vendor/foo.go"
cIter, err := r.Log(&LogOptions{
Order: LogOrderCommitterTime,
FileName: &fileName,
From: plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"),
})
c.Assert(err, IsNil)
_, iterErr := cIter.Next()
c.Assert(iterErr, Equals, io.EOF)
}

func (s *RepositorySuite) TestLogFileWithOtherParamsPass(c *C) {
r, _ := Init(memory.NewStorage(), nil)
err := r.clone(context.Background(), &CloneOptions{
URL: s.GetBasicLocalRepositoryURL(),
})
c.Assert(err, IsNil)

fileName := "LICENSE"
cIter, err := r.Log(&LogOptions{
Order: LogOrderCommitterTime,
FileName: &fileName,
From: plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"),
})
c.Assert(err, IsNil)
commitVal, iterErr := cIter.Next()
c.Assert(iterErr, Equals, nil)
c.Assert(commitVal.Hash.String(), Equals, "b029517f6300c2da0f4b651b8642506cd6aaf45d")

_, iterErr = cIter.Next()
c.Assert(iterErr, Equals, io.EOF)
}

func (s *RepositorySuite) TestCommit(c *C) {
r, _ := Init(memory.NewStorage(), nil)
err := r.clone(context.Background(), &CloneOptions{
Expand Down

0 comments on commit b2b4209

Please sign in to comment.