diff options
-rw-r--r-- | go.mod | 2 | ||||
-rw-r--r-- | options.go | 7 | ||||
-rw-r--r-- | plumbing/object/commit_walker_path.go (renamed from plumbing/object/commit_walker_file.go) | 36 | ||||
-rw-r--r-- | plumbing/object/object.go | 18 | ||||
-rw-r--r-- | repository.go | 19 | ||||
-rw-r--r-- | repository_test.go | 71 | ||||
-rw-r--r-- | storage/filesystem/dotgit/dotgit.go | 9 | ||||
-rw-r--r-- | storage/filesystem/dotgit/dotgit_test.go | 5 |
8 files changed, 139 insertions, 28 deletions
@@ -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 @@ -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) |