Skip to content

Commit 513d5af

Browse files
authored
Merge pull request go-git#1023 from rodrigocam/master
git: worktree, Fix file reported as `Untracked` while it is committed
2 parents 61f8d68 + e2d6e8f commit 513d5af

File tree

3 files changed

+115
-3
lines changed

3 files changed

+115
-3
lines changed

status.go

+69
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import (
44
"bytes"
55
"fmt"
66
"path/filepath"
7+
8+
mindex "github.com/go-git/go-git/v5/utils/merkletrie/index"
9+
"github.com/go-git/go-git/v5/utils/merkletrie/noder"
710
)
811

912
// Status represents the current status of a Worktree.
@@ -77,3 +80,69 @@ const (
7780
Copied StatusCode = 'C'
7881
UpdatedButUnmerged StatusCode = 'U'
7982
)
83+
84+
// StatusStrategy defines the different types of strategies when processing
85+
// the worktree status.
86+
type StatusStrategy int
87+
88+
const (
89+
// TODO: (V6) Review the default status strategy.
90+
// TODO: (V6) Review the type used to represent Status, to enable lazy
91+
// processing of statuses going direct to the backing filesystem.
92+
defaultStatusStrategy = Empty
93+
94+
// Empty starts its status map from empty. Missing entries for a given
95+
// path means that the file is untracked. This causes a known issue (#119)
96+
// whereby unmodified files can be incorrectly reported as untracked.
97+
//
98+
// This can be used when returning the changed state within a modified Worktree.
99+
// For example, to check whether the current worktree is clean.
100+
Empty StatusStrategy = 0
101+
// Preload goes through all existing nodes from the index and add them to the
102+
// status map as unmodified. This is currently the most reliable strategy
103+
// although it comes at a performance cost in large repositories.
104+
//
105+
// This method is recommended when fetching the status of unmodified files.
106+
// For example, to confirm the status of a specific file that is either
107+
// untracked or unmodified.
108+
Preload StatusStrategy = 1
109+
)
110+
111+
func (s StatusStrategy) new(w *Worktree) (Status, error) {
112+
switch s {
113+
case Preload:
114+
return preloadStatus(w)
115+
case Empty:
116+
return make(Status), nil
117+
}
118+
return nil, fmt.Errorf("%w: %+v", ErrUnsupportedStatusStrategy, s)
119+
}
120+
121+
func preloadStatus(w *Worktree) (Status, error) {
122+
idx, err := w.r.Storer.Index()
123+
if err != nil {
124+
return nil, err
125+
}
126+
127+
idxRoot := mindex.NewRootNode(idx)
128+
nodes := []noder.Noder{idxRoot}
129+
130+
status := make(Status)
131+
for len(nodes) > 0 {
132+
var node noder.Noder
133+
node, nodes = nodes[0], nodes[1:]
134+
if node.IsDir() {
135+
children, err := node.Children()
136+
if err != nil {
137+
return nil, err
138+
}
139+
nodes = append(nodes, children...)
140+
continue
141+
}
142+
fs := status.File(node.Name())
143+
fs.Worktree = Unmodified
144+
fs.Staging = Unmodified
145+
}
146+
147+
return status, nil
148+
}

worktree_status.go

+19-3
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,23 @@ var (
2929
// ErrGlobNoMatches in an AddGlob if the glob pattern does not match any
3030
// files in the worktree.
3131
ErrGlobNoMatches = errors.New("glob pattern did not match any files")
32+
// ErrUnsupportedStatusStrategy occurs when an invalid StatusStrategy is used
33+
// when processing the Worktree status.
34+
ErrUnsupportedStatusStrategy = errors.New("unsupported status strategy")
3235
)
3336

3437
// Status returns the working tree status.
3538
func (w *Worktree) Status() (Status, error) {
39+
return w.StatusWithOptions(StatusOptions{Strategy: defaultStatusStrategy})
40+
}
41+
42+
// StatusOptions defines the options for Worktree.StatusWithOptions().
43+
type StatusOptions struct {
44+
Strategy StatusStrategy
45+
}
46+
47+
// StatusWithOptions returns the working tree status.
48+
func (w *Worktree) StatusWithOptions(o StatusOptions) (Status, error) {
3649
var hash plumbing.Hash
3750

3851
ref, err := w.r.Head()
@@ -44,11 +57,14 @@ func (w *Worktree) Status() (Status, error) {
4457
hash = ref.Hash()
4558
}
4659

47-
return w.status(hash)
60+
return w.status(o.Strategy, hash)
4861
}
4962

50-
func (w *Worktree) status(commit plumbing.Hash) (Status, error) {
51-
s := make(Status)
63+
func (w *Worktree) status(ss StatusStrategy, commit plumbing.Hash) (Status, error) {
64+
s, err := ss.new(w)
65+
if err != nil {
66+
return nil, err
67+
}
5268

5369
left, err := w.diffCommitWithStaging(commit, false)
5470
if err != nil {

worktree_test.go

+27
Original file line numberDiff line numberDiff line change
@@ -1058,6 +1058,33 @@ func (s *WorktreeSuite) TestStatusEmptyDirty(c *C) {
10581058
c.Assert(status, HasLen, 1)
10591059
}
10601060

1061+
func (s *WorktreeSuite) TestStatusUnmodified(c *C) {
1062+
fs := memfs.New()
1063+
w := &Worktree{
1064+
r: s.Repository,
1065+
Filesystem: fs,
1066+
}
1067+
1068+
err := w.Checkout(&CheckoutOptions{Force: true})
1069+
c.Assert(err, IsNil)
1070+
1071+
status, err := w.StatusWithOptions(StatusOptions{Strategy: Preload})
1072+
c.Assert(err, IsNil)
1073+
c.Assert(status.IsClean(), Equals, true)
1074+
c.Assert(status.IsUntracked("LICENSE"), Equals, false)
1075+
1076+
c.Assert(status.File("LICENSE").Staging, Equals, Unmodified)
1077+
c.Assert(status.File("LICENSE").Worktree, Equals, Unmodified)
1078+
1079+
status, err = w.StatusWithOptions(StatusOptions{Strategy: Empty})
1080+
c.Assert(err, IsNil)
1081+
c.Assert(status.IsClean(), Equals, true)
1082+
c.Assert(status.IsUntracked("LICENSE"), Equals, false)
1083+
1084+
c.Assert(status.File("LICENSE").Staging, Equals, Untracked)
1085+
c.Assert(status.File("LICENSE").Worktree, Equals, Untracked)
1086+
}
1087+
10611088
func (s *WorktreeSuite) TestReset(c *C) {
10621089
fs := memfs.New()
10631090
w := &Worktree{

0 commit comments

Comments
 (0)