| 1 | #!/bin/sh |
| 2 | |
| 3 | test_description='git fsck random collection of tests |
| 4 | |
| 5 | * (HEAD) B |
| 6 | * (main) A |
| 7 | ' |
| 8 | |
| 9 | . ./test-lib.sh |
| 10 | . "$TEST_DIRECTORY/lib-loose.sh" |
| 11 | |
| 12 | test_expect_success setup ' |
| 13 | git config gc.auto 0 && |
| 14 | git config i18n.commitencoding ISO-8859-1 && |
| 15 | test_commit A fileA one && |
| 16 | git config --unset i18n.commitencoding && |
| 17 | git checkout HEAD^0 && |
| 18 | test_commit B fileB two && |
| 19 | orig_head=$(git rev-parse HEAD) && |
| 20 | git tag -d A B && |
| 21 | git reflog expire --expire=now --all |
| 22 | ' |
| 23 | |
| 24 | test_expect_success 'loose objects borrowed from alternate are not missing' ' |
| 25 | mkdir another && |
| 26 | ( |
| 27 | cd another && |
| 28 | git init && |
| 29 | echo ../../../.git/objects >.git/objects/info/alternates && |
| 30 | test_commit C fileC one && |
| 31 | git fsck --no-dangling >../actual 2>&1 |
| 32 | ) && |
| 33 | test_must_be_empty actual |
| 34 | ' |
| 35 | |
| 36 | test_expect_success 'HEAD is part of refs, valid objects appear valid' ' |
| 37 | git fsck >actual 2>&1 && |
| 38 | test_must_be_empty actual |
| 39 | ' |
| 40 | |
| 41 | # Corruption tests follow. Make sure to remove all traces of the |
| 42 | # specific corruption you test afterwards, lest a later test trip over |
| 43 | # it. |
| 44 | |
| 45 | sha1_file () { |
| 46 | git rev-parse --git-path objects/$(test_oid_to_path "$1") |
| 47 | } |
| 48 | |
| 49 | remove_object () { |
| 50 | rm "$(sha1_file "$1")" |
| 51 | } |
| 52 | |
| 53 | test_expect_success 'object with hash mismatch' ' |
| 54 | git init --bare hash-mismatch && |
| 55 | ( |
| 56 | cd hash-mismatch && |
| 57 | |
| 58 | oid=$(echo blob | git hash-object -w --stdin) && |
| 59 | oldoid=$oid && |
| 60 | old=$(test_oid_to_path "$oid") && |
| 61 | new=$(dirname $old)/$(test_oid ff_2) && |
| 62 | oid="$(dirname $new)$(basename $new)" && |
| 63 | |
| 64 | mv objects/$old objects/$new && |
| 65 | git update-index --add --cacheinfo 100644 $oid foo && |
| 66 | tree=$(git write-tree) && |
| 67 | cmt=$(echo bogus | git commit-tree $tree) && |
| 68 | git update-ref refs/heads/bogus $cmt && |
| 69 | |
| 70 | test_must_fail git fsck 2>out && |
| 71 | grep "$oldoid: hash-path mismatch, found at: .*$new" out |
| 72 | ) |
| 73 | ' |
| 74 | |
| 75 | test_expect_success 'zlib corrupt loose object output ' ' |
| 76 | git init --bare corrupt-loose-output && |
| 77 | ( |
| 78 | cd corrupt-loose-output && |
| 79 | oid=$(git hash-object -w --stdin --literally </dev/null) && |
| 80 | oidf=objects/$(test_oid_to_path "$oid") && |
| 81 | chmod +w $oidf && |
| 82 | echo extra garbage >>$oidf && |
| 83 | |
| 84 | cat >expect.error <<-EOF && |
| 85 | error: garbage at end of loose object '\''$oid'\'' |
| 86 | error: unable to unpack contents of ./$oidf |
| 87 | error: $oid: object corrupt or missing: ./$oidf |
| 88 | EOF |
| 89 | test_must_fail git fsck 2>actual && |
| 90 | grep ^error: actual >error && |
| 91 | test_cmp expect.error error |
| 92 | ) |
| 93 | ' |
| 94 | |
| 95 | test_expect_success 'branch pointing to non-commit' ' |
| 96 | tree_oid=$(git rev-parse --verify HEAD^{tree}) && |
| 97 | test_when_finished "git update-ref -d refs/heads/invalid" && |
| 98 | test-tool ref-store main update-ref msg refs/heads/invalid $tree_oid $ZERO_OID REF_SKIP_OID_VERIFICATION && |
| 99 | test_must_fail git fsck 2>out && |
| 100 | test_grep "not a commit" out |
| 101 | ' |
| 102 | |
| 103 | test_expect_success REFFILES 'HEAD link pointing at a funny object' ' |
| 104 | test_when_finished "git update-ref HEAD $orig_head" && |
| 105 | echo $ZERO_OID >.git/HEAD && |
| 106 | # avoid corrupt/broken HEAD from interfering with repo discovery |
| 107 | test_must_fail env GIT_DIR=.git git fsck 2>out && |
| 108 | test_grep "HEAD: badRefOid: points to invalid object ID ${SQ}$ZERO_OID${SQ}" out |
| 109 | ' |
| 110 | |
| 111 | test_expect_success 'HEAD link pointing at a funny place' ' |
| 112 | test_when_finished "git update-ref --no-deref HEAD $orig_head" && |
| 113 | test-tool ref-store main create-symref HEAD refs/funny/place && |
| 114 | # avoid corrupt/broken HEAD from interfering with repo discovery |
| 115 | test_must_fail env GIT_DIR=.git git fsck 2>out && |
| 116 | test_grep "HEAD: badHeadTarget: HEAD points to non-branch ${SQ}refs/funny/place${SQ}" out |
| 117 | ' |
| 118 | |
| 119 | test_expect_success REFFILES 'HEAD link pointing at a funny object (from different wt)' ' |
| 120 | test_when_finished "git update-ref HEAD $orig_head" && |
| 121 | test_when_finished "git worktree remove -f wt" && |
| 122 | git worktree add wt && |
| 123 | echo $ZERO_OID >.git/HEAD && |
| 124 | # avoid corrupt/broken HEAD from interfering with repo discovery |
| 125 | test_must_fail git -C wt fsck 2>out && |
| 126 | test_grep "HEAD: badRefOid: points to invalid object ID ${SQ}$ZERO_OID${SQ}" out |
| 127 | ' |
| 128 | |
| 129 | test_expect_success REFFILES 'other worktree HEAD link pointing at a funny object' ' |
| 130 | test_when_finished "git worktree remove -f other" && |
| 131 | git worktree add other && |
| 132 | echo $ZERO_OID >.git/worktrees/other/HEAD && |
| 133 | test_must_fail git fsck 2>out && |
| 134 | test_grep "worktrees/other/HEAD: badRefOid: points to invalid object ID ${SQ}$ZERO_OID${SQ}" out |
| 135 | ' |
| 136 | |
| 137 | test_expect_success 'other worktree HEAD link pointing at missing object' ' |
| 138 | test_when_finished "git worktree remove -f other" && |
| 139 | git worktree add other && |
| 140 | object_id=$(echo "Contents missing from repo" | git hash-object --stdin) && |
| 141 | test-tool -C other ref-store main update-ref msg HEAD $object_id "" REF_NO_DEREF,REF_SKIP_OID_VERIFICATION && |
| 142 | test_must_fail git fsck 2>out && |
| 143 | test_grep "worktrees/other/HEAD: invalid sha1 pointer" out |
| 144 | ' |
| 145 | |
| 146 | test_expect_success 'other worktree HEAD link pointing at a funny place' ' |
| 147 | test_when_finished "git worktree remove -f other" && |
| 148 | git worktree add other && |
| 149 | git -C other symbolic-ref HEAD refs/funny/place && |
| 150 | test_must_fail git fsck 2>out && |
| 151 | test_grep "worktrees/other/HEAD: badHeadTarget: HEAD points to non-branch ${SQ}refs/funny/place${SQ}" out |
| 152 | ' |
| 153 | |
| 154 | test_expect_success 'commit with multiple signatures is okay' ' |
| 155 | git cat-file commit HEAD >basis && |
| 156 | cat >sigs <<-EOF && |
| 157 | gpgsig -----BEGIN PGP SIGNATURE----- |
| 158 | VGhpcyBpcyBub3QgcmVhbGx5IGEgc2lnbmF0dXJlLg== |
| 159 | -----END PGP SIGNATURE----- |
| 160 | gpgsig-sha256 -----BEGIN PGP SIGNATURE----- |
| 161 | VGhpcyBpcyBub3QgcmVhbGx5IGEgc2lnbmF0dXJlLg== |
| 162 | -----END PGP SIGNATURE----- |
| 163 | EOF |
| 164 | sed -e "/^committer/q" basis >okay && |
| 165 | cat sigs >>okay && |
| 166 | echo >>okay && |
| 167 | sed -e "1,/^$/d" basis >>okay && |
| 168 | cat okay && |
| 169 | new=$(git hash-object -t commit -w --stdin <okay) && |
| 170 | test_when_finished "remove_object $new" && |
| 171 | git update-ref refs/heads/bogus "$new" && |
| 172 | test_when_finished "git update-ref -d refs/heads/bogus" && |
| 173 | git fsck 2>out && |
| 174 | cat out && |
| 175 | ! grep "commit $new" out |
| 176 | ' |
| 177 | |
| 178 | test_expect_success 'email without @ is okay' ' |
| 179 | git cat-file commit HEAD >basis && |
| 180 | sed "s/@/AT/" basis >okay && |
| 181 | new=$(git hash-object -t commit -w --stdin <okay) && |
| 182 | test_when_finished "remove_object $new" && |
| 183 | git update-ref refs/heads/bogus "$new" && |
| 184 | test_when_finished "git update-ref -d refs/heads/bogus" && |
| 185 | git fsck 2>out && |
| 186 | ! grep "commit $new" out |
| 187 | ' |
| 188 | |
| 189 | test_expect_success 'email with embedded > is not okay' ' |
| 190 | git cat-file commit HEAD >basis && |
| 191 | sed "s/@[a-z]/&>/" basis >bad-email && |
| 192 | new=$(git hash-object --literally -t commit -w --stdin <bad-email) && |
| 193 | test_when_finished "remove_object $new" && |
| 194 | git update-ref refs/heads/bogus "$new" && |
| 195 | test_when_finished "git update-ref -d refs/heads/bogus" && |
| 196 | test_must_fail git fsck 2>out && |
| 197 | test_grep "error in commit $new" out |
| 198 | ' |
| 199 | |
| 200 | test_expect_success 'missing < email delimiter is reported nicely' ' |
| 201 | git cat-file commit HEAD >basis && |
| 202 | sed "s/<//" basis >bad-email-2 && |
| 203 | new=$(git hash-object --literally -t commit -w --stdin <bad-email-2) && |
| 204 | test_when_finished "remove_object $new" && |
| 205 | git update-ref refs/heads/bogus "$new" && |
| 206 | test_when_finished "git update-ref -d refs/heads/bogus" && |
| 207 | test_must_fail git fsck 2>out && |
| 208 | test_grep "error in commit $new.* - bad name" out |
| 209 | ' |
| 210 | |
| 211 | test_expect_success 'missing email is reported nicely' ' |
| 212 | git cat-file commit HEAD >basis && |
| 213 | sed "s/[a-z]* <[^>]*>//" basis >bad-email-3 && |
| 214 | new=$(git hash-object --literally -t commit -w --stdin <bad-email-3) && |
| 215 | test_when_finished "remove_object $new" && |
| 216 | git update-ref refs/heads/bogus "$new" && |
| 217 | test_when_finished "git update-ref -d refs/heads/bogus" && |
| 218 | test_must_fail git fsck 2>out && |
| 219 | test_grep "error in commit $new.* - missing email" out |
| 220 | ' |
| 221 | |
| 222 | test_expect_success '> in name is reported' ' |
| 223 | git cat-file commit HEAD >basis && |
| 224 | sed "s/ </> </" basis >bad-email-4 && |
| 225 | new=$(git hash-object --literally -t commit -w --stdin <bad-email-4) && |
| 226 | test_when_finished "remove_object $new" && |
| 227 | git update-ref refs/heads/bogus "$new" && |
| 228 | test_when_finished "git update-ref -d refs/heads/bogus" && |
| 229 | test_must_fail git fsck 2>out && |
| 230 | test_grep "error in commit $new" out |
| 231 | ' |
| 232 | |
| 233 | # date is 2^64 + 1 |
| 234 | test_expect_success 'integer overflow in timestamps is reported' ' |
| 235 | git cat-file commit HEAD >basis && |
| 236 | sed "s/^\\(author .*>\\) [0-9]*/\\1 18446744073709551617/" \ |
| 237 | <basis >bad-timestamp && |
| 238 | new=$(git hash-object --literally -t commit -w --stdin <bad-timestamp) && |
| 239 | test_when_finished "remove_object $new" && |
| 240 | git update-ref refs/heads/bogus "$new" && |
| 241 | test_when_finished "git update-ref -d refs/heads/bogus" && |
| 242 | test_must_fail git fsck 2>out && |
| 243 | test_grep "error in commit $new.*integer overflow" out |
| 244 | ' |
| 245 | |
| 246 | test_expect_success 'commit with NUL in header' ' |
| 247 | git cat-file commit HEAD >basis && |
| 248 | sed "s/author ./author Q/" <basis | q_to_nul >commit-NUL-header && |
| 249 | new=$(git hash-object --literally -t commit -w --stdin <commit-NUL-header) && |
| 250 | test_when_finished "remove_object $new" && |
| 251 | git update-ref refs/heads/bogus "$new" && |
| 252 | test_when_finished "git update-ref -d refs/heads/bogus" && |
| 253 | test_must_fail git fsck 2>out && |
| 254 | test_grep "error in commit $new.*unterminated header: NUL at offset" out |
| 255 | ' |
| 256 | |
| 257 | test_expect_success 'tree object with duplicate entries' ' |
| 258 | test_when_finished "for i in \$T; do remove_object \$i; done" && |
| 259 | T=$( |
| 260 | GIT_INDEX_FILE=test-index && |
| 261 | export GIT_INDEX_FILE && |
| 262 | rm -f test-index && |
| 263 | >x && |
| 264 | git add x && |
| 265 | git rev-parse :x && |
| 266 | T=$(git write-tree) && |
| 267 | echo $T && |
| 268 | ( |
| 269 | git cat-file tree $T && |
| 270 | git cat-file tree $T |
| 271 | ) | |
| 272 | git hash-object --literally -w -t tree --stdin |
| 273 | ) && |
| 274 | test_must_fail git fsck 2>out && |
| 275 | test_grep "error in tree .*contains duplicate file entries" out |
| 276 | ' |
| 277 | |
| 278 | check_duplicate_names () { |
| 279 | expect=$1 && |
| 280 | shift && |
| 281 | names=$@ && |
| 282 | test_expect_$expect "tree object with duplicate names: $names" ' |
| 283 | test_when_finished "remove_object \$blob" && |
| 284 | test_when_finished "remove_object \$tree" && |
| 285 | test_when_finished "remove_object \$badtree" && |
| 286 | blob=$(echo blob | git hash-object -w --stdin) && |
| 287 | printf "100644 blob %s\t%s\n" $blob x.2 >tree && |
| 288 | tree=$(git mktree <tree) && |
| 289 | for name in $names |
| 290 | do |
| 291 | case "$name" in |
| 292 | */) printf "040000 tree %s\t%s\n" $tree "${name%/}" ;; |
| 293 | *) printf "100644 blob %s\t%s\n" $blob "$name" ;; |
| 294 | esac |
| 295 | done >badtree && |
| 296 | badtree=$(git mktree <badtree) && |
| 297 | test_must_fail git fsck 2>out && |
| 298 | test_grep "$badtree" out && |
| 299 | test_grep "error in tree .*contains duplicate file entries" out |
| 300 | ' |
| 301 | } |
| 302 | |
| 303 | check_duplicate_names success x x.1 x/ |
| 304 | check_duplicate_names success x x.1.2 x.1/ x/ |
| 305 | check_duplicate_names success x x.1 x.1.2 x/ |
| 306 | |
| 307 | test_expect_success 'unparseable tree object' ' |
| 308 | test_oid_cache <<-\EOF && |
| 309 | junk sha1:twenty-bytes-of-junk |
| 310 | junk sha256:twenty-bytes-of-junk-twelve-more |
| 311 | EOF |
| 312 | |
| 313 | test_when_finished "git update-ref -d refs/heads/wrong" && |
| 314 | test_when_finished "remove_object \$tree_sha1" && |
| 315 | test_when_finished "remove_object \$commit_sha1" && |
| 316 | junk=$(test_oid junk) && |
| 317 | tree_sha1=$(printf "100644 \0$junk" | git hash-object -t tree --stdin -w --literally) && |
| 318 | commit_sha1=$(git commit-tree $tree_sha1) && |
| 319 | git update-ref refs/heads/wrong $commit_sha1 && |
| 320 | test_must_fail git fsck 2>out && |
| 321 | test_grep "error: empty filename in tree entry" out && |
| 322 | test_grep "$tree_sha1" out && |
| 323 | test_grep ! "fatal: empty filename in tree entry" out |
| 324 | ' |
| 325 | |
| 326 | test_expect_success PERL_TEST_HELPERS 'tree entry with type mismatch' ' |
| 327 | test_when_finished "remove_object \$blob" && |
| 328 | test_when_finished "remove_object \$tree" && |
| 329 | test_when_finished "remove_object \$commit" && |
| 330 | test_when_finished "git update-ref -d refs/heads/type_mismatch" && |
| 331 | blob=$(echo blob | git hash-object -w --stdin) && |
| 332 | blob_bin=$(echo $blob | hex2oct) && |
| 333 | tree=$( |
| 334 | printf "40000 dir\0${blob_bin}100644 file\0${blob_bin}" | |
| 335 | git hash-object -t tree --stdin -w --literally |
| 336 | ) && |
| 337 | commit=$(git commit-tree $tree) && |
| 338 | git update-ref refs/heads/type_mismatch $commit && |
| 339 | test_must_fail git fsck >out 2>&1 && |
| 340 | test_grep "is a blob, not a tree" out && |
| 341 | test_grep ! "dangling blob" out |
| 342 | ' |
| 343 | |
| 344 | test_expect_success PERL_TEST_HELPERS 'tree entry with bogus mode' ' |
| 345 | test_when_finished "remove_object \$blob" && |
| 346 | test_when_finished "remove_object \$tree" && |
| 347 | blob=$(echo blob | git hash-object -w --stdin) && |
| 348 | blob_oct=$(echo $blob | hex2oct) && |
| 349 | tree=$(printf "100000 foo\0${blob_oct}" | |
| 350 | git hash-object -t tree --stdin -w --literally) && |
| 351 | git fsck 2>err && |
| 352 | cat >expect <<-EOF && |
| 353 | warning in tree $tree: badFilemode: contains bad file modes |
| 354 | EOF |
| 355 | test_cmp expect err |
| 356 | ' |
| 357 | |
| 358 | test_expect_success 'tag pointing to nonexistent' ' |
| 359 | badoid=$(test_oid deadbeef) && |
| 360 | cat >invalid-tag <<-EOF && |
| 361 | object $badoid |
| 362 | type commit |
| 363 | tag invalid |
| 364 | tagger T A Gger <tagger@example.com> 1234567890 -0000 |
| 365 | |
| 366 | This is an invalid tag. |
| 367 | EOF |
| 368 | |
| 369 | tag=$(git hash-object -t tag -w --stdin <invalid-tag) && |
| 370 | test_when_finished "remove_object $tag" && |
| 371 | git update-ref refs/tags/invalid $tag && |
| 372 | test_when_finished "git update-ref -d refs/tags/invalid" && |
| 373 | test_must_fail git fsck --tags >out && |
| 374 | test_grep "broken link" out |
| 375 | ' |
| 376 | |
| 377 | test_expect_success 'tag pointing to something else than its type' ' |
| 378 | sha=$(echo blob | git hash-object -w --stdin) && |
| 379 | test_when_finished "remove_object $sha" && |
| 380 | cat >wrong-tag <<-EOF && |
| 381 | object $sha |
| 382 | type commit |
| 383 | tag wrong |
| 384 | tagger T A Gger <tagger@example.com> 1234567890 -0000 |
| 385 | |
| 386 | This is an invalid tag. |
| 387 | EOF |
| 388 | |
| 389 | tag=$(git hash-object -t tag -w --stdin <wrong-tag) && |
| 390 | test_when_finished "remove_object $tag" && |
| 391 | git update-ref refs/tags/wrong $tag && |
| 392 | test_when_finished "git update-ref -d refs/tags/wrong" && |
| 393 | test_must_fail git fsck --tags |
| 394 | ' |
| 395 | |
| 396 | test_expect_success 'tag with incorrect tag name & missing tagger' ' |
| 397 | sha=$(git rev-parse HEAD) && |
| 398 | cat >wrong-tag <<-EOF && |
| 399 | object $sha |
| 400 | type commit |
| 401 | tag wrong name format |
| 402 | |
| 403 | This is an invalid tag. |
| 404 | EOF |
| 405 | |
| 406 | tag=$(git hash-object --literally -t tag -w --stdin <wrong-tag) && |
| 407 | test_when_finished "remove_object $tag" && |
| 408 | git update-ref refs/tags/wrong $tag && |
| 409 | test_when_finished "git update-ref -d refs/tags/wrong" && |
| 410 | git fsck --tags 2>out && |
| 411 | |
| 412 | cat >expect <<-EOF && |
| 413 | warning in tag $tag: badTagName: invalid '\''tag'\'' name: wrong name format |
| 414 | warning in tag $tag: missingTaggerEntry: invalid format - expected '\''tagger'\'' line |
| 415 | EOF |
| 416 | test_cmp expect out |
| 417 | ' |
| 418 | |
| 419 | test_expect_success 'tag with bad tagger' ' |
| 420 | sha=$(git rev-parse HEAD) && |
| 421 | cat >wrong-tag <<-EOF && |
| 422 | object $sha |
| 423 | type commit |
| 424 | tag not-quite-wrong |
| 425 | tagger Bad Tagger Name |
| 426 | |
| 427 | This is an invalid tag. |
| 428 | EOF |
| 429 | |
| 430 | tag=$(git hash-object --literally -t tag -w --stdin <wrong-tag) && |
| 431 | test_when_finished "remove_object $tag" && |
| 432 | git update-ref refs/tags/wrong $tag && |
| 433 | test_when_finished "git update-ref -d refs/tags/wrong" && |
| 434 | test_must_fail git fsck --tags 2>out && |
| 435 | test_grep "error in tag .*: invalid author/committer" out |
| 436 | ' |
| 437 | |
| 438 | test_expect_success 'tag with NUL in header' ' |
| 439 | sha=$(git rev-parse HEAD) && |
| 440 | q_to_nul >tag-NUL-header <<-EOF && |
| 441 | object $sha |
| 442 | type commit |
| 443 | tag contains-Q-in-header |
| 444 | tagger T A Gger <tagger@example.com> 1234567890 -0000 |
| 445 | |
| 446 | This is an invalid tag. |
| 447 | EOF |
| 448 | |
| 449 | tag=$(git hash-object --literally -t tag -w --stdin <tag-NUL-header) && |
| 450 | test_when_finished "remove_object $tag" && |
| 451 | git update-ref refs/tags/wrong $tag && |
| 452 | test_when_finished "git update-ref -d refs/tags/wrong" && |
| 453 | test_must_fail git fsck --tags 2>out && |
| 454 | test_grep "error in tag $tag.*unterminated header: NUL at offset" out |
| 455 | ' |
| 456 | |
| 457 | test_expect_success 'tag accepts gpgsig header even if not validly signed' ' |
| 458 | test_oid_cache <<-\EOF && |
| 459 | header sha1:gpgsig-sha256 |
| 460 | header sha256:gpgsig |
| 461 | EOF |
| 462 | header=$(test_oid header) && |
| 463 | sha=$(git rev-parse HEAD) && |
| 464 | cat >good-tag <<-EOF && |
| 465 | object $sha |
| 466 | type commit |
| 467 | tag good |
| 468 | tagger T A Gger <tagger@example.com> 1234567890 -0000 |
| 469 | $header -----BEGIN PGP SIGNATURE----- |
| 470 | Not a valid signature |
| 471 | -----END PGP SIGNATURE----- |
| 472 | |
| 473 | This is a good tag. |
| 474 | EOF |
| 475 | |
| 476 | tag=$(git hash-object --literally -t tag -w --stdin <good-tag) && |
| 477 | test_when_finished "remove_object $tag" && |
| 478 | git update-ref refs/tags/good $tag && |
| 479 | test_when_finished "git update-ref -d refs/tags/good" && |
| 480 | git -c fsck.extraHeaderEntry=error fsck --tags |
| 481 | ' |
| 482 | |
| 483 | test_expect_success 'tag rejects invalid headers' ' |
| 484 | test_oid_cache <<-\EOF && |
| 485 | header sha1:gpgsig-sha256 |
| 486 | header sha256:gpgsig |
| 487 | EOF |
| 488 | header=$(test_oid header) && |
| 489 | sha=$(git rev-parse HEAD) && |
| 490 | cat >bad-tag <<-EOF && |
| 491 | object $sha |
| 492 | type commit |
| 493 | tag good |
| 494 | tagger T A Gger <tagger@example.com> 1234567890 -0000 |
| 495 | $header -----BEGIN PGP SIGNATURE----- |
| 496 | Not a valid signature |
| 497 | -----END PGP SIGNATURE----- |
| 498 | junk |
| 499 | |
| 500 | This is a bad tag with junk at the end of the headers. |
| 501 | EOF |
| 502 | |
| 503 | tag=$(git hash-object --literally -t tag -w --stdin <bad-tag) && |
| 504 | test_when_finished "remove_object $tag" && |
| 505 | git update-ref refs/tags/bad $tag && |
| 506 | test_when_finished "git update-ref -d refs/tags/bad" && |
| 507 | test_must_fail git -c fsck.extraHeaderEntry=error fsck --tags 2>out && |
| 508 | test_grep "error in tag $tag.*invalid format - extra header" out |
| 509 | ' |
| 510 | |
| 511 | test_expect_success 'cleaned up' ' |
| 512 | git fsck >actual 2>&1 && |
| 513 | test_must_be_empty actual |
| 514 | ' |
| 515 | |
| 516 | test_expect_success 'rev-list --verify-objects' ' |
| 517 | git rev-list --verify-objects --all >/dev/null 2>out && |
| 518 | test_must_be_empty out |
| 519 | ' |
| 520 | |
| 521 | test_expect_success 'rev-list --verify-objects with bad sha1' ' |
| 522 | sha=$(echo blob | git hash-object -w --stdin) && |
| 523 | old=$(test_oid_to_path $sha) && |
| 524 | new=$(dirname $old)/$(test_oid ff_2) && |
| 525 | sha="$(dirname $new)$(basename $new)" && |
| 526 | mv .git/objects/$old .git/objects/$new && |
| 527 | test_when_finished "remove_object $sha" && |
| 528 | git update-index --add --cacheinfo 100644 $sha foo && |
| 529 | test_when_finished "git read-tree -u --reset HEAD" && |
| 530 | tree=$(git write-tree) && |
| 531 | test_when_finished "remove_object $tree" && |
| 532 | cmt=$(echo bogus | git commit-tree $tree) && |
| 533 | test_when_finished "remove_object $cmt" && |
| 534 | git update-ref refs/heads/bogus $cmt && |
| 535 | test_when_finished "git update-ref -d refs/heads/bogus" && |
| 536 | |
| 537 | test_might_fail git rev-list --verify-objects refs/heads/bogus >/dev/null 2>out && |
| 538 | test_grep -q "error: hash mismatch $(dirname $new)$(test_oid ff_2)" out |
| 539 | ' |
| 540 | |
| 541 | # An actual bit corruption is more likely than swapped commits, but |
| 542 | # this provides an easy way to have commits which don't match their purported |
| 543 | # hashes, but which aren't so broken we can't read them at all. |
| 544 | test_expect_success 'rev-list --verify-objects notices swapped commits' ' |
| 545 | git init swapped-commits && |
| 546 | ( |
| 547 | cd swapped-commits && |
| 548 | test_commit one && |
| 549 | test_commit two && |
| 550 | one_oid=$(git rev-parse HEAD) && |
| 551 | two_oid=$(git rev-parse HEAD^) && |
| 552 | one=.git/objects/$(test_oid_to_path $one_oid) && |
| 553 | two=.git/objects/$(test_oid_to_path $two_oid) && |
| 554 | mv $one tmp && |
| 555 | mv $two $one && |
| 556 | mv tmp $two && |
| 557 | test_must_fail git rev-list --verify-objects HEAD |
| 558 | ) |
| 559 | ' |
| 560 | |
| 561 | test_expect_success 'set up repository with commit-graph' ' |
| 562 | git init corrupt-graph && |
| 563 | ( |
| 564 | cd corrupt-graph && |
| 565 | test_commit one && |
| 566 | test_commit two && |
| 567 | git commit-graph write --reachable |
| 568 | ) |
| 569 | ' |
| 570 | |
| 571 | corrupt_graph_obj () { |
| 572 | oid=$(git -C corrupt-graph rev-parse "$1") && |
| 573 | obj=corrupt-graph/.git/objects/$(test_oid_to_path $oid) && |
| 574 | test_when_finished 'mv backup $obj' && |
| 575 | mv $obj backup && |
| 576 | echo garbage >$obj |
| 577 | } |
| 578 | |
| 579 | test_expect_success 'rev-list --verify-objects with commit graph (tip)' ' |
| 580 | corrupt_graph_obj HEAD && |
| 581 | test_must_fail git -C corrupt-graph rev-list --verify-objects HEAD |
| 582 | ' |
| 583 | |
| 584 | test_expect_success 'rev-list --verify-objects with commit graph (parent)' ' |
| 585 | corrupt_graph_obj HEAD^ && |
| 586 | test_must_fail git -C corrupt-graph rev-list --verify-objects HEAD |
| 587 | ' |
| 588 | |
| 589 | test_expect_success 'force fsck to ignore double author' ' |
| 590 | git cat-file commit HEAD >basis && |
| 591 | sed "s/^author .*/&,&/" <basis | tr , \\n >multiple-authors && |
| 592 | new=$(git hash-object --literally -t commit -w --stdin <multiple-authors) && |
| 593 | test_when_finished "remove_object $new" && |
| 594 | git update-ref refs/heads/bogus "$new" && |
| 595 | test_when_finished "git update-ref -d refs/heads/bogus" && |
| 596 | test_must_fail git fsck && |
| 597 | git -c fsck.multipleAuthors=ignore fsck |
| 598 | ' |
| 599 | |
| 600 | _bz='\0' |
| 601 | _bzoid=$(printf $ZERO_OID | sed -e 's/00/\\0/g') |
| 602 | |
| 603 | test_expect_success 'fsck notices blob entry pointing to null sha1' ' |
| 604 | (git init null-blob && |
| 605 | cd null-blob && |
| 606 | sha=$(printf "100644 file$_bz$_bzoid" | |
| 607 | git hash-object --literally -w --stdin -t tree) && |
| 608 | git fsck 2>out && |
| 609 | test_grep "warning.*null sha1" out |
| 610 | ) |
| 611 | ' |
| 612 | |
| 613 | test_expect_success 'fsck notices submodule entry pointing to null sha1' ' |
| 614 | (git init null-commit && |
| 615 | cd null-commit && |
| 616 | sha=$(printf "160000 submodule$_bz$_bzoid" | |
| 617 | git hash-object --literally -w --stdin -t tree) && |
| 618 | git fsck 2>out && |
| 619 | test_grep "warning.*null sha1" out |
| 620 | ) |
| 621 | ' |
| 622 | |
| 623 | test_expect_success 'fsck notices excessively large tree entry name' ' |
| 624 | git init large-name && |
| 625 | ( |
| 626 | cd large-name && |
| 627 | test_commit a-long-name && |
| 628 | git -c fsck.largePathname=warn:10 fsck 2>out && |
| 629 | grep "warning.*large pathname" out |
| 630 | ) |
| 631 | ' |
| 632 | |
| 633 | while read name path pretty; do |
| 634 | while read mode type; do |
| 635 | : ${pretty:=$path} |
| 636 | test_expect_success "fsck notices $pretty as $type" ' |
| 637 | ( |
| 638 | git init $name-$type && |
| 639 | cd $name-$type && |
| 640 | git config core.protectNTFS false && |
| 641 | echo content >file && |
| 642 | git add file && |
| 643 | git commit -m base && |
| 644 | blob=$(git rev-parse :file) && |
| 645 | tree=$(git rev-parse HEAD^{tree}) && |
| 646 | value=$(eval "echo \$$type") && |
| 647 | printf "$mode $type %s\t%s" "$value" "$path" >bad && |
| 648 | bad_tree=$(git mktree <bad) && |
| 649 | git fsck 2>out && |
| 650 | test_grep "warning.*tree $bad_tree" out |
| 651 | )' |
| 652 | done <<-\EOF |
| 653 | 100644 blob |
| 654 | 040000 tree |
| 655 | EOF |
| 656 | done <<-EOF |
| 657 | dot . |
| 658 | dotdot .. |
| 659 | dotgit .git |
| 660 | dotgit-case .GIT |
| 661 | dotgit-unicode .gI${u200c}T .gI{u200c}T |
| 662 | dotgit-case2 .Git |
| 663 | git-tilde1 git~1 |
| 664 | dotgitdot .git. |
| 665 | dot-backslash-case .\\\\.GIT\\\\foobar |
| 666 | dotgit-case-backslash .git\\\\foobar |
| 667 | EOF |
| 668 | |
| 669 | test_expect_success 'fsck allows .Ňit' ' |
| 670 | ( |
| 671 | git init not-dotgit && |
| 672 | cd not-dotgit && |
| 673 | echo content >file && |
| 674 | git add file && |
| 675 | git commit -m base && |
| 676 | blob=$(git rev-parse :file) && |
| 677 | printf "100644 blob $blob\t.\\305\\207it" >tree && |
| 678 | tree=$(git mktree <tree) && |
| 679 | git fsck 2>err && |
| 680 | test_line_count = 0 err |
| 681 | ) |
| 682 | ' |
| 683 | |
| 684 | test_expect_success 'NUL in commit' ' |
| 685 | rm -fr nul-in-commit && |
| 686 | git init nul-in-commit && |
| 687 | ( |
| 688 | cd nul-in-commit && |
| 689 | git commit --allow-empty -m "initial commitQNUL after message" && |
| 690 | git cat-file commit HEAD >original && |
| 691 | q_to_nul <original >munged && |
| 692 | git hash-object --literally -w -t commit --stdin <munged >name && |
| 693 | git branch bad $(cat name) && |
| 694 | |
| 695 | test_must_fail git -c fsck.nulInCommit=error fsck 2>warn.1 && |
| 696 | test_grep nulInCommit warn.1 && |
| 697 | git fsck 2>warn.2 && |
| 698 | test_grep nulInCommit warn.2 |
| 699 | ) |
| 700 | ' |
| 701 | |
| 702 | # create a static test repo which is broken by omitting |
| 703 | # one particular object ($1, which is looked up via rev-parse |
| 704 | # in the new repository). |
| 705 | create_repo_missing () { |
| 706 | rm -rf missing && |
| 707 | git init missing && |
| 708 | ( |
| 709 | cd missing && |
| 710 | git commit -m one --allow-empty && |
| 711 | mkdir subdir && |
| 712 | echo content >subdir/file && |
| 713 | git add subdir/file && |
| 714 | git commit -m two && |
| 715 | unrelated=$(echo unrelated | git hash-object --stdin -w) && |
| 716 | git tag -m foo tag $unrelated && |
| 717 | sha1=$(git rev-parse --verify "$1") && |
| 718 | path=$(echo $sha1 | sed 's|..|&/|') && |
| 719 | rm .git/objects/$path |
| 720 | ) |
| 721 | } |
| 722 | |
| 723 | test_expect_success 'fsck notices missing blob' ' |
| 724 | create_repo_missing HEAD:subdir/file && |
| 725 | test_must_fail git -C missing fsck |
| 726 | ' |
| 727 | |
| 728 | test_expect_success 'fsck notices missing subtree' ' |
| 729 | create_repo_missing HEAD:subdir && |
| 730 | test_must_fail git -C missing fsck |
| 731 | ' |
| 732 | |
| 733 | test_expect_success 'fsck notices missing root tree' ' |
| 734 | create_repo_missing HEAD^{tree} && |
| 735 | test_must_fail git -C missing fsck |
| 736 | ' |
| 737 | |
| 738 | test_expect_success 'fsck notices missing parent' ' |
| 739 | create_repo_missing HEAD^ && |
| 740 | test_must_fail git -C missing fsck |
| 741 | ' |
| 742 | |
| 743 | test_expect_success 'fsck notices missing tagged object' ' |
| 744 | create_repo_missing tag^{blob} && |
| 745 | test_must_fail git -C missing fsck |
| 746 | ' |
| 747 | |
| 748 | test_expect_success 'fsck notices ref pointing to missing commit' ' |
| 749 | create_repo_missing HEAD && |
| 750 | test_must_fail git -C missing fsck |
| 751 | ' |
| 752 | |
| 753 | test_expect_success 'fsck notices ref pointing to missing tag' ' |
| 754 | create_repo_missing tag && |
| 755 | test_must_fail git -C missing fsck |
| 756 | ' |
| 757 | |
| 758 | test_expect_success 'fsck --connectivity-only' ' |
| 759 | rm -rf connectivity-only && |
| 760 | git init connectivity-only && |
| 761 | ( |
| 762 | cd connectivity-only && |
| 763 | touch empty && |
| 764 | git add empty && |
| 765 | test_commit empty && |
| 766 | |
| 767 | # Drop the index now; we want to be sure that we |
| 768 | # recursively notice the broken objects |
| 769 | # because they are reachable from refs, not because |
| 770 | # they are in the index. |
| 771 | rm -f .git/index && |
| 772 | |
| 773 | # corrupt the blob, but in a way that we can still identify |
| 774 | # its type. That lets us see that --connectivity-only is |
| 775 | # not actually looking at the contents, but leaves it |
| 776 | # free to examine the type if it chooses. |
| 777 | empty=.git/objects/$(test_oid_to_path $EMPTY_BLOB) && |
| 778 | blob=$(echo unrelated | git hash-object -w --stdin) && |
| 779 | mv -f $(sha1_file $blob) $empty && |
| 780 | |
| 781 | test_must_fail git fsck --strict && |
| 782 | git fsck --strict --connectivity-only && |
| 783 | tree=$(git rev-parse HEAD:) && |
| 784 | suffix=${tree#??} && |
| 785 | tree=.git/objects/${tree%$suffix}/$suffix && |
| 786 | rm -f $tree && |
| 787 | echo invalid >$tree && |
| 788 | test_must_fail git fsck --strict --connectivity-only |
| 789 | ) |
| 790 | ' |
| 791 | |
| 792 | test_expect_success 'fsck --connectivity-only with explicit head' ' |
| 793 | rm -rf connectivity-only && |
| 794 | git init connectivity-only && |
| 795 | ( |
| 796 | cd connectivity-only && |
| 797 | test_commit foo && |
| 798 | rm -f .git/index && |
| 799 | tree=$(git rev-parse HEAD^{tree}) && |
| 800 | remove_object $(git rev-parse HEAD:foo.t) && |
| 801 | test_must_fail git fsck --connectivity-only $tree |
| 802 | ) |
| 803 | ' |
| 804 | |
| 805 | test_expect_success 'fsck --name-objects' ' |
| 806 | rm -rf name-objects && |
| 807 | git init name-objects && |
| 808 | ( |
| 809 | cd name-objects && |
| 810 | git config core.logAllRefUpdates false && |
| 811 | test_commit julius caesar.t && |
| 812 | test_commit augustus44 && |
| 813 | test_commit caesar && |
| 814 | remove_object $(git rev-parse julius:caesar.t) && |
| 815 | tree=$(git rev-parse --verify julius:) && |
| 816 | git tag -d julius && |
| 817 | test_must_fail git fsck --name-objects >out && |
| 818 | test_grep "$tree (refs/tags/augustus44\\^:" out |
| 819 | ) |
| 820 | ' |
| 821 | |
| 822 | test_expect_success 'alternate objects are correctly blamed' ' |
| 823 | test_when_finished "rm -rf alt.git .git/objects/info/alternates" && |
| 824 | name=$(test_oid numeric) && |
| 825 | path=$(test_oid_to_path "$name") && |
| 826 | git init --bare alt.git && |
| 827 | echo "../../alt.git/objects" >.git/objects/info/alternates && |
| 828 | mkdir alt.git/objects/$(dirname $path) && |
| 829 | >alt.git/objects/$(dirname $path)/$(basename $path) && |
| 830 | test_must_fail git fsck >out 2>&1 && |
| 831 | test_grep alt.git out |
| 832 | ' |
| 833 | |
| 834 | test_expect_success 'fsck errors in packed objects' ' |
| 835 | git cat-file commit HEAD >basis && |
| 836 | sed "s/</one/" basis >one && |
| 837 | sed "s/</foo/" basis >two && |
| 838 | one=$(git hash-object --literally -t commit -w one) && |
| 839 | two=$(git hash-object --literally -t commit -w two) && |
| 840 | pack=$( |
| 841 | { |
| 842 | echo $one && |
| 843 | echo $two |
| 844 | } | git pack-objects .git/objects/pack/pack |
| 845 | ) && |
| 846 | test_when_finished "rm -f .git/objects/pack/pack-$pack.*" && |
| 847 | remove_object $one && |
| 848 | remove_object $two && |
| 849 | test_must_fail git fsck 2>out && |
| 850 | test_grep "error in commit $one.* - bad name" out && |
| 851 | test_grep "error in commit $two.* - bad name" out && |
| 852 | ! grep corrupt out |
| 853 | ' |
| 854 | |
| 855 | test_expect_success 'fsck handles multiple packfiles with big blobs' ' |
| 856 | test_when_finished "rm -rf repo" && |
| 857 | git init repo && |
| 858 | ( |
| 859 | cd repo && |
| 860 | |
| 861 | # We construct two packfiles with two objects in common and one |
| 862 | # object not in common. The objects in common can then be |
| 863 | # corrupted in one of the packfiles, respectively. The other |
| 864 | # objects that are unique to the packs are merely used to not |
| 865 | # have both packs contain the same data. |
| 866 | blob_one=$(test-tool genrandom one 200k | git hash-object -t blob -w --stdin) && |
| 867 | blob_two=$(test-tool genrandom two 200k | git hash-object -t blob -w --stdin) && |
| 868 | blob_three=$(test-tool genrandom three 200k | git hash-object -t blob -w --stdin) && |
| 869 | blob_four=$(test-tool genrandom four 200k | git hash-object -t blob -w --stdin) && |
| 870 | pack_one=$(printf "%s\n" "$blob_one" "$blob_two" "$blob_three" | git pack-objects .git/objects/pack/pack) && |
| 871 | pack_two=$(printf "%s\n" "$blob_two" "$blob_three" "$blob_four" | git pack-objects .git/objects/pack/pack) && |
| 872 | chmod a+w .git/objects/pack/pack-*.pack && |
| 873 | |
| 874 | # Corrupt blob two in the first pack. |
| 875 | git verify-pack -v .git/objects/pack/pack-$pack_one >objects && |
| 876 | offset_one=$(sed <objects -n "s/^$blob_two .* \(.*\)$/\1/p") && |
| 877 | printf "\0" | dd of=.git/objects/pack/pack-$pack_one.pack bs=1 conv=notrunc seek=$offset_one && |
| 878 | |
| 879 | # Corrupt blob three in the second pack. |
| 880 | git verify-pack -v .git/objects/pack/pack-$pack_two >objects && |
| 881 | offset_two=$(sed <objects -n "s/^$blob_three .* \(.*\)$/\1/p") && |
| 882 | printf "\0" | dd of=.git/objects/pack/pack-$pack_two.pack bs=1 conv=notrunc seek=$offset_two && |
| 883 | |
| 884 | # We now expect to see two failures for the corrupted objects, |
| 885 | # even though they exist in a non-corrupted form in the |
| 886 | # respective other pack. |
| 887 | test_must_fail git -c core.bigFileThreshold=100k fsck 2>err && |
| 888 | test_grep "unknown object type 0 at offset $offset_one in .git/objects/pack/pack-$pack_one.pack" err && |
| 889 | test_grep "unknown object type 0 at offset $offset_two in .git/objects/pack/pack-$pack_two.pack" err |
| 890 | ) |
| 891 | ' |
| 892 | |
| 893 | test_expect_success 'fsck fails on corrupt packfile' ' |
| 894 | hsh=$(git commit-tree -m mycommit HEAD^{tree}) && |
| 895 | pack=$(echo $hsh | git pack-objects .git/objects/pack/pack) && |
| 896 | |
| 897 | # Corrupt the first byte of the first object. (It contains 3 type bits, |
| 898 | # at least one of which is not zero, so setting the first byte to 0 is |
| 899 | # sufficient.) |
| 900 | chmod a+w .git/objects/pack/pack-$pack.pack && |
| 901 | printf "\0" | dd of=.git/objects/pack/pack-$pack.pack bs=1 conv=notrunc seek=12 && |
| 902 | |
| 903 | test_when_finished "rm -f .git/objects/pack/pack-$pack.*" && |
| 904 | remove_object $hsh && |
| 905 | test_must_fail git fsck 2>out && |
| 906 | test_grep "checksum mismatch" out |
| 907 | ' |
| 908 | |
| 909 | test_expect_success 'fsck finds problems in duplicate loose objects' ' |
| 910 | rm -rf broken-duplicate && |
| 911 | git init broken-duplicate && |
| 912 | ( |
| 913 | cd broken-duplicate && |
| 914 | test_commit duplicate && |
| 915 | # no "-d" here, so we end up with duplicates |
| 916 | git repack && |
| 917 | # now corrupt the loose copy |
| 918 | oid="$(git rev-parse HEAD)" && |
| 919 | file=$(sha1_file "$oid") && |
| 920 | rm "$file" && |
| 921 | echo broken >"$file" && |
| 922 | test_must_fail git fsck 2>err && |
| 923 | |
| 924 | cat >expect <<-EOF && |
| 925 | error: inflate: data stream error (incorrect header check) |
| 926 | error: unable to unpack header of $file |
| 927 | error: $oid: object corrupt or missing: $file |
| 928 | EOF |
| 929 | grep "^error: " err >actual && |
| 930 | test_cmp expect actual |
| 931 | ) |
| 932 | ' |
| 933 | |
| 934 | test_expect_success 'fsck detects trailing loose garbage (commit)' ' |
| 935 | git cat-file commit HEAD >basis && |
| 936 | echo bump-commit-sha1 >>basis && |
| 937 | commit=$(git hash-object -w -t commit basis) && |
| 938 | file=$(sha1_file $commit) && |
| 939 | test_when_finished "remove_object $commit" && |
| 940 | chmod +w "$file" && |
| 941 | echo garbage >>"$file" && |
| 942 | test_must_fail git fsck 2>out && |
| 943 | test_grep "garbage.*$commit" out |
| 944 | ' |
| 945 | |
| 946 | test_expect_success 'fsck detects trailing loose garbage (large blob)' ' |
| 947 | blob=$(echo trailing | git hash-object -w --stdin) && |
| 948 | file=$(sha1_file $blob) && |
| 949 | test_when_finished "remove_object $blob" && |
| 950 | chmod +w "$file" && |
| 951 | echo garbage >>"$file" && |
| 952 | test_must_fail git -c core.bigfilethreshold=5 fsck 2>out && |
| 953 | test_grep "garbage.*$blob" out |
| 954 | ' |
| 955 | |
| 956 | test_expect_success 'fsck detects truncated loose object' ' |
| 957 | # make it big enough that we know we will truncate in the data |
| 958 | # portion, not the header |
| 959 | test-tool genrandom truncate 4k >file && |
| 960 | blob=$(git hash-object -w file) && |
| 961 | file=$(sha1_file $blob) && |
| 962 | test_when_finished "remove_object $blob" && |
| 963 | test_copy_bytes 1024 <"$file" >tmp && |
| 964 | rm "$file" && |
| 965 | mv -f tmp "$file" && |
| 966 | |
| 967 | # check both regular and streaming code paths |
| 968 | test_must_fail git fsck 2>out && |
| 969 | test_grep corrupt.*$blob out && |
| 970 | |
| 971 | test_must_fail git -c core.bigfilethreshold=128 fsck 2>out && |
| 972 | test_grep corrupt.*$blob out |
| 973 | ' |
| 974 | |
| 975 | # for each of type, we have one version which is referenced by another object |
| 976 | # (and so while unreachable, not dangling), and another variant which really is |
| 977 | # dangling. |
| 978 | test_expect_success 'create dangling-object repository' ' |
| 979 | git init dangling && |
| 980 | ( |
| 981 | cd dangling && |
| 982 | blob=$(echo not-dangling | git hash-object -w --stdin) && |
| 983 | dblob=$(echo dangling | git hash-object -w --stdin) && |
| 984 | tree=$(printf "100644 blob %s\t%s\n" $blob one | git mktree) && |
| 985 | dtree=$(printf "100644 blob %s\t%s\n" $blob two | git mktree) && |
| 986 | commit=$(git commit-tree $tree) && |
| 987 | dcommit=$(git commit-tree -p $commit $tree) && |
| 988 | |
| 989 | cat >expect <<-EOF |
| 990 | dangling blob $dblob |
| 991 | dangling commit $dcommit |
| 992 | dangling tree $dtree |
| 993 | EOF |
| 994 | ) |
| 995 | ' |
| 996 | |
| 997 | test_expect_success 'fsck notices dangling objects' ' |
| 998 | ( |
| 999 | cd dangling && |
| 1000 | git fsck >actual && |
| 1001 | # the output order is non-deterministic, as it comes from a hash |
| 1002 | sort <actual >actual.sorted && |
| 1003 | test_cmp expect actual.sorted |
| 1004 | ) |
| 1005 | ' |
| 1006 | |
| 1007 | test_expect_success 'fsck --connectivity-only notices dangling objects' ' |
| 1008 | ( |
| 1009 | cd dangling && |
| 1010 | git fsck --connectivity-only >actual && |
| 1011 | # the output order is non-deterministic, as it comes from a hash |
| 1012 | sort <actual >actual.sorted && |
| 1013 | test_cmp expect actual.sorted |
| 1014 | ) |
| 1015 | ' |
| 1016 | |
| 1017 | test_expect_success 'fsck $name notices bogus $name' ' |
| 1018 | test_must_fail git fsck bogus && |
| 1019 | test_must_fail git fsck $ZERO_OID |
| 1020 | ' |
| 1021 | |
| 1022 | test_expect_success 'bogus head does not fallback to all heads' ' |
| 1023 | # set up a case that will cause a reachability complaint |
| 1024 | echo to-be-deleted >foo && |
| 1025 | git add foo && |
| 1026 | blob=$(git rev-parse :foo) && |
| 1027 | test_when_finished "git rm --cached foo" && |
| 1028 | remove_object $blob && |
| 1029 | test_must_fail git fsck $ZERO_OID >out 2>&1 && |
| 1030 | ! grep $blob out |
| 1031 | ' |
| 1032 | |
| 1033 | # Corrupt the checksum on the index. |
| 1034 | # Add 1 to the last byte in the SHA. |
| 1035 | corrupt_index_checksum () { |
| 1036 | perl -w -e ' |
| 1037 | use Fcntl ":seek"; |
| 1038 | open my $fh, "+<", ".git/index" or die "open: $!"; |
| 1039 | binmode $fh; |
| 1040 | seek $fh, -1, SEEK_END or die "seek: $!"; |
| 1041 | read $fh, my $in_byte, 1 or die "read: $!"; |
| 1042 | |
| 1043 | $in_value = unpack("C", $in_byte); |
| 1044 | $out_value = ($in_value + 1) & 255; |
| 1045 | |
| 1046 | $out_byte = pack("C", $out_value); |
| 1047 | |
| 1048 | seek $fh, -1, SEEK_END or die "seek: $!"; |
| 1049 | print $fh $out_byte; |
| 1050 | close $fh or die "close: $!"; |
| 1051 | ' |
| 1052 | } |
| 1053 | |
| 1054 | # Corrupt the checksum on the index and then |
| 1055 | # verify that only fsck notices. |
| 1056 | test_expect_success PERL_TEST_HELPERS 'detect corrupt index file in fsck' ' |
| 1057 | cp .git/index .git/index.backup && |
| 1058 | test_when_finished "mv .git/index.backup .git/index" && |
| 1059 | corrupt_index_checksum && |
| 1060 | test_must_fail git fsck --cache 2>errors && |
| 1061 | test_grep "bad index file" errors |
| 1062 | ' |
| 1063 | |
| 1064 | test_expect_success 'fsck error and recovery on invalid object type' ' |
| 1065 | git init --bare garbage-type && |
| 1066 | ( |
| 1067 | cd garbage-type && |
| 1068 | |
| 1069 | garbage_blob=$(loose_obj objects garbage </dev/null) && |
| 1070 | |
| 1071 | test_must_fail git fsck 2>err && |
| 1072 | grep -e "^error" -e "^fatal" err >errors && |
| 1073 | test_line_count = 2 errors && |
| 1074 | test_grep "unable to parse type from header .garbage" err && |
| 1075 | test_grep "$garbage_blob: object corrupt or missing:" err |
| 1076 | ) |
| 1077 | ' |
| 1078 | |
| 1079 | test_expect_success 'fsck error on gitattributes with excessive line lengths' ' |
| 1080 | blob=$(printf "pattern %02048d" 1 | git hash-object -w --stdin) && |
| 1081 | test_when_finished "remove_object $blob" && |
| 1082 | tree=$(printf "100644 blob %s\t%s\n" $blob .gitattributes | git mktree) && |
| 1083 | test_when_finished "remove_object $tree" && |
| 1084 | cat >expected <<-EOF && |
| 1085 | error in blob $blob: gitattributesLineLength: .gitattributes has too long lines to parse |
| 1086 | EOF |
| 1087 | test_must_fail git fsck --no-dangling >actual 2>&1 && |
| 1088 | test_cmp expected actual |
| 1089 | ' |
| 1090 | |
| 1091 | test_expect_success 'fsck error on gitattributes with excessive size' ' |
| 1092 | blob=$(test-tool genzeros $((100 * 1024 * 1024 + 1)) | git hash-object -w --stdin) && |
| 1093 | test_when_finished "remove_object $blob" && |
| 1094 | tree=$(printf "100644 blob %s\t%s\n" $blob .gitattributes | git mktree) && |
| 1095 | test_when_finished "remove_object $tree" && |
| 1096 | cat >expected <<-EOF && |
| 1097 | error in blob $blob: gitattributesLarge: .gitattributes too large to parse |
| 1098 | EOF |
| 1099 | test_must_fail git fsck --no-dangling >actual 2>&1 && |
| 1100 | test_cmp expected actual |
| 1101 | ' |
| 1102 | |
| 1103 | test_expect_success 'fsck detects problems in worktree index' ' |
| 1104 | test_when_finished "git worktree remove -f wt" && |
| 1105 | git worktree add wt && |
| 1106 | |
| 1107 | echo "this will be removed to break the worktree index" >wt/file && |
| 1108 | git -C wt add file && |
| 1109 | blob=$(git -C wt rev-parse :file) && |
| 1110 | remove_object $blob && |
| 1111 | |
| 1112 | test_must_fail git fsck --name-objects >actual 2>&1 && |
| 1113 | cat >expect <<-EOF && |
| 1114 | missing blob $blob (.git/worktrees/wt/index:file) |
| 1115 | EOF |
| 1116 | test_cmp expect actual |
| 1117 | ' |
| 1118 | |
| 1119 | test_expect_success 'fsck reports problems in current worktree index without filename' ' |
| 1120 | test_when_finished "rm -f .git/index && git read-tree HEAD" && |
| 1121 | echo "this object will be removed to break current worktree index" >file && |
| 1122 | git add file && |
| 1123 | blob=$(git rev-parse :file) && |
| 1124 | remove_object $blob && |
| 1125 | |
| 1126 | test_must_fail git fsck --name-objects >actual 2>&1 && |
| 1127 | cat >expect <<-EOF && |
| 1128 | missing blob $blob (:file) |
| 1129 | EOF |
| 1130 | test_cmp expect actual |
| 1131 | ' |
| 1132 | |
| 1133 | test_done |