Skip to content

Commit 2dc718b

Browse files
committed
prune: fix deleting objects referred to by stashes
It was impossible to pop a stash with LFS data successfully after running any lfs prune command before this fix, because prune would consider stashed data unreferenced. This fixes git-lfs#4206
1 parent c2722a3 commit 2dc718b

File tree

5 files changed

+106
-1
lines changed

5 files changed

+106
-1
lines changed

commands/command_prune.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func prune(fetchPruneConfig lfs.FetchPruneConfig, verifyRemote, dryRun, verbose
7171
// Add all the base funcs to the waitgroup before starting them, in case
7272
// one completes really fast & hits 0 unexpectedly
7373
// each main process can Add() to the wg itself if it subdivides the task
74-
taskwait.Add(4) // 1..4: localObjects, current & recent refs, unpushed, worktree
74+
taskwait.Add(5) // 1..5: localObjects, current & recent refs, unpushed, worktree, stashes
7575
if verifyRemote {
7676
taskwait.Add(1) // 5
7777
}
@@ -99,6 +99,7 @@ func prune(fetchPruneConfig lfs.FetchPruneConfig, verifyRemote, dryRun, verbose
9999
go pruneTaskGetRetainedCurrentAndRecentRefs(gitscanner, fetchPruneConfig, retainChan, errorChan, &taskwait, sem)
100100
go pruneTaskGetRetainedUnpushed(gitscanner, fetchPruneConfig, retainChan, errorChan, &taskwait, sem)
101101
go pruneTaskGetRetainedWorktree(gitscanner, retainChan, errorChan, &taskwait, sem)
102+
go pruneTaskGetRetainedStashed(gitscanner, retainChan, errorChan, &taskwait, sem)
102103
if verifyRemote {
103104
reachableObjects = tools.NewStringSetWithCapacity(100)
104105
go pruneTaskGetReachableObjects(gitscanner, &reachableObjects, errorChan, &taskwait, sem)
@@ -476,6 +477,25 @@ func pruneTaskGetRetainedWorktree(gitscanner *lfs.GitScanner, retainChan chan st
476477
}
477478
}
478479

480+
// Background task, must call waitg.Done() once at end
481+
func pruneTaskGetRetainedStashed(gitscanner *lfs.GitScanner, retainChan chan string, errorChan chan error, waitg *sync.WaitGroup, sem *semaphore.Weighted) {
482+
defer waitg.Done()
483+
484+
err := gitscanner.ScanStashed(func(p *lfs.WrappedPointer, err error) {
485+
if err != nil {
486+
errorChan <- err
487+
} else {
488+
retainChan <- p.Pointer.Oid
489+
tracerx.Printf("RETAIN: %v stashed", p.Pointer.Oid)
490+
}
491+
})
492+
493+
if err != nil {
494+
errorChan <- err
495+
return
496+
}
497+
}
498+
479499
// Background task, must call waitg.Done() once at end
480500
func pruneTaskGetReachableObjects(gitscanner *lfs.GitScanner, outObjectSet *tools.StringSet, errorChan chan error, waitg *sync.WaitGroup, sem *semaphore.Weighted) {
481501
defer waitg.Done()

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 RefLog(args ...string) (*subprocess.BufferedCmd, error) {
273+
reflogArgs := append([]string{"reflog"}, args...)
274+
return gitNoLFSBuffered(reflogArgs...)
275+
}
276+
272277
func LsRemote(remote, remoteRef string) (string, error) {
273278
if remote == "" {
274279
return "", errors.New("remote required")

lfs/gitscanner.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,16 @@ func (s *GitScanner) ScanUnpushed(remote string, cb GitScannerFoundPointer) erro
192192
return scanUnpushed(callback, remote)
193193
}
194194

195+
// ScanStashed scans for all LFS pointers referenced solely by a stash
196+
func (s *GitScanner) ScanStashed(cb GitScannerFoundPointer) error {
197+
callback, err := firstGitScannerCallback(cb, s.FoundPointer)
198+
if err != nil {
199+
return err
200+
}
201+
202+
return scanStashed(callback, s)
203+
}
204+
195205
// ScanPreviousVersions scans changes reachable from ref (commit) back to since.
196206
// Returns channel of pointers for *previous* versions that overlap that time.
197207
// Does not include pointers which were still in use at ref (use ScanRefsToChan

lfs/gitscanner_log.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io"
88
"io/ioutil"
99
"regexp"
10+
"strings"
1011
"time"
1112

1213
"github.com/git-lfs/git-lfs/filepathfilter"
@@ -63,6 +64,32 @@ func scanUnpushed(cb GitScannerFoundPointer, remote string) error {
6364
return nil
6465
}
6566

67+
func scanStashed(cb GitScannerFoundPointer, s *GitScanner) error {
68+
// First get the SHAs of all stashes
69+
// git reflog show --format="%H" stash
70+
reflogArgs := []string{"show", "--format=%H", "stash"}
71+
72+
cmd, err := git.RefLog(reflogArgs...)
73+
if err != nil {
74+
return err
75+
}
76+
77+
cmd.Start()
78+
defer cmd.Wait()
79+
80+
scanner := bufio.NewScanner(cmd.Stdout)
81+
82+
for scanner.Scan() {
83+
err = s.ScanRef(strings.TrimSpace(scanner.Text()), cb)
84+
if err != nil {
85+
return err
86+
}
87+
}
88+
89+
return nil
90+
91+
}
92+
6693
func parseScannerLogOutput(cb GitScannerFoundPointer, direction LogDiffDirection, cmd *subprocess.BufferedCmd) {
6794
ch := make(chan gitscannerResult, chanBufSize)
6895

t/t-prune.sh

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,3 +594,46 @@ begin_test "prune verify large numbers of refs"
594594

595595
)
596596
end_test
597+
598+
begin_test "prune keep stashed changes"
599+
(
600+
set -e
601+
602+
reponame="prune_keep_stashed"
603+
setup_remote_repo "remote_$reponame"
604+
605+
clone_repo "remote_$reponame" "clone_$reponame"
606+
607+
git lfs track "*.dat" 2>&1 | tee track.log
608+
grep "Tracking \"\*.dat\"" track.log
609+
610+
# generate content we'll use
611+
content_inrepo="This is the original committed data"
612+
oid_inrepo=$(calc_oid "$content_inrepo")
613+
content_stashed="This data will be stashed and should not be deleted"
614+
oid_stashed=$(calc_oid "$content_stashed")
615+
616+
# We just need one commit of base data, makes it easier to test stash
617+
echo "[
618+
{
619+
\"CommitDate\":\"$(get_date -1d)\",
620+
\"Files\":[
621+
{\"Filename\":\"stashedfile.dat\",\"Size\":${#content_inrepo}, \"Data\":\"$content_inrepo\"}]
622+
}
623+
]" | lfstest-testutils addcommits
624+
625+
# now modify the file, and stash it
626+
echo -n "$content_stashed" > stashedfile.dat
627+
628+
git stash
629+
630+
# Prove that the stashed data was stored in LFS (should call clean filter)
631+
assert_local_object "$oid_stashed" "${#content_stashed}"
632+
633+
# Prune data, should NOT delete stashed file
634+
git lfs prune
635+
636+
assert_local_object "$oid_stashed" "${#content_stashed}"
637+
638+
)
639+
end_test

0 commit comments

Comments
 (0)