diff options
-rw-r--r-- | status.go | 69 | ||||
-rw-r--r-- | worktree_status.go | 53 | ||||
-rw-r--r-- | worktree_test.go | 10 |
3 files changed, 94 insertions, 38 deletions
@@ -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 2f865ce..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,45 +57,11 @@ func (w *Worktree) Status() (Status, error) { hash = ref.Hash() } - return w.status(hash) -} - -func (w *Worktree) newStatusFromIndex() (Status, error) { - idx, err := w.r.Storer.Index() - if err != nil { - return nil, err - } - - idxRoot := mindex.NewRootNode(idx) - nodes := []noder.Noder{idxRoot} - - if err != nil { - return nil, err - } - - 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 + return w.status(o.Strategy, hash) } -func (w *Worktree) status(commit plumbing.Hash) (Status, error) { - s, err := w.newStatusFromIndex() +func (w *Worktree) status(ss StatusStrategy, commit plumbing.Hash) (Status, error) { + s, err := ss.new(w) if err != nil { return nil, err } diff --git a/worktree_test.go b/worktree_test.go index deaf5e5..7ecd818 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -1062,13 +1062,21 @@ func (s *WorktreeSuite) TestStatusUnmodified(c *C) { err := w.Checkout(&CheckoutOptions{Force: true}) c.Assert(err, IsNil) - status, err := w.Status() + 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) { |