81
test_cmp expect actual
82
'
83
84
+test_expect_success 'core.diffhunks=false disables lookups' '
85
+ warm &&
86
+ git -c core.diffhunks=false blame --show-stats file.txt >out 2>&1 &&
87
+ test_grep "num precomputed hits: 0" out
88
+'
89
+
90
+# Writing seeds from the current store and merges into it, so a later
91
+# warming run keeps the entries an earlier one recorded rather than
92
+# rebuilding. Warm one pair, then a different pair, and confirm the first
93
+# is still served.
94
+test_expect_success 'a later warming run preserves earlier entries' '
95
+ git init incr &&
96
+ (
97
+ cd incr &&
98
+ test_commit a1 f.txt "1" &&
99
+ test_commit a2 f.txt "1
100
+2" &&
101
+ test_commit a3 f.txt "1
102
+2
103
+3" &&
104
+ GIT_DIFF_HUNKS_WRITE=1 git diff --stat a1 a2 >/dev/null &&
105
+ git diff-hunks verify &&
106
+ GIT_DIFF_HUNKS_WRITE=1 git diff --stat a2 a3 >/dev/null &&
107
+ git diff-hunks verify &&
108
+
109
+ # Blaming as of a2 diffs the a1..a2 pair. If seeding had
110
+ # dropped it when the a2..a3 pair was warmed, this would
111
+ # report zero precomputed hits.
112
+ git blame --show-stats a2 -- f.txt >out 2>&1 &&
113
+ test_grep "num precomputed hits: [1-9]" out &&
114
+
115
+ # The second warm ADDED the a2..a3 pair; blaming a3 diffs
116
+ # both a2..a3 and a1..a2, so a hit on each shows the store
117
+ # gained the new pair while keeping the earlier one.
118
+ git blame --show-stats a3 -- f.txt >out3 2>&1 &&
119
+ test_grep "num precomputed hits: 2" out3 &&
120
+
121
+ no_store log --stat >expect &&
122
+ git log --stat >actual &&
123
+ test_cmp expect actual
124
+ )
125
+'
126
+
127
test_expect_success 'log --stat matches with and without the store' '
128
no_store log --stat >expect &&
129
warm &&
254
test_cmp expect actual
255
'
256
257
+# One warm serves both diffstat and blame: the blob pairs a blame
258
+# walks are the same parent-child pairs the diffstat warm recorded.
259
+test_expect_success 'a single warming run serves both blame and diffstat' '
260
+ warm &&
261
+ git blame --show-stats file.txt >out 2>&1 &&
262
+ test_grep "num precomputed hits: [1-9][0-9]*" out
263
+'
264
+
265
# The diffstat read path produces identical output on a hit or a miss, so
266
# it emits a trace2 "read-hits" count to prove it consulted the store.
267
test_expect_success 'diffstat consults the store (trace shows read hits)' '
272
test_grep ! read-hits trace_off.json
273
'
274
275
+test_expect_success 'blame matches with and without the store' '
276
+ no_store blame file.txt >expect &&
277
+ warm &&
278
+ git blame file.txt >actual &&
279
+ test_cmp expect actual
280
+'
281
+
282
+test_expect_success 'blame --porcelain and --incremental match' '
283
+ no_store blame --porcelain file.txt >expect_p &&
284
+ no_store blame --incremental file.txt >expect_i &&
285
+ warm &&
286
+ git blame --porcelain file.txt >got_p &&
287
+ git blame --incremental file.txt >got_i &&
288
+ test_cmp expect_p got_p &&
289
+ test_cmp expect_i got_i
290
+'
291
+
292
# Diff settings that change hunks but are not part of the store key must
293
# bypass it in both directions, so output stays byte-identical to a
294
# store-less run.
425
)
426
'
427
428
+test_expect_success 'blame -w stays correct and does not hit default entries' '
429
+ (
430
+ cd ws-repo &&
431
+ no_store blame -w f >expect &&
432
+ git blame -w --show-stats f >out 2>&1 &&
433
+ test_grep "num precomputed hits: 0" out &&
434
+ git blame -w f >actual &&
435
+ test_cmp expect actual
436
+ )
437
+'
438
+
439
+test_expect_success 'blame with indentHeuristic off stays correct and misses' '
440
+ warm &&
441
+ git -c diff.indentHeuristic=false blame --show-stats file.txt >out 2>&1 &&
442
+ test_grep "num precomputed hits: 0" out &&
443
+ no_store -c diff.indentHeuristic=false blame file.txt >expect &&
444
+ git -c diff.indentHeuristic=false blame file.txt >actual &&
445
+ test_cmp expect actual
446
+'
447
+
448
test_expect_success 'a driver algorithm override keeps output correct and keys apart' '
449
git init driver-algo &&
450
(
479
)
480
'
481
482
+test_expect_success 'blame --reverse never consults the store' '
483
+ warm &&
484
+ git blame --reverse HEAD~3..HEAD file.txt >actual 2>/dev/null &&
485
+ no_store blame --reverse HEAD~3..HEAD file.txt >expect 2>/dev/null &&
486
+ test_cmp expect actual &&
487
+ # Reverse blame withholds the pair identity. Zero hits alone
488
+ # cannot prove that: reverse pairs are never warmed, so a
489
+ # consulted pair would miss, not hit. Zero misses is what shows
490
+ # the store was never consulted.
491
+ git blame --reverse --show-stats HEAD~3..HEAD file.txt \
492
+ >stats 2>/dev/null &&
493
+ test_grep "num precomputed hits: 0" stats &&
494
+ test_grep "num precomputed misses: 0" stats
495
+'
496
+
497
+test_expect_success 'blame with a textconv driver bypasses the store' '
498
+ echo "tc.txt diff=tc" >>.gitattributes &&
499
+ git add .gitattributes &&
500
+ git commit -m tc-attr &&
501
+ git config diff.tc.textconv "sed -e s/1/one/" &&
502
+ test_commit tc1 tc.txt "line 1" &&
503
+ test_commit tc2 tc.txt "line 1
504
+line 2" &&
505
+ warm &&
506
+ git blame --show-stats tc.txt >out 2>&1 &&
507
+ test_grep "num precomputed hits: 0" out &&
508
+ no_store blame tc.txt >expect &&
509
+ git blame tc.txt >actual &&
510
+ test_cmp expect actual
511
+'
512
+
513
+test_expect_success 'a replaced blob makes the store step aside' '
514
+ git init replace-repo &&
515
+ (
516
+ cd replace-repo &&
517
+ test_commit r1 f.txt "a" &&
518
+ test_commit r2 f.txt "a
519
+b" &&
520
+ warm &&
521
+ # Control: without a replacement the pair is served.
522
+ GIT_TRACE2_EVENT="$PWD/trace_ctl.json" \
523
+ git log -1 --format= --numstat -- f.txt >/dev/null &&
524
+ test_grep read-hits trace_ctl.json &&
525
+ # Replace r2 blob: the diff now reads different content
526
+ # (through OBJECT_INFO_LOOKUP_REPLACE) under the id the store
527
+ # keyed, so a served answer would be the pre-replacement diff.
528
+ # Identity is withheld, the store steps aside, and the builtin
529
+ # computes from the replaced content.
530
+ new_blob=$(git rev-parse HEAD:f.txt) &&
531
+ repl=$(printf "a\nB\nC\nD\n" | git hash-object -w --stdin) &&
532
+ git replace "$new_blob" "$repl" &&
533
+ no_store log -1 --format= --numstat -- f.txt >expect &&
534
+ git log -1 --format= --numstat -- f.txt >actual &&
535
+ test_cmp expect actual &&
536
+ GIT_TRACE2_EVENT="$PWD/trace_repl.json" \
537
+ git log -1 --format= --numstat -- f.txt >/dev/null &&
538
+ test_grep ! read-hits trace_repl.json
539
+ )
540
+'
541
+
542
+test_expect_success 'blame -M and -C stay correct with the store' '
543
+ warm &&
544
+ no_store blame -M file.txt >expect_m &&
545
+ no_store blame -C file.txt >expect_c &&
546
+ git blame -M file.txt >got_m &&
547
+ git blame -C file.txt >got_c &&
548
+ test_cmp expect_m got_m &&
549
+ test_cmp expect_c got_c
550
+'
551
+
552
+# Copy-detecting (and reverse) blame still diff blob pairs through
553
+# pass_blame_to_parent, so they must use the real blame xdl_opts. A
554
+# whitespace-only change is invisible under -w; if -w were dropped on
555
+# these paths the -w and non-w results would coincide.
556
+test_expect_success 'blame -C honors -w' '
557
+ git init -q blame-cw &&
558
+ (
559
+ cd blame-cw &&
560
+ printf "one\ntwo\nthree\n" >f &&
561
+ git add f && git commit -q -m base &&
562
+ printf "one\n two \nthree\n" >f &&
563
+ git add f && git commit -q -m reindent &&
564
+ git blame -C -w f >with_w &&
565
+ git blame -C f >without_w &&
566
+ ! test_cmp with_w without_w
567
+ )
568
+'
569
+
570
# Cover the pair shapes an object walk encounters: binary and
571
# mode-only changes produce no text hunks to record.
572
test_expect_success 'binary and mode-only changes do not break the writer' '
587
test_cmp expect actual
588
'
589
590
+test_expect_success 'blame across a rename matches' '
591
+ echo "original content" >rename-src.txt &&
592
+ git add rename-src.txt &&
593
+ git commit -m "add rename-src" &&
594
+ echo "more" >>rename-src.txt &&
595
+ git add rename-src.txt &&
596
+ git commit -m "modify rename-src" &&
597
+ git mv rename-src.txt rename-dst.txt &&
598
+ git commit -m "rename" &&
599
+ echo "post" >>rename-dst.txt &&
600
+ git add rename-dst.txt &&
601
+ git commit -m "modify after rename" &&
602
+ no_store blame rename-dst.txt >expect &&
603
+ warm &&
604
+ git blame rename-dst.txt >actual &&
605
+ test_cmp expect actual
606
+'
607
+
608
+test_expect_success 'blame handles merge commits' '
609
+ git checkout -b merge-side main~2 &&
610
+ test_commit merge-change merge-file.txt "side content" &&
611
+ git checkout main &&
612
+ git merge --no-edit merge-side &&
613
+ no_store blame merge-file.txt >expect &&
614
+ warm &&
615
+ git blame merge-file.txt >actual &&
616
+ test_cmp expect actual
617
+'
618
+
619
+test_expect_success 'distinct --contents against one revision do not collide' '
620
+ warm &&
621
+ test_write_lines "line 1" "appended line" >c1 &&
622
+ test_write_lines "rewritten line" >c2 &&
623
+ # Ground truth without the store.
624
+ no_store blame -s --contents=c2 file.txt initial >expect &&
625
+ # With the store, an intervening c1 run must not poison the c2 lookup.
626
+ git blame -s --contents=c1 file.txt initial >/dev/null &&
627
+ git blame -s --contents=c2 file.txt initial >actual &&
628
+ test_cmp expect actual &&
629
+ # The --contents side is a working-tree pseudo-commit (a null commit
630
+ # id), so its pairs withhold identity and never consult the store.
631
+ # Output parity alone cannot show that: a consulted unwarmed pair
632
+ # would miss, not hit, so zero misses is what proves the pair was
633
+ # never looked up.
634
+ git blame -s --show-stats --contents=c2 file.txt initial >stats 2>&1 &&
635
+ test_grep "num precomputed hits: 0" stats &&
636
+ test_grep "num precomputed misses: 0" stats
637
+'
638
+
639
+test_expect_success 'blame --ignore-rev bypasses the store for ignored pairs' '
640
+ git init ignore-rev-repo &&
641
+ (
642
+ cd ignore-rev-repo &&
643
+ test_commit ir1 f.txt "base" &&
644
+ test_commit ir2 f.txt "base
645
+more" &&
646
+ warm &&
647
+ # Control: the ordinary pass is served, nothing is computed.
648
+ git blame --show-stats f.txt >ctl 2>&1 &&
649
+ test_grep "num precomputed hits: 1" ctl &&
650
+ test_grep "num get patch: 0" ctl &&
651
+ no_store blame --ignore-rev ir2 f.txt >expect &&
652
+ git blame --ignore-rev ir2 f.txt >actual &&
653
+ test_cmp expect actual &&
654
+ # The ignored revision adds a pass that withholds identity:
655
+ # it computes its diff (get patch rises) instead of being
656
+ # served or even counted as a store consultation.
657
+ git blame --ignore-rev ir2 --show-stats f.txt >stats 2>&1 &&
658
+ test_grep "num precomputed hits: 1" stats &&
659
+ test_grep "num precomputed misses: 0" stats &&
660
+ test_grep "num get patch: 1" stats
661
+ )
662
+'
663
+
664
+test_expect_success 'blame counts misses for pairs the store does not hold' '
665
+ (
666
+ cd ignore-rev-repo &&
667
+ test_commit ir3 f.txt "base
668
+more
669
+third" &&
670
+ git blame --show-stats f.txt >stats 2>&1 &&
671
+ test_grep "num precomputed hits: 1" stats &&
672
+ test_grep "num precomputed misses: 1" stats
673
+ )
674
+'
675
+
676
test_expect_success 'log -L --stat neither reads nor records' '
677
warm &&
678
GIT_TRACE2_EVENT="$PWD/trace_linelog.json" \
683
test_path_is_missing $STORE
684
'
685
686
+# Integrity: a structurally broken header is read as absent (the reader
687
+# falls back to xdiff and stays correct); a checksum mismatch is caught
688
+# by verify, which is when integrity is checked.
689
+test_expect_success 'a truncated store is read as absent' '
690
+ warm &&
691
+ test_copy_bytes 20 <$STORE >truncated &&
692
+ mv truncated $STORE &&
693
+ no_store blame file.txt >expect &&
694
+ git blame file.txt >actual &&
695
+ test_cmp expect actual
696
+'
697
+
698
+test_expect_success 'a corrupt signature is read as absent' '
699
+ warm &&
700
+ printf "XXXX" >corrupt &&
701
+ tail -c +5 <$STORE >>corrupt &&
702
+ mv corrupt $STORE &&
703
+ no_store blame file.txt >expect &&
704
+ git blame file.txt >actual &&
705
+ test_cmp expect actual
706
+'
707
+
708
+# Byte 6 of the header is the chunk count; a value larger than the file
709
+# can hold must be rejected before the chunk table is walked.
710
+test_expect_success 'an over-claimed chunk count is read as absent' '
711
+ warm &&
712
+ printf "\377" | dd of=$STORE bs=1 seek=6 count=1 conv=notrunc 2>/dev/null &&
713
+ no_store blame file.txt >expect &&
714
+ git blame file.txt >actual &&
715
+ test_cmp expect actual
716
+'
717
+
718
+# A record with no hunks would replay as an equivalence claim, which
719
+# the writer never records; the reader must treat such a record as a
720
+# miss and recompute, and verify must flag it.
721
+test_expect_success 'a zero-hunk record is read as a miss and fails verify' '
722
+ git init zero-hunk &&
723
+ (
724
+ cd zero-hunk &&
725
+ test_commit z1 f.txt "base" &&
726
+ test_commit z2 f.txt "base
727
+more" &&
728
+ warm &&
729
+ # The store holds one entry of one hunk: a 4-byte count and
730
+ # one 16-byte hunk record, just before the trailing
731
+ # checksum. Zero the count to craft the record the writer
732
+ # refuses to produce.
733
+ rawsz=$(test_oid rawsz) &&
734
+ fsize=$(test_file_size $STORE) &&
735
+ printf "\\0\\0\\0\\0" | dd of=$STORE bs=1 \
736
+ seek=$((fsize - rawsz - 20)) count=4 conv=notrunc \
737
+ 2>/dev/null &&
738
+ no_store blame f.txt >expect &&
739
+ git blame --show-stats f.txt >stats 2>&1 &&
740
+ test_grep "num precomputed hits: 0" stats &&
741
+ git blame f.txt >actual &&
742
+ test_cmp expect actual &&
743
+ test_must_fail git diff-hunks verify
744
+ )
745
+'
746
+
747
test_expect_success 'verify succeeds on a valid store and on an absent one' '
748
warm &&
749
git diff-hunks verify &&
777
test_cmp expect actual
778
'
779
780
+# A generated patch must carry the builtin diffstat, not one served from
781
+# the sender's local store, so its counts do not depend on whether the
782
+# sender warmed the store. Poison the store so a served answer diverges
783
+# from the builtin, then confirm format-patch shows the builtin counts.
784
+test_expect_success 'format-patch keeps its diffstat off the store' '
785
+ git init fp-repo &&
786
+ (
787
+ cd fp-repo &&
788
+ test_commit p1 f.txt "a" &&
789
+ test_commit p2 f.txt "a
790
+b" &&
791
+ warm &&
792
+ # Bump the new-side count of the single recorded hunk. The
793
+ # record stays structurally valid, and a read skips the
794
+ # trailing checksum, so the store serves this poisoned count.
795
+ rawsz=$(test_oid rawsz) &&
796
+ fsize=$(test_file_size .git/objects/info/diff-hunks) &&
797
+ printf "\\0\\0\\0\\7" | dd of=.git/objects/info/diff-hunks bs=1 \
798
+ seek=$((fsize - rawsz - 4)) count=4 conv=notrunc 2>/dev/null &&
799
+ # The store now serves a divergent count, proving the poison
800
+ # is live and observable through a store consumer.
801
+ printf "7\t0\tf.txt\n" >poisoned &&
802
+ git log -1 --format= --numstat -- f.txt >served &&
803
+ test_cmp poisoned served &&
804
+ # format-patch does not consult the store, so its output is
805
+ # identical with the store poisoned and with it disabled.
806
+ no_store format-patch -1 --stdout --stat -- f.txt >expect &&
807
+ git format-patch -1 --stdout --stat -- f.txt >actual &&
808
+ test_cmp expect actual
809
+ )
810
+'
811
+
812
test_expect_success 'diff-hunks clear removes the store file' '
813
warm &&
814
test_path_is_file $STORE &&