aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--go.mod2
-rw-r--r--options.go7
-rw-r--r--plumbing/object/commit_walker_path.go (renamed from plumbing/object/commit_walker_file.go)36
-rw-r--r--plumbing/object/object.go18
-rw-r--r--repository.go19
-rw-r--r--repository_test.go71
-rw-r--r--storage/filesystem/dotgit/dotgit.go9
-rw-r--r--storage/filesystem/dotgit/dotgit_test.go5
8 files changed, 139 insertions, 28 deletions
diff --git a/go.mod b/go.mod
index 6f8b3d2..31907a6 100644
--- a/go.mod
+++ b/go.mod
@@ -27,3 +27,5 @@ require (
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0
gopkg.in/warnings.v0 v0.1.2 // indirect
)
+
+go 1.13
diff --git a/options.go b/options.go
index cc8f9c5..9a03560 100644
--- a/options.go
+++ b/options.go
@@ -343,8 +343,15 @@ type LogOptions struct {
// Show only those commits in which the specified file was inserted/updated.
// It is equivalent to running `git log -- <file-name>`.
+ // this field is kept for compatility, it can be replaced with PathFilter
FileName *string
+ // Filter commits based on the path of files that are updated
+ // takes file path as argument and should return true if the file is desired
+ // It can be used to implement `git log -- <path>`
+ // either <path> is a file path, or directory path, or a regexp of file/directory path
+ PathFilter func(string) bool
+
// 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.
diff --git a/plumbing/object/commit_walker_file.go b/plumbing/object/commit_walker_path.go
index b73e4ce..6a49fd1 100644
--- a/plumbing/object/commit_walker_file.go
+++ b/plumbing/object/commit_walker_path.go
@@ -8,27 +8,39 @@ import (
"gopkg.in/src-d/go-git.v4/plumbing/storer"
)
-type commitFileIter struct {
- fileName string
+type commitPathIter struct {
+ pathFilter func(string) bool
sourceIter CommitIter
currentCommit *Commit
checkParent bool
}
-// NewCommitFileIterFromIter returns a commit iterator which performs diffTree between
+// NewCommitPathIterFromIter 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.
// 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)
+// pathFilter is a function that takes path of file as argument and returns true if we want it
+func NewCommitPathIterFromIter(pathFilter func(string) bool, commitIter CommitIter, checkParent bool) CommitIter {
+ iterator := new(commitPathIter)
iterator.sourceIter = commitIter
- iterator.fileName = fileName
+ iterator.pathFilter = pathFilter
iterator.checkParent = checkParent
return iterator
}
-func (c *commitFileIter) Next() (*Commit, error) {
+// this function is kept for compatibilty, can be replaced with NewCommitPathIterFromIter
+func NewCommitFileIterFromIter(fileName string, commitIter CommitIter, checkParent bool) CommitIter {
+ return NewCommitPathIterFromIter(
+ func(path string) bool {
+ return path == fileName
+ },
+ commitIter,
+ checkParent,
+ )
+}
+
+func (c *commitPathIter) Next() (*Commit, error) {
if c.currentCommit == nil {
var err error
c.currentCommit, err = c.sourceIter.Next()
@@ -45,7 +57,7 @@ func (c *commitFileIter) Next() (*Commit, error) {
return commit, commitErr
}
-func (c *commitFileIter) getNextFileCommit() (*Commit, error) {
+func (c *commitPathIter) getNextFileCommit() (*Commit, error) {
for {
// Parent-commit can be nil if the current-commit is the initial commit
parentCommit, parentCommitErr := c.sourceIter.Next()
@@ -96,9 +108,9 @@ func (c *commitFileIter) getNextFileCommit() (*Commit, error) {
}
}
-func (c *commitFileIter) hasFileChange(changes Changes, parent *Commit) bool {
+func (c *commitPathIter) hasFileChange(changes Changes, parent *Commit) bool {
for _, change := range changes {
- if change.name() != c.fileName {
+ if !c.pathFilter(change.name()) {
continue
}
@@ -125,7 +137,7 @@ func isParentHash(hash plumbing.Hash, commit *Commit) bool {
return false
}
-func (c *commitFileIter) ForEach(cb func(*Commit) error) error {
+func (c *commitPathIter) ForEach(cb func(*Commit) error) error {
for {
commit, nextErr := c.Next()
if nextErr == io.EOF {
@@ -144,6 +156,6 @@ func (c *commitFileIter) ForEach(cb func(*Commit) error) error {
return nil
}
-func (c *commitFileIter) Close() {
+func (c *commitPathIter) Close() {
c.sourceIter.Close()
}
diff --git a/plumbing/object/object.go b/plumbing/object/object.go
index e960e50..c48a18d 100644
--- a/plumbing/object/object.go
+++ b/plumbing/object/object.go
@@ -138,17 +138,19 @@ func (s *Signature) decodeTimeAndTimeZone(b []byte) {
return
}
- // Include a dummy year in this time.Parse() call to avoid a bug in Go:
- // https://github.com/golang/go/issues/19750
- //
- // Parsing the timezone with no other details causes the tl.Location() call
- // below to return time.Local instead of the parsed zone in some cases
- tl, err := time.Parse("2006 -0700", "1970 "+string(b[tzStart:tzStart+timeZoneLength]))
- if err != nil {
+ timezone := string(b[tzStart : tzStart+timeZoneLength])
+ tzhours, err1 := strconv.ParseInt(timezone[0:3], 10, 64)
+ tzmins, err2 := strconv.ParseInt(timezone[3:], 10, 64)
+ if err1 != nil || err2 != nil {
return
}
+ if tzhours < 0 {
+ tzmins *= -1
+ }
+
+ tz := time.FixedZone("", int(tzhours*60*60+tzmins*60))
- s.When = s.When.In(tl.Location())
+ s.When = s.When.In(tz)
}
func (s *Signature) encodeTimeAndTimeZone(w io.Writer) error {
diff --git a/repository.go b/repository.go
index 11269ef..1e3e339 100644
--- a/repository.go
+++ b/repository.go
@@ -1067,6 +1067,9 @@ func (r *Repository) Log(o *LogOptions) (object.CommitIter, error) {
// for `git log --all` also check parent (if the next commit comes from the real parent)
it = r.logWithFile(*o.FileName, it, o.All)
}
+ if o.PathFilter != nil {
+ it = r.logWithPathFilter(o.PathFilter, it, o.All)
+ }
if o.Since != nil || o.Until != nil {
limitOptions := object.LogLimitOptions{Since: o.Since, Until: o.Until}
@@ -1099,7 +1102,21 @@ func (r *Repository) logAll(commitIterFunc func(*object.Commit) object.CommitIte
}
func (*Repository) logWithFile(fileName string, commitIter object.CommitIter, checkParent bool) object.CommitIter {
- return object.NewCommitFileIterFromIter(fileName, commitIter, checkParent)
+ return object.NewCommitPathIterFromIter(
+ func(path string) bool {
+ return path == fileName
+ },
+ commitIter,
+ checkParent,
+ )
+}
+
+func (*Repository) logWithPathFilter(pathFilter func(string) bool, commitIter object.CommitIter, checkParent bool) object.CommitIter {
+ return object.NewCommitPathIterFromIter(
+ pathFilter,
+ commitIter,
+ checkParent,
+ )
}
func (*Repository) logWithLimit(commitIter object.CommitIter, limitOptions object.LogLimitOptions) object.CommitIter {
diff --git a/repository_test.go b/repository_test.go
index e85311f..06b748a 100644
--- a/repository_test.go
+++ b/repository_test.go
@@ -10,6 +10,7 @@ import (
"os"
"os/exec"
"path/filepath"
+ "regexp"
"strings"
"testing"
"time"
@@ -1676,6 +1677,70 @@ func (s *RepositorySuite) TestLogFileWithError(c *C) {
c.Assert(err, NotNil)
}
+func (s *RepositorySuite) TestLogPathWithError(c *C) {
+ fileName := "README"
+ pathIter := func(path string) bool {
+ return path == fileName
+ }
+ cIter := object.NewCommitPathIterFromIter(pathIter, &mockErrCommitIter{}, false)
+ defer cIter.Close()
+
+ err := cIter.ForEach(func(commit *object.Commit) error {
+ return nil
+ })
+ c.Assert(err, NotNil)
+}
+
+func (s *RepositorySuite) TestLogPathRegexpWithError(c *C) {
+ pathRE := regexp.MustCompile("R.*E")
+ pathIter := func(path string) bool {
+ return pathRE.MatchString(path)
+ }
+ cIter := object.NewCommitPathIterFromIter(pathIter, &mockErrCommitIter{}, false)
+ defer cIter.Close()
+
+ err := cIter.ForEach(func(commit *object.Commit) error {
+ return nil
+ })
+ c.Assert(err, NotNil)
+}
+
+func (s *RepositorySuite) TestLogPathFilterRegexp(c *C) {
+ pathRE := regexp.MustCompile(".*\\.go")
+ pathIter := func(path string) bool {
+ return pathRE.MatchString(path)
+ }
+
+ r, _ := Init(memory.NewStorage(), nil)
+ err := r.clone(context.Background(), &CloneOptions{
+ URL: s.GetBasicLocalRepositoryURL(),
+ })
+ c.Assert(err, IsNil)
+
+ expectedCommitIDs := []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
+ "918c48b83bd081e863dbe1b80f8998f058cd8294",
+ }
+ commitIDs := []string{}
+
+ cIter, err := r.Log(&LogOptions{
+ PathFilter: pathIter,
+ From: plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"),
+ })
+ c.Assert(err, IsNil)
+ defer cIter.Close()
+
+ cIter.ForEach(func(commit *object.Commit) error {
+ commitIDs = append(commitIDs, commit.ID().String())
+ return nil
+ })
+ c.Assert(
+ strings.Join(commitIDs, ", "),
+ Equals,
+ strings.Join(expectedCommitIDs, ", "),
+ )
+}
+
func (s *RepositorySuite) TestLogLimitNext(c *C) {
r, _ := Init(memory.NewStorage(), nil)
err := r.clone(context.Background(), &CloneOptions{
@@ -2615,9 +2680,9 @@ func (s *RepositorySuite) TestResolveRevisionWithErrors(c *C) {
c.Assert(err, IsNil)
datas := map[string]string{
- "efs/heads/master~": "reference not found",
- "HEAD^3": `Revision invalid : "3" found must be 0, 1 or 2 after "^"`,
- "HEAD^{/whatever}": `No commit message match regexp : "whatever"`,
+ "efs/heads/master~": "reference not found",
+ "HEAD^3": `Revision invalid : "3" found must be 0, 1 or 2 after "^"`,
+ "HEAD^{/whatever}": `No commit message match regexp : "whatever"`,
"4e1243bd22c66e76c2ba9eddc1f91394e57f9f83": "reference not found",
}
diff --git a/storage/filesystem/dotgit/dotgit.go b/storage/filesystem/dotgit/dotgit.go
index 111769b..7989e53 100644
--- a/storage/filesystem/dotgit/dotgit.go
+++ b/storage/filesystem/dotgit/dotgit.go
@@ -33,8 +33,9 @@ const (
tmpPackedRefsPrefix = "._packed-refs"
- packExt = ".pack"
- idxExt = ".idx"
+ packPrefix = "pack-"
+ packExt = ".pack"
+ idxExt = ".idx"
)
var (
@@ -224,11 +225,11 @@ func (d *DotGit) objectPacks() ([]plumbing.Hash, error) {
var packs []plumbing.Hash
for _, f := range files {
- if !strings.HasSuffix(f.Name(), packExt) {
+ n := f.Name()
+ if !strings.HasSuffix(n, packExt) || !strings.HasPrefix(n, packPrefix) {
continue
}
- n := f.Name()
h := plumbing.NewHash(n[5 : len(n)-5]) //pack-(hash).pack
if h.IsZero() {
// Ignore files with badly-formatted names.
diff --git a/storage/filesystem/dotgit/dotgit_test.go b/storage/filesystem/dotgit/dotgit_test.go
index 31c6fe0..bd4e9f0 100644
--- a/storage/filesystem/dotgit/dotgit_test.go
+++ b/storage/filesystem/dotgit/dotgit_test.go
@@ -464,6 +464,11 @@ func testObjectPacks(c *C, fs billy.Filesystem, dir *DotGit, f *fixtures.Fixture
c.Assert(err, IsNil)
err = badFile.Close()
c.Assert(err, IsNil)
+ // temporary file generated by git gc
+ tmpFile, err := fs.Create("objects/pack/.tmp-11111-pack-58rf8y4wm1b1k52bpe0kdlx6lpreg6ahso8n3ylc.pack")
+ c.Assert(err, IsNil)
+ err = tmpFile.Close()
+ c.Assert(err, IsNil)
hashes2, err := dir.ObjectPacks()
c.Assert(err, IsNil)