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 test_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 test_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 test_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 test_expect_success 'rev-list --verify-objects with truncated loose blob' '
542 git init truncated-blob &&
543 (
544 cd truncated-blob &&
545 blob=$(test-tool genrandom one 5k | git hash-object -t blob -w --stdin) &&
546 obj=.git/objects/$(test_oid_to_path $blob) &&
547
548 # Truncate the loose blob such that its header can still be
549 # parsed, but reading the object data fails mid-stream.
550 test_copy_bytes 64 <"$obj" >obj.tmp &&
551 mv obj.tmp "$obj" &&
552
553 test_must_fail git rev-list --verify-objects "$blob" 2>err &&
554 test_grep "hash mismatch" err
555 )
556 '
557
558 # An actual bit corruption is more likely than swapped commits, but
559 # this provides an easy way to have commits which don't match their purported
560 # hashes, but which aren't so broken we can't read them at all.
561 test_expect_success 'rev-list --verify-objects notices swapped commits' '
562 git init swapped-commits &&
563 (
564 cd swapped-commits &&
565 test_commit one &&
566 test_commit two &&
567 one_oid=$(git rev-parse HEAD) &&
568 two_oid=$(git rev-parse HEAD^) &&
569 one=.git/objects/$(test_oid_to_path $one_oid) &&
570 two=.git/objects/$(test_oid_to_path $two_oid) &&
571 mv $one tmp &&
572 mv $two $one &&
573 mv tmp $two &&
574 test_must_fail git rev-list --verify-objects HEAD
575 )
576 '
577
578 test_expect_success 'set up repository with commit-graph' '
579 git init corrupt-graph &&
580 (
581 cd corrupt-graph &&
582 test_commit one &&
583 test_commit two &&
584 git commit-graph write --reachable
585 )
586 '
587
588 corrupt_graph_obj () {
589 oid=$(git -C corrupt-graph rev-parse "$1") &&
590 obj=corrupt-graph/.git/objects/$(test_oid_to_path $oid) &&
591 test_when_finished 'mv backup $obj' &&
592 mv $obj backup &&
593 echo garbage >$obj
594 }
595
596 test_expect_success 'rev-list --verify-objects with commit graph (tip)' '
597 corrupt_graph_obj HEAD &&
598 test_must_fail git -C corrupt-graph rev-list --verify-objects HEAD
599 '
600
601 test_expect_success 'rev-list --verify-objects with commit graph (parent)' '
602 corrupt_graph_obj HEAD^ &&
603 test_must_fail git -C corrupt-graph rev-list --verify-objects HEAD
604 '
605
606 test_expect_success 'force fsck to ignore double author' '
607 git cat-file commit HEAD >basis &&
608 sed "s/^author .*/&,&/" <basis | tr , \\n >multiple-authors &&
609 new=$(git hash-object --literally -t commit -w --stdin <multiple-authors) &&
610 test_when_finished "remove_object $new" &&
611 git update-ref refs/heads/bogus "$new" &&
612 test_when_finished "git update-ref -d refs/heads/bogus" &&
613 test_must_fail git fsck &&
614 git -c fsck.multipleAuthors=ignore fsck
615 '
616
617 _bz='\0'
618 _bzoid=$(printf $ZERO_OID | sed -e 's/00/\\0/g')
619
620 test_expect_success 'fsck notices blob entry pointing to null sha1' '
621 (git init null-blob &&
622 cd null-blob &&
623 sha=$(printf "100644 file$_bz$_bzoid" |
624 git hash-object --literally -w --stdin -t tree) &&
625 git fsck 2>out &&
626 test_grep "warning.*null sha1" out
627 )
628 '
629
630 test_expect_success 'fsck notices submodule entry pointing to null sha1' '
631 (git init null-commit &&
632 cd null-commit &&
633 sha=$(printf "160000 submodule$_bz$_bzoid" |
634 git hash-object --literally -w --stdin -t tree) &&
635 git fsck 2>out &&
636 test_grep "warning.*null sha1" out
637 )
638 '
639
640 test_expect_success 'fsck notices excessively large tree entry name' '
641 git init large-name &&
642 (
643 cd large-name &&
644 test_commit a-long-name &&
645 git -c fsck.largePathname=warn:10 fsck 2>out &&
646 test_grep "warning.*large pathname" out
647 )
648 '
649
650 while read name path pretty; do
651 while read mode type; do
652 : ${pretty:=$path}
653 test_expect_success "fsck notices $pretty as $type" '
654 (
655 git init $name-$type &&
656 cd $name-$type &&
657 git config core.protectNTFS false &&
658 echo content >file &&
659 git add file &&
660 git commit -m base &&
661 blob=$(git rev-parse :file) &&
662 tree=$(git rev-parse HEAD^{tree}) &&
663 value=$(eval "echo \$$type") &&
664 printf "$mode $type %s\t%s" "$value" "$path" >bad &&
665 bad_tree=$(git mktree <bad) &&
666 git fsck 2>out &&
667 test_grep "warning.*tree $bad_tree" out
668 )'
669 done <<-\EOF
670 100644 blob
671 040000 tree
672 EOF
673 done <<-EOF
674 dot .
675 dotdot ..
676 dotgit .git
677 dotgit-case .GIT
678 dotgit-unicode .gI${u200c}T .gI{u200c}T
679 dotgit-case2 .Git
680 git-tilde1 git~1
681 dotgitdot .git.
682 dot-backslash-case .\\\\.GIT\\\\foobar
683 dotgit-case-backslash .git\\\\foobar
684 EOF
685
686 test_expect_success 'fsck allows .Ňit' '
687 (
688 git init not-dotgit &&
689 cd not-dotgit &&
690 echo content >file &&
691 git add file &&
692 git commit -m base &&
693 blob=$(git rev-parse :file) &&
694 printf "100644 blob $blob\t.\\305\\207it" >tree &&
695 tree=$(git mktree <tree) &&
696 git fsck 2>err &&
697 test_line_count = 0 err
698 )
699 '
700
701 test_expect_success 'NUL in commit' '
702 rm -fr nul-in-commit &&
703 git init nul-in-commit &&
704 (
705 cd nul-in-commit &&
706 git commit --allow-empty -m "initial commitQNUL after message" &&
707 git cat-file commit HEAD >original &&
708 q_to_nul <original >munged &&
709 git hash-object --literally -w -t commit --stdin <munged >name &&
710 git branch bad $(cat name) &&
711
712 test_must_fail git -c fsck.nulInCommit=error fsck 2>warn.1 &&
713 test_grep nulInCommit warn.1 &&
714 git fsck 2>warn.2 &&
715 test_grep nulInCommit warn.2
716 )
717 '
718
719 # create a static test repo which is broken by omitting
720 # one particular object ($1, which is looked up via rev-parse
721 # in the new repository).
722 create_repo_missing () {
723 rm -rf missing &&
724 git init missing &&
725 (
726 cd missing &&
727 git commit -m one --allow-empty &&
728 mkdir subdir &&
729 echo content >subdir/file &&
730 git add subdir/file &&
731 git commit -m two &&
732 unrelated=$(echo unrelated | git hash-object --stdin -w) &&
733 git tag -m foo tag $unrelated &&
734 sha1=$(git rev-parse --verify "$1") &&
735 path=$(echo $sha1 | sed 's|..|&/|') &&
736 rm .git/objects/$path
737 )
738 }
739
740 test_expect_success 'fsck notices missing blob' '
741 create_repo_missing HEAD:subdir/file &&
742 test_must_fail git -C missing fsck
743 '
744
745 test_expect_success 'fsck notices missing subtree' '
746 create_repo_missing HEAD:subdir &&
747 test_must_fail git -C missing fsck
748 '
749
750 test_expect_success 'fsck notices missing root tree' '
751 create_repo_missing HEAD^{tree} &&
752 test_must_fail git -C missing fsck
753 '
754
755 test_expect_success 'fsck notices missing parent' '
756 create_repo_missing HEAD^ &&
757 test_must_fail git -C missing fsck
758 '
759
760 test_expect_success 'fsck notices missing tagged object' '
761 create_repo_missing tag^{blob} &&
762 test_must_fail git -C missing fsck
763 '
764
765 test_expect_success 'fsck notices ref pointing to missing commit' '
766 create_repo_missing HEAD &&
767 test_must_fail git -C missing fsck
768 '
769
770 test_expect_success 'fsck notices ref pointing to missing tag' '
771 create_repo_missing tag &&
772 test_must_fail git -C missing fsck
773 '
774
775 test_expect_success 'fsck --connectivity-only' '
776 rm -rf connectivity-only &&
777 git init connectivity-only &&
778 (
779 cd connectivity-only &&
780 touch empty &&
781 git add empty &&
782 test_commit empty &&
783
784 # Drop the index now; we want to be sure that we
785 # recursively notice the broken objects
786 # because they are reachable from refs, not because
787 # they are in the index.
788 rm -f .git/index &&
789
790 # corrupt the blob, but in a way that we can still identify
791 # its type. That lets us see that --connectivity-only is
792 # not actually looking at the contents, but leaves it
793 # free to examine the type if it chooses.
794 empty=.git/objects/$(test_oid_to_path $EMPTY_BLOB) &&
795 blob=$(echo unrelated | git hash-object -w --stdin) &&
796 mv -f $(sha1_file $blob) $empty &&
797
798 test_must_fail git fsck --strict &&
799 git fsck --strict --connectivity-only &&
800 tree=$(git rev-parse HEAD:) &&
801 suffix=${tree#??} &&
802 tree=.git/objects/${tree%$suffix}/$suffix &&
803 rm -f $tree &&
804 echo invalid >$tree &&
805 test_must_fail git fsck --strict --connectivity-only
806 )
807 '
808
809 test_expect_success 'fsck --connectivity-only with explicit head' '
810 rm -rf connectivity-only &&
811 git init connectivity-only &&
812 (
813 cd connectivity-only &&
814 test_commit foo &&
815 rm -f .git/index &&
816 tree=$(git rev-parse HEAD^{tree}) &&
817 remove_object $(git rev-parse HEAD:foo.t) &&
818 test_must_fail git fsck --connectivity-only $tree
819 )
820 '
821
822 test_expect_success 'fsck --name-objects' '
823 rm -rf name-objects &&
824 git init name-objects &&
825 (
826 cd name-objects &&
827 git config core.logAllRefUpdates false &&
828 test_commit julius caesar.t &&
829 test_commit augustus44 &&
830 test_commit caesar &&
831 remove_object $(git rev-parse julius:caesar.t) &&
832 tree=$(git rev-parse --verify julius:) &&
833 git tag -d julius &&
834 test_must_fail git fsck --name-objects >out &&
835 test_grep "$tree (refs/tags/augustus44\\^:" out
836 )
837 '
838
839 test_expect_success 'alternate objects are correctly blamed' '
840 test_when_finished "rm -rf alt.git .git/objects/info/alternates" &&
841 name=$(test_oid numeric) &&
842 path=$(test_oid_to_path "$name") &&
843 git init --bare alt.git &&
844 echo "../../alt.git/objects" >.git/objects/info/alternates &&
845 mkdir alt.git/objects/$(dirname $path) &&
846 >alt.git/objects/$(dirname $path)/$(basename $path) &&
847 test_must_fail git fsck >out 2>&1 &&
848 test_grep alt.git out
849 '
850
851 test_expect_success 'fsck errors in packed objects' '
852 git cat-file commit HEAD >basis &&
853 sed "s/</one/" basis >one &&
854 sed "s/</foo/" basis >two &&
855 one=$(git hash-object --literally -t commit -w one) &&
856 two=$(git hash-object --literally -t commit -w two) &&
857 pack=$(
858 {
859 echo $one &&
860 echo $two
861 } | git pack-objects .git/objects/pack/pack
862 ) &&
863 test_when_finished "rm -f .git/objects/pack/pack-$pack.*" &&
864 remove_object $one &&
865 remove_object $two &&
866 test_must_fail git fsck 2>out &&
867 test_grep "error in commit $one.* - bad name" out &&
868 test_grep "error in commit $two.* - bad name" out &&
869 test_grep ! corrupt out
870 '
871
872 test_expect_success 'fsck handles multiple packfiles with big blobs' '
873 test_when_finished "rm -rf repo" &&
874 git init repo &&
875 (
876 cd repo &&
877
878 # We construct two packfiles with two objects in common and one
879 # object not in common. The objects in common can then be
880 # corrupted in one of the packfiles, respectively. The other
881 # objects that are unique to the packs are merely used to not
882 # have both packs contain the same data.
883 blob_one=$(test-tool genrandom one 200k | git hash-object -t blob -w --stdin) &&
884 blob_two=$(test-tool genrandom two 200k | git hash-object -t blob -w --stdin) &&
885 blob_three=$(test-tool genrandom three 200k | git hash-object -t blob -w --stdin) &&
886 blob_four=$(test-tool genrandom four 200k | git hash-object -t blob -w --stdin) &&
887 pack_one=$(printf "%s\n" "$blob_one" "$blob_two" "$blob_three" | git pack-objects .git/objects/pack/pack) &&
888 pack_two=$(printf "%s\n" "$blob_two" "$blob_three" "$blob_four" | git pack-objects .git/objects/pack/pack) &&
889 chmod a+w .git/objects/pack/pack-*.pack &&
890
891 # Corrupt blob two in the first pack.
892 git verify-pack -v .git/objects/pack/pack-$pack_one >objects &&
893 offset_one=$(sed <objects -n "s/^$blob_two .* \(.*\)$/\1/p") &&
894 printf "\0" | dd of=.git/objects/pack/pack-$pack_one.pack bs=1 conv=notrunc seek=$offset_one &&
895
896 # Corrupt blob three in the second pack.
897 git verify-pack -v .git/objects/pack/pack-$pack_two >objects &&
898 offset_two=$(sed <objects -n "s/^$blob_three .* \(.*\)$/\1/p") &&
899 printf "\0" | dd of=.git/objects/pack/pack-$pack_two.pack bs=1 conv=notrunc seek=$offset_two &&
900
901 # We now expect to see two failures for the corrupted objects,
902 # even though they exist in a non-corrupted form in the
903 # respective other pack.
904 test_must_fail git -c core.bigFileThreshold=100k fsck 2>err &&
905 test_grep "unknown object type 0 at offset $offset_one in .git/objects/pack/pack-$pack_one.pack" err &&
906 test_grep "unknown object type 0 at offset $offset_two in .git/objects/pack/pack-$pack_two.pack" err
907 )
908 '
909
910 test_expect_success 'fsck fails on corrupt packfile' '
911 hsh=$(git commit-tree -m mycommit HEAD^{tree}) &&
912 pack=$(echo $hsh | git pack-objects .git/objects/pack/pack) &&
913
914 # Corrupt the first byte of the first object. (It contains 3 type bits,
915 # at least one of which is not zero, so setting the first byte to 0 is
916 # sufficient.)
917 chmod a+w .git/objects/pack/pack-$pack.pack &&
918 printf "\0" | dd of=.git/objects/pack/pack-$pack.pack bs=1 conv=notrunc seek=12 &&
919
920 test_when_finished "rm -f .git/objects/pack/pack-$pack.*" &&
921 remove_object $hsh &&
922 test_must_fail git fsck 2>out &&
923 test_grep "checksum mismatch" out
924 '
925
926 test_expect_success 'fsck finds problems in duplicate loose objects' '
927 rm -rf broken-duplicate &&
928 git init broken-duplicate &&
929 (
930 cd broken-duplicate &&
931 test_commit duplicate &&
932 # no "-d" here, so we end up with duplicates
933 git repack &&
934 # now corrupt the loose copy
935 oid="$(git rev-parse HEAD)" &&
936 file=$(sha1_file "$oid") &&
937 rm "$file" &&
938 echo broken >"$file" &&
939 test_must_fail git fsck 2>err &&
940
941 cat >expect <<-EOF &&
942 error: inflate: data stream error (incorrect header check)
943 error: unable to unpack header of $file
944 error: $oid: object corrupt or missing: $file
945 EOF
946 grep "^error: " err >actual &&
947 test_cmp expect actual
948 )
949 '
950
951 test_expect_success 'fsck detects trailing loose garbage (commit)' '
952 git cat-file commit HEAD >basis &&
953 echo bump-commit-sha1 >>basis &&
954 commit=$(git hash-object -w -t commit basis) &&
955 file=$(sha1_file $commit) &&
956 test_when_finished "remove_object $commit" &&
957 chmod +w "$file" &&
958 echo garbage >>"$file" &&
959 test_must_fail git fsck 2>out &&
960 test_grep "garbage.*$commit" out
961 '
962
963 test_expect_success 'fsck detects trailing loose garbage (large blob)' '
964 blob=$(echo trailing | git hash-object -w --stdin) &&
965 file=$(sha1_file $blob) &&
966 test_when_finished "remove_object $blob" &&
967 chmod +w "$file" &&
968 echo garbage >>"$file" &&
969 test_must_fail git -c core.bigfilethreshold=5 fsck 2>out &&
970 test_grep "garbage.*$blob" out
971 '
972
973 test_expect_success 'fsck detects truncated loose object' '
974 # make it big enough that we know we will truncate in the data
975 # portion, not the header
976 test-tool genrandom truncate 4k >file &&
977 blob=$(git hash-object -w file) &&
978 file=$(sha1_file $blob) &&
979 test_when_finished "remove_object $blob" &&
980 test_copy_bytes 1024 <"$file" >tmp &&
981 rm "$file" &&
982 mv -f tmp "$file" &&
983
984 # check both regular and streaming code paths
985 test_must_fail git fsck 2>out &&
986 test_grep corrupt.*$blob out &&
987
988 test_must_fail git -c core.bigfilethreshold=128 fsck 2>out &&
989 test_grep corrupt.*$blob out
990 '
991
992 # for each of type, we have one version which is referenced by another object
993 # (and so while unreachable, not dangling), and another variant which really is
994 # dangling.
995 test_expect_success 'create dangling-object repository' '
996 git init dangling &&
997 (
998 cd dangling &&
999 blob=$(echo not-dangling | git hash-object -w --stdin) &&
1000 dblob=$(echo dangling | git hash-object -w --stdin) &&
1001 tree=$(printf "100644 blob %s\t%s\n" $blob one | git mktree) &&
1002 dtree=$(printf "100644 blob %s\t%s\n" $blob two | git mktree) &&
1003 commit=$(git commit-tree $tree) &&
1004 dcommit=$(git commit-tree -p $commit $tree) &&
1005
1006 cat >expect <<-EOF
1007 dangling blob $dblob
1008 dangling commit $dcommit
1009 dangling tree $dtree
1010 EOF
1011 )
1012 '
1013
1014 test_expect_success 'fsck notices dangling objects' '
1015 (
1016 cd dangling &&
1017 git fsck >actual &&
1018 # the output order is non-deterministic, as it comes from a hash
1019 sort <actual >actual.sorted &&
1020 test_cmp expect actual.sorted
1021 )
1022 '
1023
1024 test_expect_success 'fsck --connectivity-only notices dangling objects' '
1025 (
1026 cd dangling &&
1027 git fsck --connectivity-only >actual &&
1028 # the output order is non-deterministic, as it comes from a hash
1029 sort <actual >actual.sorted &&
1030 test_cmp expect actual.sorted
1031 )
1032 '
1033
1034 test_expect_success 'fsck $name notices bogus $name' '
1035 test_must_fail git fsck bogus &&
1036 test_must_fail git fsck $ZERO_OID
1037 '
1038
1039 test_expect_success 'bogus head does not fallback to all heads' '
1040 # set up a case that will cause a reachability complaint
1041 echo to-be-deleted >foo &&
1042 git add foo &&
1043 blob=$(git rev-parse :foo) &&
1044 test_when_finished "git rm --cached foo" &&
1045 remove_object $blob &&
1046 test_must_fail git fsck $ZERO_OID >out 2>&1 &&
1047 test_grep ! $blob out
1048 '
1049
1050 # Corrupt the checksum on the index.
1051 # Add 1 to the last byte in the SHA.
1052 corrupt_index_checksum () {
1053 perl -w -e '
1054 use Fcntl ":seek";
1055 open my $fh, "+<", ".git/index" or die "open: $!";
1056 binmode $fh;
1057 seek $fh, -1, SEEK_END or die "seek: $!";
1058 read $fh, my $in_byte, 1 or die "read: $!";
1059
1060 $in_value = unpack("C", $in_byte);
1061 $out_value = ($in_value + 1) & 255;
1062
1063 $out_byte = pack("C", $out_value);
1064
1065 seek $fh, -1, SEEK_END or die "seek: $!";
1066 print $fh $out_byte;
1067 close $fh or die "close: $!";
1068 '
1069 }
1070
1071 # Corrupt the checksum on the index and then
1072 # verify that only fsck notices.
1073 test_expect_success PERL_TEST_HELPERS 'detect corrupt index file in fsck' '
1074 cp .git/index .git/index.backup &&
1075 test_when_finished "mv .git/index.backup .git/index" &&
1076 corrupt_index_checksum &&
1077 test_must_fail git fsck --cache 2>errors &&
1078 test_grep "bad index file" errors
1079 '
1080
1081 test_expect_success 'fsck error and recovery on invalid object type' '
1082 git init --bare garbage-type &&
1083 (
1084 cd garbage-type &&
1085
1086 garbage_blob=$(loose_obj objects garbage </dev/null) &&
1087
1088 test_must_fail git fsck 2>err &&
1089 grep -e "^error" -e "^fatal" err >errors &&
1090 test_line_count = 2 errors &&
1091 test_grep "unable to parse type from header .garbage" err &&
1092 test_grep "$garbage_blob: object corrupt or missing:" err
1093 )
1094 '
1095
1096 test_expect_success 'fsck error on gitattributes with excessive line lengths' '
1097 blob=$(printf "pattern %02048d" 1 | git hash-object -w --stdin) &&
1098 test_when_finished "remove_object $blob" &&
1099 tree=$(printf "100644 blob %s\t%s\n" $blob .gitattributes | git mktree) &&
1100 test_when_finished "remove_object $tree" &&
1101 cat >expected <<-EOF &&
1102 error in blob $blob: gitattributesLineLength: .gitattributes has too long lines to parse
1103 EOF
1104 test_must_fail git fsck --no-dangling >actual 2>&1 &&
1105 test_cmp expected actual
1106 '
1107
1108 test_expect_success 'fsck error on gitattributes with excessive size' '
1109 blob=$(test-tool genzeros $((100 * 1024 * 1024 + 1)) | git hash-object -w --stdin) &&
1110 test_when_finished "remove_object $blob" &&
1111 tree=$(printf "100644 blob %s\t%s\n" $blob .gitattributes | git mktree) &&
1112 test_when_finished "remove_object $tree" &&
1113 cat >expected <<-EOF &&
1114 error in blob $blob: gitattributesLarge: .gitattributes too large to parse
1115 EOF
1116 test_must_fail git fsck --no-dangling >actual 2>&1 &&
1117 test_cmp expected actual
1118 '
1119
1120 test_expect_success 'fsck detects problems in worktree index' '
1121 test_when_finished "git worktree remove -f wt" &&
1122 git worktree add wt &&
1123
1124 echo "this will be removed to break the worktree index" >wt/file &&
1125 git -C wt add file &&
1126 blob=$(git -C wt rev-parse :file) &&
1127 remove_object $blob &&
1128
1129 test_must_fail git fsck --name-objects >actual 2>&1 &&
1130 cat >expect <<-EOF &&
1131 missing blob $blob (.git/worktrees/wt/index:file)
1132 EOF
1133 test_cmp expect actual
1134 '
1135
1136 test_expect_success 'fsck reports problems in current worktree index without filename' '
1137 test_when_finished "rm -f .git/index && git read-tree HEAD" &&
1138 echo "this object will be removed to break current worktree index" >file &&
1139 git add file &&
1140 blob=$(git rev-parse :file) &&
1141 remove_object $blob &&
1142
1143 test_must_fail git fsck --name-objects >actual 2>&1 &&
1144 cat >expect <<-EOF &&
1145 missing blob $blob (:file)
1146 EOF
1147 test_cmp expect actual
1148 '
1149
1150 test_done