git-lfs/t/t-fsck.sh
Chris Darroch fef4c0ff98 t: pipe data to base64 to be compatible with macOS
The base64(1) binary provided with macOS does not accept a bare input
file name, unlike any of the GNU coreutils implementations of base64(1)
or either the older Fourmilab or newer bintrans(1) BSD implementations.
Instead, it appears to be a version unique to OS X which requires an -i
option before a file name to be used for input.  See, for reference:

  https://www.unix.com/man-page/osx/1/base64/
  https://man7.org/linux/man-pages/man1/base64.1.html
  https://www.fourmilab.ch/webtools/base64/
  https://man.freebsd.org/cgi/man.cgi?query=base64&manpath=FreeBSD+15.0-CURRENT

With the Xcode developer tools for macOS installed, along with Go, Git,
etc., our test suite mostly succeeds, but several tests fail because
they expect to call base64(1) and pass the bare /dev/urandom file name.

We can resolve this inconvenience for developers by adjusting those
tests to use the technique we already use in the t/t-migrate-*.sh
test suites where we redirect /dev/urandom into base64 as its
standard input.  This allows the test suites to function on both
macOS as well as Linux systems which use one of the other base64(1)
implementations.
2024-01-10 20:29:43 -08:00

514 lines
14 KiB
Bash
Executable File

#!/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 <<EOF
*.dat filter=lfs diff=lfs merge=lfs -text
b.dat !filter !diff !merge text
EOF
echo "# Test" > 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