commands/fsck: allow scanning revision ranges

Currently, git lfs fsck operates only on the HEAD and index.  For
objects, this is usually the right decision, since only objects for HEAD
are checked out.

However, for pointers, this may not be desired.  A user may want to
check pointers for a range of commits, such as during a CI job.  To deal
with these cases, let a user specify a revision or a simple range of
revisions to operate on, and process those revisions.

Note that we don't currently process the index with --pointers because
this requires a completely different set of scanners which are not yet
implemented.  We can implement such a feature in a future revision if
desired.

In the tests, refactor out our setup code into a function for reuse in
multiple tests.
This commit is contained in:
brian m. carlson 2021-06-10 18:02:43 +00:00
parent cca4977b23
commit e6c9d1de19
No known key found for this signature in database
GPG Key ID: 2D0C9BC12F82B3A1
4 changed files with 98 additions and 18 deletions

@ -7,6 +7,7 @@ import (
"io"
"os"
"path/filepath"
"strings"
"github.com/git-lfs/git-lfs/errors"
"github.com/git-lfs/git-lfs/filepathfilter"
@ -45,9 +46,30 @@ func fsckCommand(cmd *cobra.Command, args []string) {
installHooks(false)
setupRepository()
ref, err := git.CurrentRef()
if err != nil {
ExitWithError(err)
useIndex := false
start := ""
end := "HEAD"
switch len(args) {
case 0:
useIndex = true
ref, err := git.CurrentRef()
if err != nil {
ExitWithError(err)
}
end = ref.Sha
case 1:
pieces := strings.SplitN(args[0], "..", 2)
refs, err := git.ResolveRefs(pieces)
if err != nil {
ExitWithError(err)
}
if len(refs) == 2 {
start = refs[0].Sha
end = refs[1].Sha
} else {
end = refs[0].Sha
}
}
if !fsckPointers && !fsckObjects {
@ -93,7 +115,7 @@ func fsckCommand(cmd *cobra.Command, args []string) {
}
// doFsckObjects checks that the objects in the given ref are correct and exist.
func doFsckObjects(ref *git.Ref) []string {
func doFsckObjects(start, end string, useIndex bool) []string {
var corruptOids []string
gitscanner := lfs.NewGitScanner(cfg, func(p *lfs.WrappedPointer, err error) {
if err == nil {
@ -116,12 +138,20 @@ func doFsckObjects(ref *git.Ref) []string {
// Attach a filepathfilter to avoid _only_ the excluded paths.
gitscanner.Filter = filepathfilter.New(nil, cfg.FetchExcludePaths())
if err := gitscanner.ScanRef(ref.Sha, nil); err != nil {
ExitWithError(err)
if start == "" {
if err := gitscanner.ScanRef(end, nil); err != nil {
ExitWithError(err)
}
} else {
if err := gitscanner.ScanRefRange(start, end, nil); err != nil {
ExitWithError(err)
}
}
if err := gitscanner.ScanIndex("HEAD", nil); err != nil {
ExitWithError(err)
if useIndex {
if err := gitscanner.ScanIndex("HEAD", nil); err != nil {
ExitWithError(err)
}
}
gitscanner.Close()
@ -161,8 +191,14 @@ func doFsckPointers(start, end string) []corruptPointer {
}
})
if err := gitscanner.ScanRefByTree(ref.Sha, nil); err != nil {
ExitWithError(err)
if start == "" {
if err := gitscanner.ScanRefByTree(end, nil); err != nil {
ExitWithError(err)
}
} else {
if err := gitscanner.ScanRefRangeByTree(start, end, nil); err != nil {
ExitWithError(err)
}
}
gitscanner.Close()

@ -3,7 +3,7 @@ git-lfs-fsck(1) -- Check GIT LFS files for consistency
## SYNOPSIS
`git lfs fsck` [options]
`git lfs fsck` [options] [revisions]
## DESCRIPTION

@ -140,6 +140,20 @@ func (s *GitScanner) ScanRefRange(left, right string, cb GitScannerFoundPointer)
return scanLeftRightToChan(s, callback, left, right, s.cfg.GitEnv(), s.cfg.OSEnv(), opts)
}
// ScanRefRangeByTree scans through all trees from the given left and right
// refs.
func (s *GitScanner) ScanRefRangeByTree(left, right string, cb GitScannerFoundPointer) error {
callback, err := firstGitScannerCallback(cb, s.FoundPointer)
if err != nil {
return err
}
opts := s.opts(ScanRefsMode)
opts.SkipDeletedBlobs = false
opts.CommitsOnly = true
return scanRefsByTree(s, callback, []string{right}, []string{left}, s.cfg.GitEnv(), s.cfg.OSEnv(), opts)
}
// ScanRefWithDeleted scans through all objects in the given ref, including
// git objects that have been modified or deleted.
func (s *GitScanner) ScanRefWithDeleted(ref string, cb GitScannerFoundPointer) error {

@ -138,11 +138,7 @@ begin_test "fsck: outside git repository"
)
end_test
begin_test "fsck detects invalid pointers"
(
set -e
reponame="fsck-pointers"
setup_invalid_pointers () {
git init $reponame
cd $reponame
@ -151,6 +147,7 @@ begin_test "fsck detects invalid pointers"
echo "test data" > a.dat
echo "test data 2" > b.dat
git add .gitattributes *.dat
git commit -m "first commit"
git cat-file blob :a.dat | awk '{ sub(/$/, "\r"); print }' >crlf.dat
base64 /dev/urandom | head -c 1025 > large.dat
@ -158,8 +155,16 @@ begin_test "fsck detects invalid pointers"
-c "filter.lfs.process=" \
-c "filter.lfs.clean=cat" \
-c "filter.lfs.required=false" \
add *.dat
git commit -m "first commit"
add crlf.dat large.dat
git commit -m "second commit"
}
begin_test "fsck detects invalid pointers"
(
set -e
reponame="fsck-pointers"
setup_invalid_pointers
set +e
git lfs fsck >test.log 2>&1
@ -174,3 +179,28 @@ begin_test "fsck detects invalid pointers"
[ $(grep -c 'pointer: unexpectedGitObject: "large.dat".*should have been a pointer but was not' test.log) -eq 2 ]
)
end_test
begin_test "fsck operates on specified refs"
(
set -e
reponame="fsck-refs"
setup_invalid_pointers
git rm -f crlf.dat large.dat
git commit -m 'third commit'
git commit --allow-empty -m 'fourth commit'
# Should succeed. (HEAD and index).
git lfs fsck
git lfs fsck HEAD
git lfs fsck HEAD^^ && exit 1
git lfs fsck HEAD^
git lfs fsck HEAD^..HEAD
git lfs fsck HEAD^^^..HEAD && exit 1
git lfs fsck HEAD^^^..HEAD^ && exit 1
# Make the result of the subshell a success.
true
)
end_test