Raw
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