aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaulo Gomes <pjbgf@linux.com>2024-08-03 10:36:33 +0100
committerGitHub <noreply@github.com>2024-08-03 10:36:33 +0100
commit513d5af79345df6edb22f37e4b233dbaf5f7c399 (patch)
tree79e6b7ee9d99c0f33bf86b97e24a5a02f4b8c050
parent61f8d68aae95647f21a7d0983a428363acac8782 (diff)
parente2d6e8f264604b1b71dc2c958f7bdd44a90d029b (diff)
downloadgo-git-513d5af79345df6edb22f37e4b233dbaf5f7c399.tar.gz
Merge pull request #1023 from rodrigocam/master
git: worktree, Fix file reported as `Untracked` while it is committed
-rw-r--r--status.go69
-rw-r--r--worktree_status.go22
-rw-r--r--worktree_test.go27
3 files changed, 115 insertions, 3 deletions
diff --git a/status.go b/status.go
index 7f18e02..d14f7e6 100644
--- a/status.go
+++ b/status.go
@@ -4,6 +4,9 @@ import (
"bytes"
"fmt"
"path/filepath"
+
+ mindex "github.com/go-git/go-git/v5/utils/merkletrie/index"
+ "github.com/go-git/go-git/v5/utils/merkletrie/noder"
)
// Status represents the current status of a Worktree.
@@ -77,3 +80,69 @@ const (
Copied StatusCode = 'C'
UpdatedButUnmerged StatusCode = 'U'
)
+
+// StatusStrategy defines the different types of strategies when processing
+// the worktree status.
+type StatusStrategy int
+
+const (
+ // TODO: (V6) Review the default status strategy.
+ // TODO: (V6) Review the type used to represent Status, to enable lazy
+ // processing of statuses going direct to the backing filesystem.
+ defaultStatusStrategy = Empty
+
+ // Empty starts its status map from empty. Missing entries for a given
+ // path means that the file is untracked. This causes a known issue (#119)
+ // whereby unmodified files can be incorrectly reported as untracked.
+ //
+ // This can be used when returning the changed state within a modified Worktree.
+ // For example, to check whether the current worktree is clean.
+ Empty StatusStrategy = 0
+ // Preload goes through all existing nodes from the index and add them to the
+ // status map as unmodified. This is currently the most reliable strategy
+ // although it comes at a performance cost in large repositories.
+ //
+ // This method is recommended when fetching the status of unmodified files.
+ // For example, to confirm the status of a specific file that is either
+ // untracked or unmodified.
+ Preload StatusStrategy = 1
+)
+
+func (s StatusStrategy) new(w *Worktree) (Status, error) {
+ switch s {
+ case Preload:
+ return preloadStatus(w)
+ case Empty:
+ return make(Status), nil
+ }
+ return nil, fmt.Errorf("%w: %+v", ErrUnsupportedStatusStrategy, s)
+}
+
+func preloadStatus(w *Worktree) (Status, error) {
+ idx, err := w.r.Storer.Index()
+ if err != nil {
+ return nil, err
+ }
+
+ idxRoot := mindex.NewRootNode(idx)
+ nodes := []noder.Noder{idxRoot}
+
+ status := make(Status)
+ for len(nodes) > 0 {
+ var node noder.Noder
+ node, nodes = nodes[0], nodes[1:]
+ if node.IsDir() {
+ children, err := node.Children()
+ if err != nil {
+ return nil, err
+ }
+ nodes = append(nodes, children...)
+ continue
+ }
+ fs := status.File(node.Name())
+ fs.Worktree = Unmodified
+ fs.Staging = Unmodified
+ }
+
+ return status, nil
+}
diff --git a/worktree_status.go b/worktree_status.go
index dd9b243..6a0049c 100644
--- a/worktree_status.go
+++ b/worktree_status.go
@@ -29,10 +29,23 @@ var (
// ErrGlobNoMatches in an AddGlob if the glob pattern does not match any
// files in the worktree.
ErrGlobNoMatches = errors.New("glob pattern did not match any files")
+ // ErrUnsupportedStatusStrategy occurs when an invalid StatusStrategy is used
+ // when processing the Worktree status.
+ ErrUnsupportedStatusStrategy = errors.New("unsupported status strategy")
)
// Status returns the working tree status.
func (w *Worktree) Status() (Status, error) {
+ return w.StatusWithOptions(StatusOptions{Strategy: defaultStatusStrategy})
+}
+
+// StatusOptions defines the options for Worktree.StatusWithOptions().
+type StatusOptions struct {
+ Strategy StatusStrategy
+}
+
+// StatusWithOptions returns the working tree status.
+func (w *Worktree) StatusWithOptions(o StatusOptions) (Status, error) {
var hash plumbing.Hash
ref, err := w.r.Head()
@@ -44,11 +57,14 @@ func (w *Worktree) Status() (Status, error) {
hash = ref.Hash()
}
- return w.status(hash)
+ return w.status(o.Strategy, hash)
}
-func (w *Worktree) status(commit plumbing.Hash) (Status, error) {
- s := make(Status)
+func (w *Worktree) status(ss StatusStrategy, commit plumbing.Hash) (Status, error) {
+ s, err := ss.new(w)
+ if err != nil {
+ return nil, err
+ }
left, err := w.diffCommitWithStaging(commit, false)
if err != nil {
diff --git a/worktree_test.go b/worktree_test.go
index 3e151f6..636ccbe 100644
--- a/worktree_test.go
+++ b/worktree_test.go
@@ -1058,6 +1058,33 @@ func (s *WorktreeSuite) TestStatusEmptyDirty(c *C) {
c.Assert(status, HasLen, 1)
}
+func (s *WorktreeSuite) TestStatusUnmodified(c *C) {
+ fs := memfs.New()
+ w := &Worktree{
+ r: s.Repository,
+ Filesystem: fs,
+ }
+
+ err := w.Checkout(&CheckoutOptions{Force: true})
+ c.Assert(err, IsNil)
+
+ status, err := w.StatusWithOptions(StatusOptions{Strategy: Preload})
+ c.Assert(err, IsNil)
+ c.Assert(status.IsClean(), Equals, true)
+ c.Assert(status.IsUntracked("LICENSE"), Equals, false)
+
+ c.Assert(status.File("LICENSE").Staging, Equals, Unmodified)
+ c.Assert(status.File("LICENSE").Worktree, Equals, Unmodified)
+
+ status, err = w.StatusWithOptions(StatusOptions{Strategy: Empty})
+ c.Assert(err, IsNil)
+ c.Assert(status.IsClean(), Equals, true)
+ c.Assert(status.IsUntracked("LICENSE"), Equals, false)
+
+ c.Assert(status.File("LICENSE").Staging, Equals, Untracked)
+ c.Assert(status.File("LICENSE").Worktree, Equals, Untracked)
+}
+
func (s *WorktreeSuite) TestReset(c *C) {
fs := memfs.New()
w := &Worktree{