@@ -65,26 +65,102 @@ func scanUnpushed(cb GitScannerFoundPointer, remote string) error {
65
65
}
66
66
67
67
func scanStashed (cb GitScannerFoundPointer , s * GitScanner ) error {
68
- // First get the SHAs of all stashes
69
- logArgs := []string {"-g" , "--format=%H" , "refs/stash" , "--" }
68
+
69
+ // Stashes are actually 2-3 commits, each containing one of:
70
+ // 1. Working copy modified files
71
+ // 2. Index changes
72
+ // 3. Untracked files, if -u was used
73
+ // We need to get the diff of all 3 of these commits to ensure we have all
74
+ // of the LFS objects necessary to pop the stash
75
+
76
+ // First get the list of stashes
77
+ // In recent version of git you can get parents directly from
78
+ // this command, avoiding the intermediate "git show"
79
+ // However older gits (at least <=2.7) don't report merge parents in the reflog
80
+ // So we need to do it in 2 stages
81
+ logArgs := []string {"-g" , "--format=%h" , "refs/stash" , "--" }
70
82
71
83
cmd , err := git .Log (logArgs ... )
72
84
if err != nil {
73
85
return err
74
86
}
75
87
76
- cmd .Start ()
77
- defer cmd .Wait ()
78
-
79
88
scanner := bufio .NewScanner (cmd .Stdout )
80
89
90
+ var allStashShas []string
81
91
for scanner .Scan () {
82
- err = s .ScanRef (strings .TrimSpace (scanner .Text ()), cb )
92
+ leafSha := strings .TrimSpace (scanner .Text ())
93
+
94
+ allStashShas = append (allStashShas , leafSha )
95
+
96
+ // For each leaf, use "git show" to expand parents & thus get
97
+ // all 2-3 shas involved in the stash
98
+ // As mentioned newer gits could do this in the reflog output but not gteed
99
+ showArgs := []string {"--quiet" , "--format=%p" , leafSha }
100
+ showCmd , err := git .Show (showArgs ... )
83
101
if err != nil {
84
102
return err
85
103
}
104
+
105
+ // gets the abbreviated parent hashes as :
106
+ // A B [C]
107
+ // A = Parent commit of the stash (ignore, not part of the stash)
108
+ // B = Index changes for the hash
109
+ // C = Untracked files (optional, only present if -u)
110
+ // So we need to scan refs for A, C and optionally D
111
+ showScanner := bufio .NewScanner (showCmd .Stdout )
112
+
113
+ for showScanner .Scan () {
114
+ line := strings .TrimSpace (showScanner .Text ())
115
+ refs := strings .Split (line , " " )
116
+ for i , ref := range refs {
117
+ if i > 0 { // Extra merge parents
118
+ allStashShas = append (allStashShas , ref )
119
+ }
120
+ }
121
+ }
122
+ err = showCmd .Wait ()
123
+ if err != nil {
124
+ return err
125
+ }
126
+
86
127
}
128
+ err = cmd .Wait ()
129
+ if err != nil {
130
+ // Ignore this error, it really only happens when there's no refs/stash
131
+ return nil
132
+ }
133
+
134
+ // Now we need to specifically use "git show" to parse results
135
+ // We can't use "git log" because weirdly that omits the index changes
136
+ // in the diff display, it collapses both into one diff and only shows the
137
+ // final change (not a 3-way like show). Only "show" on all the shas
138
+ // above displays them separately
139
+
140
+ // The "leaf" stash actually shows both the index and working copy, like this:
141
+
142
+ // - oid sha256:8e1c163c2a04e25158962537cbff2540ded60d4612506a27bc04d059c7ae16dd
143
+ // - oid sha256:f2f84832183a0fca648c1ef49cfd32632b16b47ef5f17ac07dcfcb0ae00b86e5
144
+ // -- size 16
145
+ // +++oid sha256:b23f7e7314c5921e3e1cd87456d7867a51ccbe0c2c19ee4df64525c468d775df
146
+ // +++size 30
147
+
148
+ // The second "-" entry has a space prefix which shows this as a 3-way diff
149
+ // However since we include all 2-3 commits explicitly in the git show,
150
+ // We get this line as a "+" entry in the other commit
151
+ // So we only need to care about the "+" entries
152
+ // We can use the log parser, which can now handle 3-char +/- prefixes as well
87
153
154
+ showArgs := logLfsSearchArgs
155
+ showArgs = append (showArgs , allStashShas ... )
156
+ showArgs = append (showArgs , "--" )
157
+
158
+ cmd , err = git .Show (showArgs ... )
159
+ if err != nil {
160
+ return err
161
+ }
162
+
163
+ parseScannerLogOutput (cb , LogDiffAdditions , cmd )
88
164
return nil
89
165
90
166
}
@@ -181,7 +257,8 @@ func newLogScanner(dir LogDiffDirection, r io.Reader) *logScanner {
181
257
commitHeaderRegex : regexp .MustCompile (fmt .Sprintf (`^lfs-commit-sha: (%s)(?: (%s))*` , git .ObjectIDRegex , git .ObjectIDRegex )),
182
258
fileHeaderRegex : regexp .MustCompile (`diff --git a\/(.+?)\s+b\/(.+)` ),
183
259
fileMergeHeaderRegex : regexp .MustCompile (`diff --cc (.+)` ),
184
- pointerDataRegex : regexp .MustCompile (`^([\+\- ])(version https://git-lfs|oid sha256|size|ext-).*$` ),
260
+ // stash diff can have up to 3 +/- characters. We only capture the first one
261
+ pointerDataRegex : regexp .MustCompile (`^([\+\- ]{1,3})(version https://git-lfs|oid sha256|size|ext-).*$` ),
185
262
}
186
263
}
187
264
@@ -273,11 +350,14 @@ func (s *logScanner) scan() (*WrappedPointer, bool) {
273
350
// -U3 will ensure we always get all of it, even if only
274
351
// the SHA changed (version & size the same)
275
352
changeType := match [1 ][0 ]
353
+ // merge lines can have 2-3 chars so can't just use changeType==' ' for blank
354
+ changeIsBlank := len (strings .TrimSpace (match [1 ])) == 0
276
355
277
356
// Always include unchanged context lines (normally just the version line)
278
- if LogDiffDirection (changeType ) == s .dir || changeType == ' ' {
357
+ if LogDiffDirection (changeType ) == s .dir || changeIsBlank {
279
358
// Must skip diff +/- marker
280
- s .pointerData .WriteString (line [1 :])
359
+ // can be 1-3 chars (3 for merge)
360
+ s .pointerData .WriteString (line [len (match [1 ]):])
281
361
s .pointerData .WriteString ("\n " ) // newline was stripped off by scanner
282
362
}
283
363
}
0 commit comments