| 1 | #!/bin/sh |
| 2 | |
| 3 | test_description='partial clone' |
| 4 | |
| 5 | . ./test-lib.sh |
| 6 | . "$TEST_DIRECTORY"/lib-terminal.sh |
| 7 | |
| 8 | # When enabled, some commands will write commit-graphs. This causes fsck |
| 9 | # to fail when delete_object() is called because fsck will attempt to |
| 10 | # verify the out-of-sync commit graph. |
| 11 | GIT_TEST_COMMIT_GRAPH=0 |
| 12 | |
| 13 | delete_object () { |
| 14 | local repo="$1" |
| 15 | local obj="$2" |
| 16 | local path="$repo/.git/objects/$(test_oid_to_path "$obj")" && |
| 17 | rm "$path" |
| 18 | } |
| 19 | |
| 20 | pack_as_from_promisor () { |
| 21 | HASH=$(git -C repo pack-objects .git/objects/pack/pack) && |
| 22 | >repo/.git/objects/pack/pack-$HASH.promisor && |
| 23 | echo $HASH |
| 24 | } |
| 25 | |
| 26 | promise_and_delete () { |
| 27 | HASH=$(git -C repo rev-parse "$1") && |
| 28 | git -C repo tag -a -m message my_annotated_tag "$HASH" && |
| 29 | git -C repo rev-parse my_annotated_tag | pack_as_from_promisor && |
| 30 | # tag -d prints a message to stdout, so redirect it |
| 31 | git -C repo tag -d my_annotated_tag >/dev/null && |
| 32 | delete_object repo "$HASH" |
| 33 | } |
| 34 | |
| 35 | test_expect_success 'extensions.partialclone without filter' ' |
| 36 | test_create_repo server && |
| 37 | git clone --filter="blob:none" "file://$(pwd)/server" client && |
| 38 | git -C client config --unset remote.origin.partialclonefilter && |
| 39 | git -C client fetch origin |
| 40 | ' |
| 41 | |
| 42 | test_expect_success 'convert shallow clone to partial clone' ' |
| 43 | rm -fr server client && |
| 44 | test_create_repo server && |
| 45 | test_commit -C server my_commit 1 && |
| 46 | test_commit -C server my_commit2 1 && |
| 47 | git clone --depth=1 "file://$(pwd)/server" client && |
| 48 | git -C client fetch --unshallow --filter="blob:none" && |
| 49 | test_cmp_config -C client true remote.origin.promisor && |
| 50 | test_cmp_config -C client blob:none remote.origin.partialclonefilter && |
| 51 | test_cmp_config -C client 1 core.repositoryformatversion |
| 52 | ' |
| 53 | |
| 54 | test_expect_success DEFAULT_REPO_FORMAT 'convert to partial clone with noop extension' ' |
| 55 | rm -fr server client && |
| 56 | test_create_repo server && |
| 57 | test_commit -C server my_commit 1 && |
| 58 | test_commit -C server my_commit2 1 && |
| 59 | git clone --depth=1 "file://$(pwd)/server" client && |
| 60 | test_cmp_config -C client 0 core.repositoryformatversion && |
| 61 | git -C client config extensions.noop true && |
| 62 | git -C client fetch --unshallow --filter="blob:none" |
| 63 | ' |
| 64 | |
| 65 | test_expect_success DEFAULT_REPO_FORMAT 'converting to partial clone fails with unrecognized extension' ' |
| 66 | rm -fr server client && |
| 67 | test_create_repo server && |
| 68 | test_commit -C server my_commit 1 && |
| 69 | test_commit -C server my_commit2 1 && |
| 70 | git clone --depth=1 "file://$(pwd)/server" client && |
| 71 | test_cmp_config -C client 0 core.repositoryformatversion && |
| 72 | git -C client config extensions.nonsense true && |
| 73 | test_must_fail git -C client fetch --unshallow --filter="blob:none" |
| 74 | ' |
| 75 | |
| 76 | test_expect_success 'missing reflog object, but promised by a commit, passes fsck' ' |
| 77 | rm -rf repo && |
| 78 | test_create_repo repo && |
| 79 | test_commit -C repo my_commit && |
| 80 | |
| 81 | A=$(git -C repo commit-tree -m a HEAD^{tree}) && |
| 82 | C=$(git -C repo commit-tree -m c -p $A HEAD^{tree}) && |
| 83 | |
| 84 | # Reference $A only from reflog, and delete it |
| 85 | git -C repo branch my_branch "$A" && |
| 86 | git -C repo branch -f my_branch my_commit && |
| 87 | delete_object repo "$A" && |
| 88 | |
| 89 | # State that we got $C, which refers to $A, from promisor |
| 90 | printf "$C\n" | pack_as_from_promisor && |
| 91 | |
| 92 | # Normally, it fails |
| 93 | test_must_fail git -C repo fsck && |
| 94 | |
| 95 | # But with the extension, it succeeds |
| 96 | git -C repo config core.repositoryformatversion 1 && |
| 97 | git -C repo config extensions.partialclone "arbitrary string" && |
| 98 | git -C repo fsck |
| 99 | ' |
| 100 | |
| 101 | test_expect_success 'missing reflog object, but promised by a tag, passes fsck' ' |
| 102 | rm -rf repo && |
| 103 | test_create_repo repo && |
| 104 | test_commit -C repo my_commit && |
| 105 | |
| 106 | A=$(git -C repo commit-tree -m a HEAD^{tree}) && |
| 107 | git -C repo tag -a -m d my_tag_name $A && |
| 108 | T=$(git -C repo rev-parse my_tag_name) && |
| 109 | git -C repo tag -d my_tag_name && |
| 110 | |
| 111 | # Reference $A only from reflog, and delete it |
| 112 | git -C repo branch my_branch "$A" && |
| 113 | git -C repo branch -f my_branch my_commit && |
| 114 | delete_object repo "$A" && |
| 115 | |
| 116 | # State that we got $T, which refers to $A, from promisor |
| 117 | printf "$T\n" | pack_as_from_promisor && |
| 118 | |
| 119 | git -C repo config core.repositoryformatversion 1 && |
| 120 | git -C repo config extensions.partialclone "arbitrary string" && |
| 121 | git -C repo fsck |
| 122 | ' |
| 123 | |
| 124 | test_expect_success 'missing reflog object alone fails fsck, even with extension set' ' |
| 125 | rm -rf repo && |
| 126 | test_create_repo repo && |
| 127 | test_commit -C repo my_commit && |
| 128 | |
| 129 | A=$(git -C repo commit-tree -m a HEAD^{tree}) && |
| 130 | B=$(git -C repo commit-tree -m b HEAD^{tree}) && |
| 131 | |
| 132 | # Reference $A only from reflog, and delete it |
| 133 | git -C repo branch my_branch "$A" && |
| 134 | git -C repo branch -f my_branch my_commit && |
| 135 | delete_object repo "$A" && |
| 136 | |
| 137 | git -C repo config core.repositoryformatversion 1 && |
| 138 | git -C repo config extensions.partialclone "arbitrary string" && |
| 139 | test_must_fail git -C repo fsck |
| 140 | ' |
| 141 | |
| 142 | test_expect_success 'missing ref object, but promised, passes fsck' ' |
| 143 | rm -rf repo && |
| 144 | test_create_repo repo && |
| 145 | test_commit -C repo my_commit && |
| 146 | |
| 147 | A=$(git -C repo commit-tree -m a HEAD^{tree}) && |
| 148 | |
| 149 | # Reference $A only from ref |
| 150 | git -C repo branch my_branch "$A" && |
| 151 | promise_and_delete "$A" && |
| 152 | |
| 153 | git -C repo config core.repositoryformatversion 1 && |
| 154 | git -C repo config extensions.partialclone "arbitrary string" && |
| 155 | git -C repo fsck |
| 156 | ' |
| 157 | |
| 158 | test_expect_success 'missing object, but promised, passes fsck' ' |
| 159 | rm -rf repo && |
| 160 | test_create_repo repo && |
| 161 | test_commit -C repo 1 && |
| 162 | test_commit -C repo 2 && |
| 163 | test_commit -C repo 3 && |
| 164 | git -C repo tag -a annotated_tag -m "annotated tag" && |
| 165 | |
| 166 | C=$(git -C repo rev-parse 1) && |
| 167 | T=$(git -C repo rev-parse 2^{tree}) && |
| 168 | B=$(git hash-object repo/3.t) && |
| 169 | AT=$(git -C repo rev-parse annotated_tag) && |
| 170 | |
| 171 | promise_and_delete "$C" && |
| 172 | promise_and_delete "$T" && |
| 173 | promise_and_delete "$B" && |
| 174 | promise_and_delete "$AT" && |
| 175 | |
| 176 | git -C repo config core.repositoryformatversion 1 && |
| 177 | git -C repo config extensions.partialclone "arbitrary string" && |
| 178 | git -C repo fsck |
| 179 | ' |
| 180 | |
| 181 | test_expect_success 'missing CLI object, but promised, passes fsck' ' |
| 182 | rm -rf repo && |
| 183 | test_create_repo repo && |
| 184 | test_commit -C repo my_commit && |
| 185 | |
| 186 | A=$(git -C repo commit-tree -m a HEAD^{tree}) && |
| 187 | promise_and_delete "$A" && |
| 188 | |
| 189 | git -C repo config core.repositoryformatversion 1 && |
| 190 | git -C repo config extensions.partialclone "arbitrary string" && |
| 191 | git -C repo fsck "$A" |
| 192 | ' |
| 193 | |
| 194 | test_expect_success 'fetching of missing objects' ' |
| 195 | rm -rf repo err && |
| 196 | test_create_repo server && |
| 197 | test_commit -C server foo && |
| 198 | git -C server repack -a -d --write-bitmap-index && |
| 199 | |
| 200 | git clone "file://$(pwd)/server" repo && |
| 201 | HASH=$(git -C repo rev-parse foo) && |
| 202 | rm -rf repo/.git/objects/* && |
| 203 | |
| 204 | git -C repo config core.repositoryformatversion 1 && |
| 205 | git -C repo config extensions.partialclone "origin" && |
| 206 | git -C repo cat-file -p "$HASH" 2>err && |
| 207 | |
| 208 | # Ensure that no spurious FETCH_HEAD messages are written |
| 209 | test_grep ! FETCH_HEAD err && |
| 210 | |
| 211 | # Ensure that the .promisor file is written, and check that its |
| 212 | # associated packfile contains the object |
| 213 | ls repo/.git/objects/pack/pack-*.promisor >promisorlist && |
| 214 | test_line_count = 1 promisorlist && |
| 215 | IDX=$(sed "s/promisor$/idx/" promisorlist) && |
| 216 | git verify-pack --verbose "$IDX" >out && |
| 217 | test_grep "$HASH" out |
| 218 | ' |
| 219 | |
| 220 | test_expect_success 'fetching of a promised object that promisor remote no longer has' ' |
| 221 | rm -f err && |
| 222 | test_create_repo unreliable-server && |
| 223 | git -C unreliable-server config uploadpack.allowanysha1inwant 1 && |
| 224 | git -C unreliable-server config uploadpack.allowfilter 1 && |
| 225 | test_commit -C unreliable-server foo && |
| 226 | |
| 227 | git clone --filter=blob:none --no-checkout "file://$(pwd)/unreliable-server" unreliable-client && |
| 228 | |
| 229 | rm -rf unreliable-server/.git/objects/* && |
| 230 | test_must_fail git -C unreliable-client checkout HEAD 2>err && |
| 231 | test_grep "could not fetch.*from promisor remote" err |
| 232 | ' |
| 233 | |
| 234 | test_expect_success 'fetching of missing objects works with ref-in-want enabled' ' |
| 235 | # ref-in-want requires protocol version 2 |
| 236 | git -C server config protocol.version 2 && |
| 237 | git -C server config uploadpack.allowrefinwant 1 && |
| 238 | git -C repo config protocol.version 2 && |
| 239 | |
| 240 | rm -rf repo/.git/objects/* && |
| 241 | rm -f trace && |
| 242 | GIT_TRACE_PACKET="$(pwd)/trace" git -C repo cat-file -p "$HASH" && |
| 243 | test_grep "fetch< fetch=.*ref-in-want" trace |
| 244 | ' |
| 245 | |
| 246 | test_expect_success 'fetching from another promisor remote' ' |
| 247 | git clone "file://$(pwd)/server" server2 && |
| 248 | test_commit -C server2 bar && |
| 249 | git -C server2 repack -a -d --write-bitmap-index && |
| 250 | HASH2=$(git -C server2 rev-parse bar) && |
| 251 | |
| 252 | git -C repo remote add server2 "file://$(pwd)/server2" && |
| 253 | git -C repo config remote.server2.promisor true && |
| 254 | git -C repo cat-file -p "$HASH2" && |
| 255 | |
| 256 | git -C repo fetch server2 && |
| 257 | rm -rf repo/.git/objects/* && |
| 258 | git -C repo cat-file -p "$HASH2" && |
| 259 | |
| 260 | # Ensure that the .promisor file is written, and check that its |
| 261 | # associated packfile contains the object |
| 262 | ls repo/.git/objects/pack/pack-*.promisor >promisorlist && |
| 263 | test_line_count = 1 promisorlist && |
| 264 | IDX=$(sed "s/promisor$/idx/" promisorlist) && |
| 265 | git verify-pack --verbose "$IDX" >out && |
| 266 | test_grep "$HASH2" out |
| 267 | ' |
| 268 | |
| 269 | test_expect_success 'fetching with --filter configures a promisor remote' ' |
| 270 | test_create_repo server3 && |
| 271 | test_commit -C server3 baz && |
| 272 | git -C server3 repack -a -d --write-bitmap-index && |
| 273 | HASH3=$(git -C server3 rev-parse baz) && |
| 274 | git -C server3 config uploadpack.allowfilter 1 && |
| 275 | |
| 276 | rm repo/.git/objects/pack/pack-*.promisor && |
| 277 | |
| 278 | git -C repo remote add server3 "file://$(pwd)/server3" && |
| 279 | git -C repo fetch --filter="blob:none" server3 $HASH3 && |
| 280 | |
| 281 | test_cmp_config -C repo true remote.server3.promisor && |
| 282 | |
| 283 | # Ensure that the .promisor file is written, and check that its |
| 284 | # associated packfile contains the object |
| 285 | ls repo/.git/objects/pack/pack-*.promisor >promisorlist && |
| 286 | test_line_count = 1 promisorlist && |
| 287 | IDX=$(sed "s/promisor$/idx/" promisorlist) && |
| 288 | git verify-pack --verbose "$IDX" >out && |
| 289 | test_grep "$HASH3" out |
| 290 | ' |
| 291 | |
| 292 | test_expect_success 'fetching of missing blobs works' ' |
| 293 | rm -rf server server2 repo && |
| 294 | rm -rf server server3 repo && |
| 295 | test_create_repo server && |
| 296 | test_commit -C server foo && |
| 297 | git -C server repack -a -d --write-bitmap-index && |
| 298 | |
| 299 | git clone "file://$(pwd)/server" repo && |
| 300 | git hash-object repo/foo.t >blobhash && |
| 301 | rm -rf repo/.git/objects/* && |
| 302 | |
| 303 | git -C server config uploadpack.allowanysha1inwant 1 && |
| 304 | git -C server config uploadpack.allowfilter 1 && |
| 305 | git -C repo config core.repositoryformatversion 1 && |
| 306 | git -C repo config extensions.partialclone "origin" && |
| 307 | |
| 308 | git -C repo cat-file -p $(cat blobhash) |
| 309 | ' |
| 310 | |
| 311 | test_expect_success 'fetching of missing trees does not fetch blobs' ' |
| 312 | rm -rf server repo && |
| 313 | test_create_repo server && |
| 314 | test_commit -C server foo && |
| 315 | git -C server repack -a -d --write-bitmap-index && |
| 316 | |
| 317 | git clone "file://$(pwd)/server" repo && |
| 318 | git -C repo rev-parse foo^{tree} >treehash && |
| 319 | git hash-object repo/foo.t >blobhash && |
| 320 | rm -rf repo/.git/objects/* && |
| 321 | |
| 322 | git -C server config uploadpack.allowanysha1inwant 1 && |
| 323 | git -C server config uploadpack.allowfilter 1 && |
| 324 | git -C repo config core.repositoryformatversion 1 && |
| 325 | git -C repo config extensions.partialclone "origin" && |
| 326 | git -C repo cat-file -p $(cat treehash) && |
| 327 | |
| 328 | # Ensure that the tree, but not the blob, is fetched |
| 329 | git -C repo rev-list --objects --missing=print $(cat treehash) >objects && |
| 330 | test_grep "^$(cat treehash)" objects && |
| 331 | test_grep "^[?]$(cat blobhash)" objects |
| 332 | ' |
| 333 | |
| 334 | test_expect_success 'rev-list stops traversal at missing and promised commit' ' |
| 335 | rm -rf repo && |
| 336 | test_create_repo repo && |
| 337 | test_commit -C repo foo && |
| 338 | test_commit -C repo bar && |
| 339 | |
| 340 | FOO=$(git -C repo rev-parse foo) && |
| 341 | promise_and_delete "$FOO" && |
| 342 | |
| 343 | git -C repo config core.repositoryformatversion 1 && |
| 344 | git -C repo config extensions.partialclone "arbitrary string" && |
| 345 | git -C repo rev-list --exclude-promisor-objects --objects bar >out && |
| 346 | test_grep $(git -C repo rev-parse bar) out && |
| 347 | test_grep ! $FOO out |
| 348 | ' |
| 349 | |
| 350 | test_expect_success 'missing tree objects with --missing=allow-promisor and --exclude-promisor-objects' ' |
| 351 | rm -rf repo && |
| 352 | test_create_repo repo && |
| 353 | test_commit -C repo foo && |
| 354 | test_commit -C repo bar && |
| 355 | test_commit -C repo baz && |
| 356 | |
| 357 | promise_and_delete $(git -C repo rev-parse bar^{tree}) && |
| 358 | promise_and_delete $(git -C repo rev-parse foo^{tree}) && |
| 359 | |
| 360 | git -C repo config core.repositoryformatversion 1 && |
| 361 | git -C repo config extensions.partialclone "arbitrary string" && |
| 362 | |
| 363 | git -C repo rev-list --missing=allow-promisor --objects HEAD >objs 2>rev_list_err && |
| 364 | test_must_be_empty rev_list_err && |
| 365 | # 3 commits, 3 blobs, and 1 tree |
| 366 | test_line_count = 7 objs && |
| 367 | |
| 368 | # Do the same for --exclude-promisor-objects, but with all trees gone. |
| 369 | promise_and_delete $(git -C repo rev-parse baz^{tree}) && |
| 370 | git -C repo rev-list --exclude-promisor-objects --objects HEAD >objs 2>rev_list_err && |
| 371 | test_must_be_empty rev_list_err && |
| 372 | # 3 commits, no blobs or trees |
| 373 | test_line_count = 3 objs |
| 374 | ' |
| 375 | |
| 376 | test_expect_success 'missing non-root tree object and rev-list' ' |
| 377 | rm -rf repo && |
| 378 | test_create_repo repo && |
| 379 | mkdir repo/dir && |
| 380 | echo foo >repo/dir/foo && |
| 381 | git -C repo add dir/foo && |
| 382 | git -C repo commit -m "commit dir/foo" && |
| 383 | |
| 384 | promise_and_delete $(git -C repo rev-parse HEAD:dir) && |
| 385 | |
| 386 | git -C repo config core.repositoryformatversion 1 && |
| 387 | git -C repo config extensions.partialclone "arbitrary string" && |
| 388 | |
| 389 | git -C repo rev-list --missing=allow-any --objects HEAD >objs 2>rev_list_err && |
| 390 | test_must_be_empty rev_list_err && |
| 391 | # 1 commit and 1 tree |
| 392 | test_line_count = 2 objs |
| 393 | ' |
| 394 | |
| 395 | test_expect_success 'rev-list stops traversal at missing and promised tree' ' |
| 396 | rm -rf repo && |
| 397 | test_create_repo repo && |
| 398 | test_commit -C repo foo && |
| 399 | mkdir repo/a_dir && |
| 400 | echo something >repo/a_dir/something && |
| 401 | git -C repo add a_dir/something && |
| 402 | git -C repo commit -m bar && |
| 403 | |
| 404 | # foo^{tree} (tree referenced from commit) |
| 405 | TREE=$(git -C repo rev-parse foo^{tree}) && |
| 406 | |
| 407 | # a tree referenced by HEAD^{tree} (tree referenced from tree) |
| 408 | TREE2=$(git -C repo ls-tree HEAD^{tree} | grep " tree " | head -1 | cut -b13-52) && |
| 409 | |
| 410 | promise_and_delete "$TREE" && |
| 411 | promise_and_delete "$TREE2" && |
| 412 | |
| 413 | git -C repo config core.repositoryformatversion 1 && |
| 414 | git -C repo config extensions.partialclone "arbitrary string" && |
| 415 | git -C repo rev-list --exclude-promisor-objects --objects HEAD >out && |
| 416 | test_grep $(git -C repo rev-parse foo) out && |
| 417 | test_grep ! $TREE out && |
| 418 | test_grep $(git -C repo rev-parse HEAD) out && |
| 419 | test_grep ! $TREE2 out |
| 420 | ' |
| 421 | |
| 422 | test_expect_success 'rev-list stops traversal at missing and promised blob' ' |
| 423 | rm -rf repo && |
| 424 | test_create_repo repo && |
| 425 | echo something >repo/something && |
| 426 | git -C repo add something && |
| 427 | git -C repo commit -m foo && |
| 428 | |
| 429 | BLOB=$(git -C repo hash-object -w something) && |
| 430 | promise_and_delete "$BLOB" && |
| 431 | |
| 432 | git -C repo config core.repositoryformatversion 1 && |
| 433 | git -C repo config extensions.partialclone "arbitrary string" && |
| 434 | git -C repo rev-list --exclude-promisor-objects --objects HEAD >out && |
| 435 | test_grep $(git -C repo rev-parse HEAD) out && |
| 436 | test_grep ! $BLOB out |
| 437 | ' |
| 438 | |
| 439 | test_expect_success 'rev-list stops traversal at promisor commit, tree, and blob' ' |
| 440 | rm -rf repo && |
| 441 | test_create_repo repo && |
| 442 | test_commit -C repo foo && |
| 443 | test_commit -C repo bar && |
| 444 | test_commit -C repo baz && |
| 445 | |
| 446 | COMMIT=$(git -C repo rev-parse foo) && |
| 447 | TREE=$(git -C repo rev-parse bar^{tree}) && |
| 448 | BLOB=$(git hash-object repo/baz.t) && |
| 449 | printf "%s\n%s\n%s\n" $COMMIT $TREE $BLOB | pack_as_from_promisor && |
| 450 | |
| 451 | git -C repo config core.repositoryformatversion 1 && |
| 452 | git -C repo config extensions.partialclone "arbitrary string" && |
| 453 | git -C repo rev-list --exclude-promisor-objects --objects HEAD >out && |
| 454 | test_grep ! $COMMIT out && |
| 455 | test_grep ! $TREE out && |
| 456 | test_grep ! $BLOB out && |
| 457 | test_grep $(git -C repo rev-parse bar) out # sanity check that some walking was done |
| 458 | ' |
| 459 | |
| 460 | test_expect_success 'rev-list dies for missing objects on cmd line' ' |
| 461 | rm -rf repo && |
| 462 | test_create_repo repo && |
| 463 | test_commit -C repo foo && |
| 464 | test_commit -C repo bar && |
| 465 | test_commit -C repo baz && |
| 466 | |
| 467 | COMMIT=$(git -C repo rev-parse foo) && |
| 468 | TREE=$(git -C repo rev-parse bar^{tree}) && |
| 469 | BLOB=$(git hash-object repo/baz.t) && |
| 470 | |
| 471 | promise_and_delete $COMMIT && |
| 472 | promise_and_delete $TREE && |
| 473 | promise_and_delete $BLOB && |
| 474 | |
| 475 | git -C repo config core.repositoryformatversion 1 && |
| 476 | git -C repo config extensions.partialclone "arbitrary string" && |
| 477 | |
| 478 | for OBJ in "$COMMIT" "$TREE" "$BLOB"; do |
| 479 | test_must_fail git -C repo rev-list --objects \ |
| 480 | --exclude-promisor-objects "$OBJ" && |
| 481 | test_must_fail git -C repo rev-list --objects-edge-aggressive \ |
| 482 | --exclude-promisor-objects "$OBJ" && |
| 483 | |
| 484 | # Do not die or crash when --ignore-missing is passed. |
| 485 | git -C repo rev-list --ignore-missing --objects \ |
| 486 | --exclude-promisor-objects "$OBJ" && |
| 487 | git -C repo rev-list --ignore-missing --objects-edge-aggressive \ |
| 488 | --exclude-promisor-objects "$OBJ" || return 1 |
| 489 | done |
| 490 | ' |
| 491 | |
| 492 | test_expect_success '--exclude-promisor-objects with ^@ on missing object' ' |
| 493 | rm -rf repo && |
| 494 | test_create_repo repo && |
| 495 | test_commit -C repo foo && |
| 496 | test_commit -C repo bar && |
| 497 | |
| 498 | COMMIT=$(git -C repo rev-parse foo) && |
| 499 | promise_and_delete "$COMMIT" && |
| 500 | |
| 501 | git -C repo config core.repositoryformatversion 1 && |
| 502 | git -C repo config extensions.partialclone "arbitrary string" && |
| 503 | |
| 504 | # Ensure that "$COMMIT^@" is handled gracefully even though the |
| 505 | # actual commits are missing. |
| 506 | git -C repo rev-list --exclude-promisor-objects "$COMMIT^@" >out && |
| 507 | test_must_be_empty out |
| 508 | ' |
| 509 | |
| 510 | test_expect_success 'single promisor remote can be re-initialized gracefully' ' |
| 511 | # ensure one promisor is in the promisors list |
| 512 | rm -rf repo && |
| 513 | test_create_repo repo && |
| 514 | test_create_repo other && |
| 515 | git -C repo remote add foo "file://$(pwd)/other" && |
| 516 | git -C repo config remote.foo.promisor true && |
| 517 | git -C repo config extensions.partialclone foo && |
| 518 | |
| 519 | # reinitialize the promisors list |
| 520 | git -C repo fetch --filter=blob:none foo |
| 521 | ' |
| 522 | |
| 523 | test_expect_success 'gc repacks promisor objects separately from non-promisor objects' ' |
| 524 | rm -rf repo && |
| 525 | test_create_repo repo && |
| 526 | test_commit -C repo one && |
| 527 | test_commit -C repo two && |
| 528 | |
| 529 | TREE_ONE=$(git -C repo rev-parse one^{tree}) && |
| 530 | printf "$TREE_ONE\n" | pack_as_from_promisor && |
| 531 | TREE_TWO=$(git -C repo rev-parse two^{tree}) && |
| 532 | printf "$TREE_TWO\n" | pack_as_from_promisor && |
| 533 | |
| 534 | git -C repo config core.repositoryformatversion 1 && |
| 535 | git -C repo config extensions.partialclone "arbitrary string" && |
| 536 | git -C repo gc && |
| 537 | |
| 538 | # Ensure that exactly one promisor packfile exists, and that it |
| 539 | # contains the trees but not the commits |
| 540 | ls repo/.git/objects/pack/pack-*.promisor >promisorlist && |
| 541 | test_line_count = 1 promisorlist && |
| 542 | PROMISOR_PACKFILE=$(sed "s/.promisor/.pack/" <promisorlist) && |
| 543 | git verify-pack $PROMISOR_PACKFILE -v >out && |
| 544 | test_grep "$TREE_ONE" out && |
| 545 | test_grep "$TREE_TWO" out && |
| 546 | test_grep ! "$(git -C repo rev-parse one)" out && |
| 547 | test_grep ! "$(git -C repo rev-parse two)" out && |
| 548 | |
| 549 | # Remove the promisor packfile and associated files |
| 550 | rm $(sed "s/.promisor//" <promisorlist).* && |
| 551 | |
| 552 | # Ensure that the single other pack contains the commits, but not the |
| 553 | # trees |
| 554 | ls repo/.git/objects/pack/pack-*.pack >packlist && |
| 555 | test_line_count = 1 packlist && |
| 556 | git verify-pack repo/.git/objects/pack/pack-*.pack -v >out && |
| 557 | test_grep "$(git -C repo rev-parse one)" out && |
| 558 | test_grep "$(git -C repo rev-parse two)" out && |
| 559 | test_grep ! "$TREE_ONE" out && |
| 560 | test_grep ! "$TREE_TWO" out |
| 561 | ' |
| 562 | |
| 563 | test_expect_success 'gc does not repack promisor objects if there are none' ' |
| 564 | rm -rf repo && |
| 565 | test_create_repo repo && |
| 566 | test_commit -C repo one && |
| 567 | |
| 568 | git -C repo config core.repositoryformatversion 1 && |
| 569 | git -C repo config extensions.partialclone "arbitrary string" && |
| 570 | git -C repo gc && |
| 571 | |
| 572 | # Ensure that only one pack exists |
| 573 | ls repo/.git/objects/pack/pack-*.pack >packlist && |
| 574 | test_line_count = 1 packlist |
| 575 | ' |
| 576 | |
| 577 | repack_and_check () { |
| 578 | rm -rf repo2 && |
| 579 | cp -r repo repo2 && |
| 580 | if test x"$1" = "x--must-fail" |
| 581 | then |
| 582 | shift |
| 583 | test_must_fail git -C repo2 repack $1 -d |
| 584 | else |
| 585 | git -C repo2 repack $1 -d |
| 586 | fi && |
| 587 | git -C repo2 fsck && |
| 588 | |
| 589 | git -C repo2 cat-file -e $2 && |
| 590 | git -C repo2 cat-file -e $3 |
| 591 | } |
| 592 | |
| 593 | test_expect_success 'repack -d does not irreversibly delete promisor objects' ' |
| 594 | rm -rf repo && |
| 595 | test_create_repo repo && |
| 596 | git -C repo config core.repositoryformatversion 1 && |
| 597 | git -C repo config extensions.partialclone "arbitrary string" && |
| 598 | |
| 599 | git -C repo commit --allow-empty -m one && |
| 600 | git -C repo commit --allow-empty -m two && |
| 601 | git -C repo commit --allow-empty -m three && |
| 602 | git -C repo commit --allow-empty -m four && |
| 603 | ONE=$(git -C repo rev-parse HEAD^^^) && |
| 604 | TWO=$(git -C repo rev-parse HEAD^^) && |
| 605 | THREE=$(git -C repo rev-parse HEAD^) && |
| 606 | |
| 607 | printf "$TWO\n" | pack_as_from_promisor && |
| 608 | printf "$THREE\n" | pack_as_from_promisor && |
| 609 | delete_object repo "$ONE" && |
| 610 | |
| 611 | repack_and_check --must-fail -ab "$TWO" "$THREE" && |
| 612 | repack_and_check -a "$TWO" "$THREE" && |
| 613 | repack_and_check -A "$TWO" "$THREE" && |
| 614 | repack_and_check -l "$TWO" "$THREE" |
| 615 | ' |
| 616 | |
| 617 | test_expect_success 'gc stops traversal when a missing but promised object is reached' ' |
| 618 | rm -rf repo && |
| 619 | test_create_repo repo && |
| 620 | test_commit -C repo my_commit && |
| 621 | |
| 622 | TREE_HASH=$(git -C repo rev-parse HEAD^{tree}) && |
| 623 | HASH=$(promise_and_delete $TREE_HASH) && |
| 624 | |
| 625 | git -C repo config core.repositoryformatversion 1 && |
| 626 | git -C repo config extensions.partialclone "arbitrary string" && |
| 627 | git -C repo gc && |
| 628 | |
| 629 | # Ensure that the promisor packfile still exists, and remove it |
| 630 | test -e repo/.git/objects/pack/pack-$HASH.pack && |
| 631 | rm repo/.git/objects/pack/pack-$HASH.* && |
| 632 | |
| 633 | # Ensure that the single other pack contains the commit, but not the tree |
| 634 | ls repo/.git/objects/pack/pack-*.pack >packlist && |
| 635 | test_line_count = 1 packlist && |
| 636 | git verify-pack repo/.git/objects/pack/pack-*.pack -v >out && |
| 637 | test_grep "$(git -C repo rev-parse HEAD)" out && |
| 638 | test_grep ! "$TREE_HASH" out |
| 639 | ' |
| 640 | |
| 641 | test_expect_success 'do not fetch when checking existence of tree we construct ourselves' ' |
| 642 | rm -rf repo && |
| 643 | test_create_repo repo && |
| 644 | test_commit -C repo base && |
| 645 | test_commit -C repo side1 && |
| 646 | git -C repo checkout base && |
| 647 | test_commit -C repo side2 && |
| 648 | |
| 649 | git -C repo config core.repositoryformatversion 1 && |
| 650 | git -C repo config extensions.partialclone "arbitrary string" && |
| 651 | |
| 652 | git -C repo cherry-pick side1 |
| 653 | ' |
| 654 | |
| 655 | test_expect_success 'exact rename does not need to fetch the blob lazily' ' |
| 656 | rm -rf repo partial.git && |
| 657 | test_create_repo repo && |
| 658 | content="some dummy content" && |
| 659 | test_commit -C repo create-a-file file.txt "$content" && |
| 660 | git -C repo mv file.txt new-file.txt && |
| 661 | git -C repo commit -m rename-the-file && |
| 662 | FILE_HASH=$(git -C repo rev-parse HEAD:new-file.txt) && |
| 663 | test_config -C repo uploadpack.allowfilter 1 && |
| 664 | test_config -C repo uploadpack.allowanysha1inwant 1 && |
| 665 | |
| 666 | git clone --filter=blob:none --bare "file://$(pwd)/repo" partial.git && |
| 667 | git -C partial.git rev-list --objects --missing=print HEAD >out && |
| 668 | test_grep "[?]$FILE_HASH" out && |
| 669 | git -C partial.git log --follow -- new-file.txt && |
| 670 | git -C partial.git rev-list --objects --missing=print HEAD >out && |
| 671 | test_grep "[?]$FILE_HASH" out |
| 672 | ' |
| 673 | |
| 674 | test_expect_success 'lazy-fetch when accessing object not in the_repository' ' |
| 675 | rm -rf full partial.git && |
| 676 | test_create_repo full && |
| 677 | test_commit -C full create-a-file file.txt && |
| 678 | |
| 679 | test_config -C full uploadpack.allowfilter 1 && |
| 680 | test_config -C full uploadpack.allowanysha1inwant 1 && |
| 681 | git clone --filter=blob:none --bare "file://$(pwd)/full" partial.git && |
| 682 | FILE_HASH=$(git -C full rev-parse HEAD:file.txt) && |
| 683 | |
| 684 | # Sanity check that the file is missing |
| 685 | git -C partial.git rev-list --objects --missing=print HEAD >out && |
| 686 | test_grep "[?]$FILE_HASH" out && |
| 687 | |
| 688 | # The no-lazy-fetch mechanism prevents Git from fetching |
| 689 | test_must_fail env GIT_NO_LAZY_FETCH=1 \ |
| 690 | git -C partial.git cat-file -e "$FILE_HASH" && |
| 691 | |
| 692 | # The same with command line option to "git" |
| 693 | test_must_fail git --no-lazy-fetch -C partial.git cat-file -e "$FILE_HASH" && |
| 694 | |
| 695 | # The same, forcing a subprocess via an alias |
| 696 | test_must_fail git --no-lazy-fetch -C partial.git \ |
| 697 | -c alias.foo="!git cat-file" foo -e "$FILE_HASH" && |
| 698 | |
| 699 | # Sanity check that the file is still missing |
| 700 | git -C partial.git rev-list --objects --missing=print HEAD >out && |
| 701 | test_grep "[?]$FILE_HASH" out && |
| 702 | |
| 703 | git -C full cat-file -s "$FILE_HASH" >expect && |
| 704 | test-tool partial-clone object-info partial.git "$FILE_HASH" >actual && |
| 705 | test_cmp expect actual && |
| 706 | |
| 707 | # Sanity check that the file is now present |
| 708 | git -C partial.git rev-list --objects --missing=print HEAD >out && |
| 709 | test_grep ! "[?]$FILE_HASH" out |
| 710 | ' |
| 711 | |
| 712 | test_expect_success 'push should not fetch new commit objects' ' |
| 713 | rm -rf server client && |
| 714 | test_create_repo server && |
| 715 | test_config -C server uploadpack.allowfilter 1 && |
| 716 | test_config -C server uploadpack.allowanysha1inwant 1 && |
| 717 | test_commit -C server server1 && |
| 718 | |
| 719 | git clone --filter=blob:none "file://$(pwd)/server" client && |
| 720 | test_commit -C client client1 && |
| 721 | |
| 722 | test_commit -C server server2 && |
| 723 | COMMIT=$(git -C server rev-parse server2) && |
| 724 | |
| 725 | test_must_fail git -C client push 2>err && |
| 726 | test_grep "fetch first" err && |
| 727 | git -C client rev-list --objects --missing=print "$COMMIT" >objects && |
| 728 | test_grep "^[?]$COMMIT" objects |
| 729 | ' |
| 730 | |
| 731 | test_expect_success 'setup for promisor.quiet tests' ' |
| 732 | rm -rf server && |
| 733 | test_create_repo server && |
| 734 | test_commit -C server foo && |
| 735 | git -C server rm foo.t && |
| 736 | git -C server commit -m remove && |
| 737 | git -C server config uploadpack.allowanysha1inwant 1 && |
| 738 | git -C server config uploadpack.allowfilter 1 && |
| 739 | |
| 740 | # Setup for submodule repo test: superproject whose submodule is a |
| 741 | # partial clone, so that promisor.quiet is read via a non-main repo. |
| 742 | rm -rf sub-pc-src sub-pc-srv.bare super-src super-work && |
| 743 | git init sub-pc-src && |
| 744 | test_commit -C sub-pc-src initial file.txt "hello" && |
| 745 | |
| 746 | git clone --bare sub-pc-src sub-pc-srv.bare && |
| 747 | git -C sub-pc-srv.bare config uploadpack.allowfilter 1 && |
| 748 | git -C sub-pc-srv.bare config uploadpack.allowanysha1inwant 1 && |
| 749 | |
| 750 | git init super-src && |
| 751 | git -C super-src -c protocol.file.allow=always \ |
| 752 | submodule add "file://$(pwd)/sub-pc-srv.bare" sub && |
| 753 | git -C super-src commit -m "add submodule" && |
| 754 | |
| 755 | git -c protocol.file.allow=always clone super-src super-work && |
| 756 | git -C super-work -c protocol.file.allow=always \ |
| 757 | submodule update --init --filter=blob:none sub && |
| 758 | |
| 759 | # Allow file:// in the submodule so that lazy-fetch subprocesses work. |
| 760 | git -C super-work/sub config protocol.file.allow always |
| 761 | ' |
| 762 | |
| 763 | test_expect_success TTY 'promisor.quiet=false shows progress messages' ' |
| 764 | rm -rf repo && |
| 765 | git clone --filter=blob:none "file://$(pwd)/server" repo && |
| 766 | git -C repo config promisor.quiet "false" && |
| 767 | |
| 768 | test_terminal git -C repo cat-file -p foo:foo.t 2>err && |
| 769 | |
| 770 | # Ensure that progress messages are written |
| 771 | test_grep "Receiving objects" err |
| 772 | ' |
| 773 | |
| 774 | test_expect_success TTY 'promisor.quiet=true does not show progress messages' ' |
| 775 | rm -rf repo && |
| 776 | git clone --filter=blob:none "file://$(pwd)/server" repo && |
| 777 | git -C repo config promisor.quiet "true" && |
| 778 | |
| 779 | test_terminal git -C repo cat-file -p foo:foo.t 2>err && |
| 780 | |
| 781 | # Ensure that no progress messages are written |
| 782 | test_grep ! "Receiving objects" err |
| 783 | ' |
| 784 | |
| 785 | test_expect_success TTY 'promisor.quiet=unconfigured shows progress messages' ' |
| 786 | rm -rf repo && |
| 787 | git clone --filter=blob:none "file://$(pwd)/server" repo && |
| 788 | |
| 789 | test_terminal git -C repo cat-file -p foo:foo.t 2>err && |
| 790 | |
| 791 | # Ensure that progress messages are written |
| 792 | test_grep "Receiving objects" err |
| 793 | ' |
| 794 | |
| 795 | test_expect_success 'promisor.quiet from submodule repo is honored' ' |
| 796 | rm -f pc-quiet-trace && |
| 797 | |
| 798 | # Set promisor.quiet only in the submodule, not the superproject. |
| 799 | git -C super-work/sub config promisor.quiet true && |
| 800 | |
| 801 | # Push a new commit+blob to the server; the blob stays missing in the |
| 802 | # partial-clone submodule until a lazy fetch is triggered. |
| 803 | test_commit -C sub-pc-src updated new-file.txt "world" && |
| 804 | git -C sub-pc-src push "$(pwd)/sub-pc-srv.bare" HEAD:master && |
| 805 | git -C super-work/sub -c protocol.file.allow=always fetch origin && |
| 806 | git -C super-work/sub reset --mixed origin/master && |
| 807 | |
| 808 | # grep descends into the submodule and triggers a lazy fetch for the |
| 809 | # missing blob; verify the fetch subprocess carries --quiet. |
| 810 | GIT_TRACE2_EVENT="$(pwd)/pc-quiet-trace" \ |
| 811 | git -C super-work grep --cached --recurse-submodules "world" \ |
| 812 | 2>/dev/null && |
| 813 | grep negotiationAlgorithm pc-quiet-trace | grep -e --quiet |
| 814 | ' |
| 815 | |
| 816 | . "$TEST_DIRECTORY"/lib-httpd.sh |
| 817 | start_httpd |
| 818 | |
| 819 | test_expect_success 'fetching of missing objects from an HTTP server' ' |
| 820 | rm -rf repo && |
| 821 | SERVER="$HTTPD_DOCUMENT_ROOT_PATH/server" && |
| 822 | test_create_repo "$SERVER" && |
| 823 | test_commit -C "$SERVER" foo && |
| 824 | git -C "$SERVER" repack -a -d --write-bitmap-index && |
| 825 | |
| 826 | git clone $HTTPD_URL/smart/server repo && |
| 827 | HASH=$(git -C repo rev-parse foo) && |
| 828 | rm -rf repo/.git/objects/* && |
| 829 | |
| 830 | git -C repo config core.repositoryformatversion 1 && |
| 831 | git -C repo config extensions.partialclone "origin" && |
| 832 | git -C repo cat-file -p "$HASH" && |
| 833 | |
| 834 | # Ensure that the .promisor file is written, and check that its |
| 835 | # associated packfile contains the object |
| 836 | ls repo/.git/objects/pack/pack-*.promisor >promisorlist && |
| 837 | test_line_count = 1 promisorlist && |
| 838 | IDX=$(sed "s/promisor$/idx/" promisorlist) && |
| 839 | git verify-pack --verbose "$IDX" >out && |
| 840 | test_grep "$HASH" out |
| 841 | ' |
| 842 | |
| 843 | # DO NOT add non-httpd-specific tests here, because the last part of this |
| 844 | # test script is only executed when httpd is available and enabled. |
| 845 | |
| 846 | test_done |