| 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 |