Skip to content

Commit 4049928

Browse files
committed
Enhanced prune stash scanner to handle index, working copy, untracked
1 parent eaa85a7 commit 4049928

File tree

2 files changed

+94
-9
lines changed

2 files changed

+94
-9
lines changed

git/git.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,11 @@ func Log(args ...string) (*subprocess.BufferedCmd, error) {
269269
return gitNoLFSBuffered(logArgs...)
270270
}
271271

272+
func Show(args ...string) (*subprocess.BufferedCmd, error) {
273+
logArgs := append([]string{"show"}, args...)
274+
return gitNoLFSBuffered(logArgs...)
275+
}
276+
272277
func LsRemote(remote, remoteRef string) (string, error) {
273278
if remote == "" {
274279
return "", errors.New("remote required")

lfs/gitscanner_log.go

Lines changed: 89 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,26 +65,102 @@ func scanUnpushed(cb GitScannerFoundPointer, remote string) error {
6565
}
6666

6767
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", "--"}
7082

7183
cmd, err := git.Log(logArgs...)
7284
if err != nil {
7385
return err
7486
}
7587

76-
cmd.Start()
77-
defer cmd.Wait()
78-
7988
scanner := bufio.NewScanner(cmd.Stdout)
8089

90+
var allStashShas []string
8191
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...)
83101
if err != nil {
84102
return err
85103
}
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+
86127
}
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
87153

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)
88164
return nil
89165

90166
}
@@ -181,7 +257,8 @@ func newLogScanner(dir LogDiffDirection, r io.Reader) *logScanner {
181257
commitHeaderRegex: regexp.MustCompile(fmt.Sprintf(`^lfs-commit-sha: (%s)(?: (%s))*`, git.ObjectIDRegex, git.ObjectIDRegex)),
182258
fileHeaderRegex: regexp.MustCompile(`diff --git a\/(.+?)\s+b\/(.+)`),
183259
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-).*$`),
185262
}
186263
}
187264

@@ -273,11 +350,14 @@ func (s *logScanner) scan() (*WrappedPointer, bool) {
273350
// -U3 will ensure we always get all of it, even if only
274351
// the SHA changed (version & size the same)
275352
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
276355

277356
// Always include unchanged context lines (normally just the version line)
278-
if LogDiffDirection(changeType) == s.dir || changeType == ' ' {
357+
if LogDiffDirection(changeType) == s.dir || changeIsBlank {
279358
// 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]):])
281361
s.pointerData.WriteString("\n") // newline was stripped off by scanner
282362
}
283363
}

0 commit comments

Comments
 (0)