Skip to content

Commit e2d6e8f

Browse files
committed
git: worktree, Add StatusWithOptions
The fix for go-git#119 improves the Worktree.Status() behaviour by preloading all existing files and setting their status to unmodified. Which makes it more reliable when doing per file status verification, however breaks backwards compatibility in two ways: - Increased execution time and space: the preloading can be slow in very large repositories and will increase memory usage when representing the state. - Behaviour: the previous behaviour returned a map with a small subset of entries. The new behaviour will include a new entry for every file within the repository. This commit introduces reverts the change in the default behaviour, and introduces StatusWithOptions so that users can opt-in the new option. Signed-off-by: Paulo Gomes <[email protected]>
1 parent 0931346 commit e2d6e8f

File tree

3 files changed

+94
-38
lines changed

3 files changed

+94
-38
lines changed

status.go

Lines changed: 69 additions & 0 deletions
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

Lines changed: 16 additions & 37 deletions
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,45 +57,11 @@ func (w *Worktree) Status() (Status, error) {
4457
hash = ref.Hash()
4558
}
4659

47-
return w.status(hash)
48-
}
49-
50-
func (w *Worktree) newStatusFromIndex() (Status, error) {
51-
idx, err := w.r.Storer.Index()
52-
if err != nil {
53-
return nil, err
54-
}
55-
56-
idxRoot := mindex.NewRootNode(idx)
57-
nodes := []noder.Noder{idxRoot}
58-
59-
if err != nil {
60-
return nil, err
61-
}
62-
63-
status := make(Status)
64-
65-
for len(nodes) > 0 {
66-
var node noder.Noder
67-
node, nodes = nodes[0], nodes[1:]
68-
if node.IsDir() {
69-
children, err := node.Children()
70-
if err != nil {
71-
return nil, err
72-
}
73-
nodes = append(nodes, children...)
74-
continue
75-
}
76-
fs := status.File(node.Name())
77-
fs.Worktree = Unmodified
78-
fs.Staging = Unmodified
79-
}
80-
81-
return status, nil
60+
return w.status(o.Strategy, hash)
8261
}
8362

84-
func (w *Worktree) status(commit plumbing.Hash) (Status, error) {
85-
s, err := w.newStatusFromIndex()
63+
func (w *Worktree) status(ss StatusStrategy, commit plumbing.Hash) (Status, error) {
64+
s, err := ss.new(w)
8665
if err != nil {
8766
return nil, err
8867
}

worktree_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1062,13 +1062,21 @@ func (s *WorktreeSuite) TestStatusUnmodified(c *C) {
10621062
err := w.Checkout(&CheckoutOptions{Force: true})
10631063
c.Assert(err, IsNil)
10641064

1065-
status, err := w.Status()
1065+
status, err := w.StatusWithOptions(StatusOptions{Strategy: Preload})
10661066
c.Assert(err, IsNil)
10671067
c.Assert(status.IsClean(), Equals, true)
10681068
c.Assert(status.IsUntracked("LICENSE"), Equals, false)
10691069

10701070
c.Assert(status.File("LICENSE").Staging, Equals, Unmodified)
10711071
c.Assert(status.File("LICENSE").Worktree, Equals, Unmodified)
1072+
1073+
status, err = w.StatusWithOptions(StatusOptions{Strategy: Empty})
1074+
c.Assert(err, IsNil)
1075+
c.Assert(status.IsClean(), Equals, true)
1076+
c.Assert(status.IsUntracked("LICENSE"), Equals, false)
1077+
1078+
c.Assert(status.File("LICENSE").Staging, Equals, Untracked)
1079+
c.Assert(status.File("LICENSE").Worktree, Equals, Untracked)
10721080
}
10731081

10741082
func (s *WorktreeSuite) TestReset(c *C) {

0 commit comments

Comments
 (0)