fork of go-git with some jj specific features

Merge pull request #1023 from rodrigocam/master

git: worktree, Fix file reported as `Untracked` while it is committed

authored by Paulo Gomes and committed by GitHub 513d5af7 61f8d68a

+69
status.go
··· 4 4 "bytes" 5 5 "fmt" 6 6 "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" 7 10 ) 8 11 9 12 // Status represents the current status of a Worktree. ··· 77 80 Copied StatusCode = 'C' 78 81 UpdatedButUnmerged StatusCode = 'U' 79 82 ) 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 + }
+19 -3
worktree_status.go
··· 29 29 // ErrGlobNoMatches in an AddGlob if the glob pattern does not match any 30 30 // files in the worktree. 31 31 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") 32 35 ) 33 36 34 37 // Status returns the working tree status. 35 38 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) { 36 49 var hash plumbing.Hash 37 50 38 51 ref, err := w.r.Head() ··· 44 57 hash = ref.Hash() 45 58 } 46 59 47 - return w.status(hash) 60 + return w.status(o.Strategy, hash) 48 61 } 49 62 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 + } 52 68 53 69 left, err := w.diffCommitWithStaging(commit, false) 54 70 if err != nil {
+27
worktree_test.go
··· 1058 1058 c.Assert(status, HasLen, 1) 1059 1059 } 1060 1060 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 + 1061 1088 func (s *WorktreeSuite) TestReset(c *C) { 1062 1089 fs := memfs.New() 1063 1090 w := &Worktree{