aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaulo Gomes <pjbgf@linux.com>2024-01-23 08:51:37 +0000
committerGitHub <noreply@github.com>2024-01-23 08:51:37 +0000
commit9a6715aba7a88defd27aeba9ddb24cce9a9416c1 (patch)
treed329738b9852c088e87f3986cefef34bb4a58111
parent40074d6f7331cc8be5f65e916c01802843d8463b (diff)
parentd5f1dd61fb79389218a9fe3b5574acf8409737b1 (diff)
downloadgo-git-9a6715aba7a88defd27aeba9ddb24cce9a9416c1.tar.gz
Merge pull request #994 from moranCohen26/supportFastAddPath
Worktree.AddWithOptions: add skipStatus option when providing a specific path
-rw-r--r--options.go5
-rw-r--r--worktree_commit_test.go112
-rw-r--r--worktree_status.go27
-rw-r--r--worktree_test.go160
4 files changed, 294 insertions, 10 deletions
diff --git a/options.go b/options.go
index ddd637b..0a6eb94 100644
--- a/options.go
+++ b/options.go
@@ -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{