diff options
-rw-r--r-- | options.go | 5 | ||||
-rw-r--r-- | worktree_commit_test.go | 112 | ||||
-rw-r--r-- | worktree_status.go | 27 | ||||
-rw-r--r-- | worktree_test.go | 160 |
4 files changed, 294 insertions, 10 deletions
@@ -475,6 +475,11 @@ type AddOptions struct { // Glob adds all paths, matching pattern, to the index. If pattern matches a // directory path, all directory contents are added to the index recursively. Glob string + // SkipStatus adds the path with no status check. This option is relevant only + // when the `Path` option is specified and does not apply when the `All` option is used. + // Notice that when passing an ignored path it will be added anyway. + // When true it can speed up adding files to the worktree in very large repositories. + SkipStatus bool } // Validate validates the fields and sets the default values. diff --git a/worktree_commit_test.go b/worktree_commit_test.go index cc3c9a9..a3103b7 100644 --- a/worktree_commit_test.go +++ b/worktree_commit_test.go @@ -143,6 +143,118 @@ func (s *WorktreeSuite) TestCommitAmend(c *C) { assertStorageStatus(c, s.Repository, 13, 11, 11, amendedHash) } +func (s *WorktreeSuite) TestAddAndCommitWithSkipStatus(c *C) { + expected := plumbing.NewHash("375a3808ffde7f129cdd3c8c252fd0fe37cfd13b") + + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + Filesystem: fs, + } + + err := w.Checkout(&CheckoutOptions{}) + c.Assert(err, IsNil) + + util.WriteFile(fs, "LICENSE", []byte("foo"), 0644) + util.WriteFile(fs, "foo", []byte("foo"), 0644) + + err = w.AddWithOptions(&AddOptions{ + Path: "foo", + SkipStatus: true, + }) + c.Assert(err, IsNil) + + hash, err := w.Commit("commit foo only\n", &CommitOptions{ + Author: defaultSignature(), + }) + + c.Assert(hash, Equals, expected) + c.Assert(err, IsNil) + + assertStorageStatus(c, s.Repository, 13, 11, 10, expected) +} + +func (s *WorktreeSuite) TestAddAndCommitWithSkipStatusPathNotModified(c *C) { + expected := plumbing.NewHash("375a3808ffde7f129cdd3c8c252fd0fe37cfd13b") + expected2 := plumbing.NewHash("8691273baf8f6ee2cccfc05e910552c04d02d472") + + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + Filesystem: fs, + } + + err := w.Checkout(&CheckoutOptions{}) + c.Assert(err, IsNil) + + util.WriteFile(fs, "foo", []byte("foo"), 0644) + + status, err := w.Status() + c.Assert(err, IsNil) + foo := status.File("foo") + c.Assert(foo.Staging, Equals, Untracked) + c.Assert(foo.Worktree, Equals, Untracked) + + err = w.AddWithOptions(&AddOptions{ + Path: "foo", + SkipStatus: true, + }) + c.Assert(err, IsNil) + + status, err = w.Status() + c.Assert(err, IsNil) + foo = status.File("foo") + c.Assert(foo.Staging, Equals, Added) + c.Assert(foo.Worktree, Equals, Unmodified) + + hash, err := w.Commit("commit foo only\n", &CommitOptions{All: true, + Author: defaultSignature(), + }) + c.Assert(hash, Equals, expected) + c.Assert(err, IsNil) + commit1, err := w.r.CommitObject(hash) + + status, err = w.Status() + c.Assert(err, IsNil) + foo = status.File("foo") + c.Assert(foo.Staging, Equals, Untracked) + c.Assert(foo.Worktree, Equals, Untracked) + + assertStorageStatus(c, s.Repository, 13, 11, 10, expected) + + err = w.AddWithOptions(&AddOptions{ + Path: "foo", + SkipStatus: true, + }) + c.Assert(err, IsNil) + + status, err = w.Status() + c.Assert(err, IsNil) + foo = status.File("foo") + c.Assert(foo.Staging, Equals, Untracked) + c.Assert(foo.Worktree, Equals, Untracked) + + hash, err = w.Commit("commit with no changes\n", &CommitOptions{ + Author: defaultSignature(), + }) + c.Assert(hash, Equals, expected2) + c.Assert(err, IsNil) + commit2, err := w.r.CommitObject(hash) + + status, err = w.Status() + c.Assert(err, IsNil) + foo = status.File("foo") + c.Assert(foo.Staging, Equals, Untracked) + c.Assert(foo.Worktree, Equals, Untracked) + + patch, err := commit2.Patch(commit1) + c.Assert(err, IsNil) + files := patch.FilePatches() + c.Assert(files, IsNil) + + assertStorageStatus(c, s.Repository, 13, 11, 11, expected2) +} + func (s *WorktreeSuite) TestCommitAll(c *C) { expected := plumbing.NewHash("aede6f8c9c1c7ec9ca8d287c64b8ed151276fa28") diff --git a/worktree_status.go b/worktree_status.go index 7301087..dd9b243 100644 --- a/worktree_status.go +++ b/worktree_status.go @@ -271,7 +271,7 @@ func diffTreeIsEquals(a, b noder.Hasher) bool { // no error is returned. When path is a file, the blob.Hash is returned. func (w *Worktree) Add(path string) (plumbing.Hash, error) { // TODO(mcuadros): deprecate in favor of AddWithOption in v6. - return w.doAdd(path, make([]gitignore.Pattern, 0)) + return w.doAdd(path, make([]gitignore.Pattern, 0), false) } func (w *Worktree) doAddDirectory(idx *index.Index, s Status, directory string, ignorePattern []gitignore.Pattern) (added bool, err error) { @@ -321,7 +321,7 @@ func (w *Worktree) AddWithOptions(opts *AddOptions) error { } if opts.All { - _, err := w.doAdd(".", w.Excludes) + _, err := w.doAdd(".", w.Excludes, false) return err } @@ -329,16 +329,11 @@ func (w *Worktree) AddWithOptions(opts *AddOptions) error { return w.AddGlob(opts.Glob) } - _, err := w.Add(opts.Path) + _, err := w.doAdd(opts.Path, make([]gitignore.Pattern, 0), opts.SkipStatus) return err } -func (w *Worktree) doAdd(path string, ignorePattern []gitignore.Pattern) (plumbing.Hash, error) { - s, err := w.Status() - if err != nil { - return plumbing.ZeroHash, err - } - +func (w *Worktree) doAdd(path string, ignorePattern []gitignore.Pattern, skipStatus bool) (plumbing.Hash, error) { idx, err := w.r.Storer.Index() if err != nil { return plumbing.ZeroHash, err @@ -348,6 +343,17 @@ func (w *Worktree) doAdd(path string, ignorePattern []gitignore.Pattern) (plumbi var added bool fi, err := w.Filesystem.Lstat(path) + + // status is required for doAddDirectory + var s Status + var err2 error + if !skipStatus || fi == nil || fi.IsDir() { + s, err2 = w.Status() + if err2 != nil { + return plumbing.ZeroHash, err2 + } + } + if err != nil || !fi.IsDir() { added, h, err = w.doAddFile(idx, s, path, ignorePattern) } else { @@ -421,8 +427,9 @@ func (w *Worktree) AddGlob(pattern string) error { // doAddFile create a new blob from path and update the index, added is true if // the file added is different from the index. +// if s status is nil will skip the status check and update the index anyway func (w *Worktree) doAddFile(idx *index.Index, s Status, path string, ignorePattern []gitignore.Pattern) (added bool, h plumbing.Hash, err error) { - if s.File(path).Worktree == Unmodified { + if s != nil && s.File(path).Worktree == Unmodified { return false, h, nil } if len(ignorePattern) > 0 { diff --git a/worktree_test.go b/worktree_test.go index 5759ec4..2c3c592 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -1930,6 +1930,166 @@ func (s *WorktreeSuite) TestAddGlobErrorNoMatches(c *C) { c.Assert(err, Equals, ErrGlobNoMatches) } +func (s *WorktreeSuite) TestAddSkipStatusAddedPath(c *C) { + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + Filesystem: fs, + } + + err := w.Checkout(&CheckoutOptions{Force: true}) + c.Assert(err, IsNil) + + idx, err := w.r.Storer.Index() + c.Assert(err, IsNil) + c.Assert(idx.Entries, HasLen, 9) + + err = util.WriteFile(w.Filesystem, "file1", []byte("file1"), 0644) + c.Assert(err, IsNil) + + err = w.AddWithOptions(&AddOptions{Path: "file1", SkipStatus: true}) + c.Assert(err, IsNil) + + idx, err = w.r.Storer.Index() + c.Assert(err, IsNil) + c.Assert(idx.Entries, HasLen, 10) + + e, err := idx.Entry("file1") + c.Assert(err, IsNil) + c.Assert(e.Mode, Equals, filemode.Regular) + + status, err := w.Status() + c.Assert(err, IsNil) + c.Assert(status, HasLen, 1) + + file := status.File("file1") + c.Assert(file.Staging, Equals, Added) + c.Assert(file.Worktree, Equals, Unmodified) +} + +func (s *WorktreeSuite) TestAddSkipStatusModifiedPath(c *C) { + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + Filesystem: fs, + } + + err := w.Checkout(&CheckoutOptions{Force: true}) + c.Assert(err, IsNil) + + idx, err := w.r.Storer.Index() + c.Assert(err, IsNil) + c.Assert(idx.Entries, HasLen, 9) + + err = util.WriteFile(w.Filesystem, "LICENSE", []byte("file1"), 0644) + c.Assert(err, IsNil) + + err = w.AddWithOptions(&AddOptions{Path: "LICENSE", SkipStatus: true}) + c.Assert(err, IsNil) + + idx, err = w.r.Storer.Index() + c.Assert(err, IsNil) + c.Assert(idx.Entries, HasLen, 9) + + e, err := idx.Entry("LICENSE") + c.Assert(err, IsNil) + c.Assert(e.Mode, Equals, filemode.Regular) + + status, err := w.Status() + c.Assert(err, IsNil) + c.Assert(status, HasLen, 1) + + file := status.File("LICENSE") + c.Assert(file.Staging, Equals, Modified) + c.Assert(file.Worktree, Equals, Unmodified) +} + +func (s *WorktreeSuite) TestAddSkipStatusNonModifiedPath(c *C) { + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + Filesystem: fs, + } + + err := w.Checkout(&CheckoutOptions{Force: true}) + c.Assert(err, IsNil) + + idx, err := w.r.Storer.Index() + c.Assert(err, IsNil) + c.Assert(idx.Entries, HasLen, 9) + + err = w.AddWithOptions(&AddOptions{Path: "LICENSE", SkipStatus: true}) + c.Assert(err, IsNil) + + idx, err = w.r.Storer.Index() + c.Assert(err, IsNil) + c.Assert(idx.Entries, HasLen, 9) + + e, err := idx.Entry("LICENSE") + c.Assert(err, IsNil) + c.Assert(e.Mode, Equals, filemode.Regular) + + status, err := w.Status() + c.Assert(err, IsNil) + c.Assert(status, HasLen, 0) + + file := status.File("LICENSE") + c.Assert(file.Staging, Equals, Untracked) + c.Assert(file.Worktree, Equals, Untracked) +} + +func (s *WorktreeSuite) TestAddSkipStatusWithIgnoredPath(c *C) { + fs := memfs.New() + w := &Worktree{ + r: s.Repository, + Filesystem: fs, + } + + err := w.Checkout(&CheckoutOptions{Force: true}) + c.Assert(err, IsNil) + + idx, err := w.r.Storer.Index() + c.Assert(err, IsNil) + c.Assert(idx.Entries, HasLen, 9) + + err = util.WriteFile(fs, ".gitignore", []byte("fileToIgnore\n"), 0755) + c.Assert(err, IsNil) + _, err = w.Add(".gitignore") + c.Assert(err, IsNil) + _, err = w.Commit("Added .gitignore", defaultTestCommitOptions) + c.Assert(err, IsNil) + + err = util.WriteFile(fs, "fileToIgnore", []byte("file to ignore"), 0644) + c.Assert(err, IsNil) + + status, err := w.Status() + c.Assert(err, IsNil) + c.Assert(status, HasLen, 0) + + file := status.File("fileToIgnore") + c.Assert(file.Staging, Equals, Untracked) + c.Assert(file.Worktree, Equals, Untracked) + + err = w.AddWithOptions(&AddOptions{Path: "fileToIgnore", SkipStatus: true}) + c.Assert(err, IsNil) + + idx, err = w.r.Storer.Index() + c.Assert(err, IsNil) + c.Assert(idx.Entries, HasLen, 10) + + e, err := idx.Entry("fileToIgnore") + c.Assert(err, IsNil) + c.Assert(e.Mode, Equals, filemode.Regular) + + status, err = w.Status() + c.Assert(err, IsNil) + c.Assert(status, HasLen, 1) + + file = status.File("fileToIgnore") + c.Assert(file.Staging, Equals, Added) + c.Assert(file.Worktree, Equals, Unmodified) +} + func (s *WorktreeSuite) TestRemove(c *C) { fs := memfs.New() w := &Worktree{ |