#!/usr/bin/env bash . "$(dirname "$0")/testlib.sh" begin_test "fsck default" ( set -e reponame="fsck-default" git init $reponame cd $reponame # Create a commit with some files tracked by git-lfs git lfs track *.dat echo "test data" > a.dat echo "test data 2" > b.dat git add .gitattributes *.dat git commit -m "first commit" [ "Git LFS fsck OK" = "$(git lfs fsck)" ] aOid=$(git log --patch a.dat | grep "^+oid" | cut -d ":" -f 2) aOid12=$(echo $aOid | cut -b 1-2) aOid34=$(echo $aOid | cut -b 3-4) if [ "$aOid" != "$(calc_oid_file .git/lfs/objects/$aOid12/$aOid34/$aOid)" ]; then echo "oid for a.dat does not match" exit 1 fi bOid=$(git log --patch b.dat | grep "^+oid" | cut -d ":" -f 2) bOid12=$(echo $bOid | cut -b 1-2) bOid34=$(echo $bOid | cut -b 3-4) if [ "$bOid" != "$(calc_oid_file .git/lfs/objects/$bOid12/$bOid34/$bOid)" ]; then echo "oid for b.dat does not match" exit 1 fi echo "CORRUPTION" >> .git/lfs/objects/$aOid12/$aOid34/$aOid moved=$(canonical_path "$TRASHDIR/$reponame/.git/lfs/bad") expected="$(printf 'objects: corruptObject: a.dat (%s) is corrupt objects: repair: moving corrupt objects to %s' "$aOid" "$moved")" [ "$expected" = "$(git lfs fsck)" ] [ -e ".git/lfs/bad/$aOid" ] [ ! -e ".git/lfs/objects/$aOid12/$aOid34/$aOid" ] [ "$bOid" = "$(calc_oid_file .git/lfs/objects/$bOid12/$bOid34/$bOid)" ] ) end_test begin_test "fsck dry run" ( set -e reponame="fsck-dry-run" git init $reponame cd $reponame # Create a commit with some files tracked by git-lfs git lfs track *.dat echo "test data" > a.dat echo "test data 2" > b.dat git add .gitattributes *.dat git commit -m "first commit" [ "Git LFS fsck OK" = "$(git lfs fsck --dry-run)" ] aOid=$(git log --patch a.dat | grep "^+oid" | cut -d ":" -f 2) aOid12=$(echo $aOid | cut -b 1-2) aOid34=$(echo $aOid | cut -b 3-4) if [ "$aOid" != "$(calc_oid_file .git/lfs/objects/$aOid12/$aOid34/$aOid)" ]; then echo "oid for a.dat does not match" exit 1 fi bOid=$(git log --patch b.dat | grep "^+oid" | cut -d ":" -f 2) bOid12=$(echo $bOid | cut -b 1-2) bOid34=$(echo $bOid | cut -b 3-4) if [ "$bOid" != "$(calc_oid_file .git/lfs/objects/$bOid12/$bOid34/$bOid)" ]; then echo "oid for b.dat does not match" exit 1 fi echo "CORRUPTION" >> .git/lfs/objects/$aOid12/$aOid34/$aOid [ "objects: corruptObject: a.dat ($aOid) is corrupt" = "$(git lfs fsck --dry-run)" ] if [ "$aOid" = "$(calc_oid_file .git/lfs/objects/$aOid12/$aOid34/$aOid)" ]; then echo "oid for a.dat still matches match" exit 1 fi if [ "$bOid" != "$(calc_oid_file .git/lfs/objects/$bOid12/$bOid34/$bOid)" ]; then echo "oid for b.dat does not match" exit 1 fi ) end_test begin_test "fsck does not fail with shell characters in paths" ( set -e mkdir '[[path]]' cd '[[path]]' reponame="fsck-shell-paths" git init $reponame cd $reponame # Create a commit with some files tracked by git-lfs git lfs track *.dat echo "test data" > a.dat echo "test data 2" > b.dat git add .gitattributes *.dat git commit -m "first commit" # Verify that the pack code handles glob patterns properly. git gc --aggressive --prune=now [ "Git LFS fsck OK" = "$(git lfs fsck)" ] ) end_test begin_test "fsck: outside git repository" ( set +e git lfs fsck 2>&1 > fsck.log res=$? set -e if [ "$res" = "0" ]; then echo "Passes because $GIT_LFS_TEST_DIR is unset." exit 0 fi [ "$res" = "128" ] grep "Not in a Git repository" fsck.log ) end_test create_invalid_pointers() { valid="$1" ext="${2:-dat}" git cat-file blob ":$valid" | awk '{ sub(/$/, "\r"); print }' >"crlf.$ext" base64 /dev/urandom | head -c 1025 >"large.$ext" git \ -c "filter.lfs.process=" \ -c "filter.lfs.clean=cat" \ -c "filter.lfs.required=false" \ add "crlf.$ext" "large.$ext" git commit -m "invalid pointers" } setup_invalid_pointers () { git init $reponame cd $reponame # Create a commit with some files tracked by git-lfs git lfs track *.dat echo "test data" > a.dat echo "test data 2" > b.dat git add .gitattributes *.dat git commit -m "first commit" create_invalid_pointers "a.dat" } begin_test "fsck detects invalid pointers" ( set -e reponame="fsck-pointers" setup_invalid_pointers set +e git lfs fsck >test.log 2>&1 RET=$? git lfs fsck --pointers >>test.log 2>&1 RET2=$? set -e [ "$RET" -eq 1 ] [ "$RET2" -eq 1 ] [ $(grep -c 'pointer: nonCanonicalPointer: Pointer.*was not canonical' test.log) -eq 2 ] [ $(grep -c 'pointer: unexpectedGitObject: "large.dat".*should have been a pointer but was not' test.log) -eq 2 ] ) end_test begin_test "fsck detects invalid pointers with macro patterns" ( set -e reponame="fsck-pointers-macros" git init $reponame cd $reponame printf '[attr]lfs filter=lfs diff=lfs merge=lfs -text\n*.dat lfs\n' \ >.gitattributes echo "test data" >a.dat mkdir dir printf '*.bin lfs\n' >dir/.gitattributes git add .gitattributes a.dat dir git commit -m "first commit" create_invalid_pointers "a.dat" cd dir create_invalid_pointers "a.dat" "bin" cd .. # NOTE: We should also create a .dir directory with the same files as # as in the dir/ directory, and confirm those .dir/*.bin files are # reported by "git lfs fsck" as well. However, at the moment # "git lfs fsck" will not resolve a macro attribute reference # in .dir/.gitattributes because it sorts that file before # .gitattributes and then processes them in that order. set +e git lfs fsck >test.log 2>&1 RET=$? git lfs fsck --pointers >>test.log 2>&1 RET2=$? set -e [ "$RET" -eq 1 ] [ "$RET2" -eq 1 ] [ $(grep -c 'pointer: nonCanonicalPointer: Pointer.*was not canonical' test.log) -eq 4 ] [ $(grep -c 'pointer: unexpectedGitObject: "large.dat".*should have been a pointer but was not' test.log) -eq 2 ] [ $(grep -c 'pointer: unexpectedGitObject: "dir/large.bin".*should have been a pointer but was not' test.log) -eq 2 ] ) end_test begin_test "fsck detects invalid pointers with GIT_OBJECT_DIRECTORY" ( set -e reponame="fsck-pointers-object-directory" setup_invalid_pointers head=$(git rev-parse HEAD) objdir="$(lfstest-realpath .git/objects)" cd .. git init "$reponame-2" gitdir="$(lfstest-realpath "$reponame-2/.git")" GIT_WORK_TREE="$reponame-2" GIT_DIR="$gitdir" GIT_OBJECT_DIRECTORY="$objdir" git update-ref refs/heads/main "$head" set +e GIT_WORK_TREE="$reponame-2" GIT_DIR="$gitdir" GIT_OBJECT_DIRECTORY="$objdir" git lfs fsck --pointers >test.log 2>&1 RET=$? set -e [ "$RET" -eq 1 ] grep 'pointer: nonCanonicalPointer: Pointer.*was not canonical' test.log grep 'pointer: unexpectedGitObject: "large.dat".*should have been a pointer but was not' test.log ) end_test begin_test "fsck does not detect invalid pointers with no LFS objects" ( set -e reponame="fsck-pointers-none" git init "$reponame" cd "$reponame" echo "# README" > README.md git add README.md git commit -m "Add README" git lfs fsck git lfs fsck --pointers ) end_test begin_test "fsck does not detect invalid pointers with symlinks" ( set -e reponame="fsck-pointers-symlinks" git init "$reponame" cd "$reponame" git lfs track '*.dat' echo "# Test" > a.dat ln -s a.dat b.dat git add .gitattributes *.dat git commit -m "Add files" git lfs fsck git lfs fsck --pointers ) end_test begin_test "fsck does not detect invalid pointers with negated patterns" ( set -e reponame="fsck-pointers-none" git init "$reponame" cd "$reponame" cat > .gitattributes < a.dat cp a.dat b.dat git add .gitattributes *.dat git commit -m "Add files" git lfs fsck git lfs fsck --pointers ) end_test begin_test "fsck does not detect invalid pointers with negated macro patterns" ( set -e reponame="fsck-pointers-macros-none" git init "$reponame" cd "$reponame" printf '[attr]lfs filter=lfs diff=lfs merge=lfs -text\n*.dat lfs\nb.dat !lfs\n' \ >.gitattributes echo "test data" >a.dat cp a.dat b.dat mkdir dir .dir printf '*.dat !lfs\n' >dir/.gitattributes cp b.dat dir printf '*.dat !lfs\n' >.dir/.gitattributes cp b.dat .dir git add .gitattributes *.dat dir .dir git commit -m "first commit" # NOTE: The "git lfs fsck" command exempts the .dir/b.dat file from the # *.dat pattern from the top-level .gitattributes and so permits # it as a valid non-pointer file; however, it permits it for a # different reason than the dir/b.dat file, because it processes # the .dir/.gitattributes file before the .gitattributes one # and does not recognize the "!lfs" macro attribute reference until # after it has processed .gitattributes. Ideally both the dir/ # and .dir/ directories should be processed identically. git lfs fsck git lfs fsck --pointers ) end_test setup_invalid_objects () { git init $reponame cd $reponame # Create a commit with some files tracked by git-lfs git lfs track *.dat echo "test data" > a.dat echo "test data 2" > b.dat mkdir foo echo "test test 3" > foo/a.dat echo "test data 4" > foo/b.dat git add .gitattributes *.dat foo git commit -m "first commit" oid1=$(calc_oid_file a.dat) oid2=$(calc_oid_file b.dat) oid3=$(calc_oid_file foo/a.dat) oid4=$(calc_oid_file foo/b.dat) echo "CORRUPTION" >>".git/lfs/objects/${oid1:0:2}/${oid1:2:2}/$oid1" rm ".git/lfs/objects/${oid2:0:2}/${oid2:2:2}/$oid2" echo "CORRUPTION" >>".git/lfs/objects/${oid3:0:2}/${oid3:2:2}/$oid3" rm ".git/lfs/objects/${oid4:0:2}/${oid4:2:2}/$oid4" } begin_test "fsck detects invalid objects" ( set -e reponame="fsck-objects" setup_invalid_objects set +e git lfs fsck >test.log 2>&1 RET=$? set -e [ "$RET" -eq 1 ] [ $(grep -c 'objects: corruptObject: a.dat (.*) is corrupt' test.log) -eq 1 ] [ $(grep -c 'objects: openError: b.dat (.*) could not be checked: .*' test.log) -eq 1 ] [ $(grep -c 'objects: corruptObject: foo/a.dat (.*) is corrupt' test.log) -eq 1 ] [ $(grep -c 'objects: openError: foo/b.dat (.*) could not be checked: .*' test.log) -eq 1 ] [ $(grep -c 'objects: repair: moving corrupt objects to .*' test.log) -eq 1 ] cd .. rm -rf $reponame setup_invalid_objects set +e git lfs fsck --objects >test.log 2>&1 RET=$? set -e [ "$RET" -eq 1 ] [ $(grep -c 'objects: corruptObject: a.dat (.*) is corrupt' test.log) -eq 1 ] [ $(grep -c 'objects: openError: b.dat (.*) could not be checked: .*' test.log) -eq 1 ] [ $(grep -c 'objects: corruptObject: foo/a.dat (.*) is corrupt' test.log) -eq 1 ] [ $(grep -c 'objects: openError: foo/b.dat (.*) could not be checked: .*' test.log) -eq 1 ] [ $(grep -c 'objects: repair: moving corrupt objects to .*' test.log) -eq 1 ] ) end_test begin_test "fsck detects invalid objects except in excluded paths" ( set -e reponame="fsck-objects-exclude" setup_invalid_objects # We need to prevent MSYS from rewriting /foo into a Windows path. MSYS_NO_PATHCONV=1 git config "lfs.fetchexclude" "/foo" set +e git lfs fsck >test.log 2>&1 RET=$? set -e [ "$RET" -eq 1 ] [ $(grep -c 'objects: corruptObject: a.dat (.*) is corrupt' test.log) -eq 1 ] [ $(grep -c 'objects: openError: b.dat (.*) could not be checked: .*' test.log) -eq 1 ] [ $(grep -c 'objects: corruptObject: foo/a.dat (.*) is corrupt' test.log) -eq 0 ] [ $(grep -c 'objects: openError: foo/b.dat (.*) could not be checked: .*' test.log) -eq 0 ] [ $(grep -c 'objects: repair: moving corrupt objects to .*' test.log) -eq 1 ] cd .. rm -rf $reponame setup_invalid_objects # We need to prevent MSYS from rewriting /foo into a Windows path. MSYS_NO_PATHCONV=1 git config "lfs.fetchexclude" "/foo" set +e git lfs fsck --objects >test.log 2>&1 RET=$? set -e [ "$RET" -eq 1 ] [ $(grep -c 'objects: corruptObject: a.dat (.*) is corrupt' test.log) -eq 1 ] [ $(grep -c 'objects: openError: b.dat (.*) could not be checked: .*' test.log) -eq 1 ] [ $(grep -c 'objects: corruptObject: foo/a.dat (.*) is corrupt' test.log) -eq 0 ] [ $(grep -c 'objects: openError: foo/b.dat (.*) could not be checked: .*' test.log) -eq 0 ] [ $(grep -c 'objects: repair: moving corrupt objects to .*' test.log) -eq 1 ] ) end_test begin_test "fsck does not detect invalid objects with no LFS objects" ( set -e reponame="fsck-objects-none" git init "$reponame" cd "$reponame" echo "# README" > README.md git add README.md git commit -m "Add README" git lfs fsck git lfs fsck --objects ) end_test begin_test "fsck operates on specified refs" ( set -e reponame="fsck-refs" setup_invalid_pointers git rm -f crlf.dat large.dat echo "# Test" > new.dat git add new.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 git lfs fsck --pointers HEAD^^^..HEAD^^ >test.log 2>&1 && exit 1 grep 'pointer: nonCanonicalPointer: Pointer.*was not canonical' test.log grep 'pointer: unexpectedGitObject: "large.dat".*should have been a pointer but was not' test.log oid=$(calc_oid_file new.dat) echo "CORRUPTION" >>".git/lfs/objects/${oid:0:2}/${oid:2:2}/$oid" git lfs fsck --objects HEAD^^..HEAD^ >test.log 2>&1 && exit 1 grep 'objects: corruptObject: new.dat (.*) is corrupt' test.log grep 'objects: repair: moving corrupt objects to .*' test.log # Make the result of the subshell a success. true ) end_test