Raw
1 #!/bin/sh
2
3 test_description='test index-pack handling of delta cycles in packfiles'
4
5 . ./test-lib.sh
6 . "$TEST_DIRECTORY"/lib-pack.sh
7
8 # Two similar-ish objects that we have computed deltas between.
9 A=$(test_oid packlib_7_0)
10 B=$(test_oid packlib_7_76)
11
12 # Copy the entries from a complete pack without its header or trailer.
13 pack_entries () {
14 entry_size=$(wc -c <"$1") &&
15 dd if="$1" bs=1 skip=12 \
16 count=$((entry_size - 12 - $(test_oid rawsz))) 2>/dev/null
17 }
18
19 # B as an OFS_DELTA against A at the given one-byte distance.
20 pack_obj_b_ofs_a () {
21 pack_obj "$B" "$A" >b-ref.tmp &&
22 printf "\145" &&
23 printf "\\$(printf "%03o" "$1")" &&
24 dd if=b-ref.tmp bs=1 skip=$((1 + $(test_oid rawsz))) 2>/dev/null
25 }
26
27 # Return the base of the first one-byte-header REF_DELTA for the given OID.
28 first_ref_base () {
29 idx=$(echo .git/objects/pack/*.idx) &&
30 offset=$(git show-index <"$idx" |
31 awk -v oid="$1" '$2 == oid { print $1; exit }') &&
32 dd if="${idx%.idx}.pack" bs=1 skip=$((offset + 1)) \
33 count=$(test_oid rawsz) 2>/dev/null |
34 test-tool hexdump |
35 tr -d " \n"
36 }
37
38 # The order of equal-OID entries in the .idx is unspecified. Retain a pack
39 # which selects $1 as a delta against $2. Unless $3 is "-", also require the
40 # first copy searched during recovery to be a REF_DELTA against $3.
41 install_cycle () {
42 cycle_oid=$1 &&
43 cycle_base=$2 &&
44 first_base=$3 &&
45 shift 3 &&
46 for pack
47 do
48 clear_packs &&
49 git index-pack --fix-thin --stdin <"$pack" &&
50 selected_base=$(echo "$cycle_oid" |
51 git cat-file --batch-check="%(deltabase)") ||
52 return 1
53 if test "$selected_base" = "$cycle_base" &&
54 { test "$first_base" = "-" ||
55 test "$(first_ref_base "$cycle_oid")" = "$first_base"; }
56 then
57 return 0
58 fi
59 done
60 return 1
61 }
62
63 make_cycle_pack () {
64 cycle_pack=$1 &&
65 shift &&
66 test-tool -C alt-source pack-deltas --num-objects=6 >refs.tmp <<-EOF &&
67 REF_DELTA $T $X
68 REF_DELTA $X $1
69 REF_DELTA $Y $Z
70 REF_DELTA $Z $X
71 REF_DELTA $X $2
72 REF_DELTA $X $3
73 EOF
74 {
75 pack_header 8 &&
76 pack_entries refs.tmp &&
77 cat a-full &&
78 pack_obj_b_ofs_a "$a_full_size"
79 } >"$cycle_pack" &&
80 pack_trailer "$cycle_pack"
81 }
82
83 check_blob () {
84 test "$(git cat-file -t "$1")" = blob &&
85 git cat-file blob "$1" >actual &&
86 test_cmp_bin "$2" actual
87 }
88
89 # double-check our hand-constucted packs
90 test_expect_success 'index-pack works with a single delta (A->B)' '
91 clear_packs &&
92 {
93 pack_header 2 &&
94 pack_obj $A $B &&
95 pack_obj $B
96 } >ab.pack &&
97 pack_trailer ab.pack &&
98 git index-pack --stdin <ab.pack &&
99 git cat-file -t $A &&
100 git cat-file -t $B
101 '
102
103 test_expect_success 'index-pack works with a single delta (B->A)' '
104 clear_packs &&
105 {
106 pack_header 2 &&
107 pack_obj $A &&
108 pack_obj $B $A
109 } >ba.pack &&
110 pack_trailer ba.pack &&
111 git index-pack --stdin <ba.pack &&
112 git cat-file -t $A &&
113 git cat-file -t $B
114 '
115
116 test_expect_success 'index-pack detects missing base objects' '
117 clear_packs &&
118 {
119 pack_header 1 &&
120 pack_obj $A $B
121 } >missing.pack &&
122 pack_trailer missing.pack &&
123 test_must_fail git index-pack --fix-thin --stdin <missing.pack
124 '
125
126 test_expect_success 'index-pack detects REF_DELTA cycles' '
127 clear_packs &&
128 {
129 pack_header 2 &&
130 pack_obj $A $B &&
131 pack_obj $B $A
132 } >cycle.pack &&
133 pack_trailer cycle.pack &&
134 test_must_fail git index-pack --fix-thin --stdin <cycle.pack
135 '
136
137 test_expect_success 'failover to an object in another pack' '
138 clear_packs &&
139 git index-pack --stdin <ab.pack &&
140
141 # This cycle does not fail since the existence of A & B in
142 # the repo allows us to resolve the cycle.
143 git index-pack --stdin --fix-thin <cycle.pack
144 '
145
146 test_expect_success 'failover to a duplicate object in the same pack' '
147 {
148 pack_header 3 &&
149 pack_obj $A &&
150 pack_obj $B $A &&
151 pack_obj $A $B
152 } >recoverable-1.pack &&
153 pack_trailer recoverable-1.pack &&
154 {
155 pack_header 3 &&
156 pack_obj $A $B &&
157 pack_obj $B $A &&
158 pack_obj $A
159 } >recoverable-2.pack &&
160 pack_trailer recoverable-2.pack &&
161
162 # The selected copy of A is part of the cycle, but the full copy
163 # lets both type and content lookups resolve it.
164 install_cycle "$A" "$B" - recoverable-1.pack recoverable-2.pack &&
165 printf "\7\0" >expect &&
166 check_blob "$A" expect
167 '
168
169 test_expect_success 'failover from a mixed REF/OFS cycle' '
170 pack_obj "$A" "$B" >a-ref &&
171 pack_obj "$B" >b-full &&
172 a_ref_size=$(wc -c <a-ref) &&
173 b_full_size=$(wc -c <b-full) &&
174
175 {
176 pack_header 3 &&
177 cat a-ref &&
178 cat b-full &&
179 pack_obj_b_ofs_a "$((a_ref_size + b_full_size))"
180 } >mixed-1.pack &&
181 pack_trailer mixed-1.pack &&
182 {
183 pack_header 3 &&
184 cat a-ref &&
185 pack_obj_b_ofs_a "$a_ref_size" &&
186 cat b-full
187 } >mixed-2.pack &&
188 pack_trailer mixed-2.pack &&
189
190 # The REF_DELTA for A selects the OFS_DELTA copy of B; the
191 # full B is its escape.
192 install_cycle "$B" "$A" - mixed-1.pack mixed-2.pack &&
193 printf "\7\0" >expect &&
194 check_blob "$A" expect
195 '
196
197 test_expect_success 'failover after a tail into a three-object delta cycle' '
198 git init alt-source &&
199 printf "\7\76" |
200 git -C alt-source hash-object -w --stdin >/dev/null &&
201 X=$(printf x | git -C alt-source hash-object -w --stdin) &&
202 Y=$(printf y | git -C alt-source hash-object -w --stdin) &&
203 Z=$(printf z | git -C alt-source hash-object -w --stdin) &&
204 printf "tail T\n" >tail &&
205 T=$(git -C alt-source hash-object -w --stdin <tail) &&
206
207 pack_obj "$A" >a-full &&
208 a_full_size=$(wc -c <a-full) &&
209 make_cycle_pack alternate-1.pack "$B" "$Y" "$Y" &&
210 make_cycle_pack alternate-2.pack "$Y" "$B" "$Y" &&
211 make_cycle_pack alternate-3.pack "$Y" "$Y" "$B" &&
212
213 # Lookup of T follows T->X->Y->Z->X. Recovery must exhaust that
214 # branch, then use X->B->A, whose final edge is an OFS_DELTA.
215 install_cycle "$X" "$Y" "$Y" \
216 alternate-1.pack alternate-2.pack alternate-3.pack &&
217 check_blob "$T" tail
218 '
219
220 test_expect_success 'index-pack works with thin pack A->B->C with B on disk' '
221 git init server &&
222 (
223 cd server &&
224 test_commit_bulk 4
225 ) &&
226
227 A=$(git -C server rev-parse HEAD^{tree}) &&
228 B=$(git -C server rev-parse HEAD~1^{tree}) &&
229 C=$(git -C server rev-parse HEAD~2^{tree}) &&
230 git -C server reset --hard HEAD~1 &&
231
232 test-tool -C server pack-deltas --num-objects=2 >thin.pack <<-EOF &&
233 REF_DELTA $A $B
234 REF_DELTA $B $C
235 EOF
236
237 git clone "file://$(pwd)/server" client &&
238 (
239 cd client &&
240 git index-pack --fix-thin --stdin <../thin.pack
241 )
242 '
243
244 test_done