1package git
2
3import (
4 "bytes"
5 "fmt"
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"
10)
11
12// Status represents the current status of a Worktree.
13// The key of the map is the path of the file.
14type Status map[string]*FileStatus
15
16// File returns the FileStatus for a given path, if the FileStatus doesn't
17// exists a new FileStatus is added to the map using the path as key.
18func (s Status) File(path string) *FileStatus {
19 if _, ok := (s)[path]; !ok {
20 s[path] = &FileStatus{Worktree: Untracked, Staging: Untracked}
21 }
22
23 return s[path]
24}
25
26// IsUntracked checks if file for given path is 'Untracked'
27func (s Status) IsUntracked(path string) bool {
28 stat, ok := (s)[filepath.ToSlash(path)]
29 return ok && stat.Worktree == Untracked
30}
31
32// IsClean returns true if all the files are in Unmodified status.
33func (s Status) IsClean() bool {
34 for _, status := range s {
35 if status.Worktree != Unmodified || status.Staging != Unmodified {
36 return false
37 }
38 }
39
40 return true
41}
42
43func (s Status) String() string {
44 buf := bytes.NewBuffer(nil)
45 for path, status := range s {
46 if status.Staging == Unmodified && status.Worktree == Unmodified {
47 continue
48 }
49
50 if status.Staging == Renamed {
51 path = fmt.Sprintf("%s -> %s", path, status.Extra)
52 }
53
54 fmt.Fprintf(buf, "%c%c %s\n", status.Staging, status.Worktree, path)
55 }
56
57 return buf.String()
58}
59
60// FileStatus contains the status of a file in the worktree
61type FileStatus struct {
62 // Staging is the status of a file in the staging area
63 Staging StatusCode
64 // Worktree is the status of a file in the worktree
65 Worktree StatusCode
66 // Extra contains extra information, such as the previous name in a rename
67 Extra string
68}
69
70// StatusCode status code of a file in the Worktree
71type StatusCode byte
72
73const (
74 Unmodified StatusCode = ' '
75 Untracked StatusCode = '?'
76 Modified StatusCode = 'M'
77 Added StatusCode = 'A'
78 Deleted StatusCode = 'D'
79 Renamed StatusCode = 'R'
80 Copied StatusCode = 'C'
81 UpdatedButUnmerged StatusCode = 'U'
82)
83
84// StatusStrategy defines the different types of strategies when processing
85// the worktree status.
86type StatusStrategy int
87
88const (
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
111func (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
121func 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}