Raw
1 #!/bin/sh
2
3 test_description='git backfill on partial clones'
4
5 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
6 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
7
8 . ./test-lib.sh
9
10 test_expect_success 'backfill rejects unexpected arguments' '
11 test_must_fail git backfill unexpected-arg 2>err &&
12 test_grep "ambiguous argument .*unexpected-arg" err &&
13
14 test_must_fail git backfill --all --unexpected-arg --first-parent 2>err &&
15 test_grep "unrecognized argument: --unexpected-arg" err
16 '
17
18 test_expect_success 'backfill rejects incompatible filter options' '
19 test_must_fail git backfill --objects --filter=tree:1 2>err &&
20 test_grep "cannot backfill with these filter options" err &&
21
22 test_must_fail git backfill --objects --filter=blob:limit=10m 2>err &&
23 test_grep "cannot backfill with blob size limits" err
24 '
25
26 # We create objects in the 'src' repo.
27 test_expect_success 'setup repo for object creation' '
28 echo "{print \$1}" >print_1.awk &&
29 echo "{print \$2}" >print_2.awk &&
30
31 git init src &&
32
33 mkdir -p src/a/b/c &&
34 mkdir -p src/d/f &&
35
36 for i in 1 2
37 do
38 for n in 1 2 3 4
39 do
40 echo "Version $i of file $n" > src/file.$n.txt &&
41 echo "Version $i of file a/$n" > src/a/file.$n.txt &&
42 echo "Version $i of file a/b/$n" > src/a/b/file.$n.txt &&
43 echo "Version $i of file a/b/c/$n" > src/a/b/c/file.$n.txt &&
44 echo "Version $i of file d/$n" > src/d/file.$n.txt &&
45 echo "Version $i of file d/f/$n" > src/d/f/file.$n.txt &&
46 git -C src add . &&
47 test_tick &&
48 git -C src commit -m "Iteration $n" || return 1
49 done
50 done
51 '
52
53 # Clone 'src' into 'srv.bare' so we have a bare repo to be our origin
54 # server for the partial clone.
55 test_expect_success 'setup bare clone for server' '
56 git clone --bare "file://$(pwd)/src" srv.bare &&
57 git -C srv.bare config --local uploadpack.allowfilter 1 &&
58 git -C srv.bare config --local uploadpack.allowanysha1inwant 1
59 '
60
61 # Create a version of the repo with branches for testing revision
62 # arguments like --all, --first-parent, and --since.
63 #
64 # main: 8 commits (linear) + merge of side branch
65 # 48 original blobs + 4 side blobs = 52 blobs from main HEAD
66 # side: 2 commits adding s/file.{1,2}.txt (v1, v2), merged into main
67 # other: 1 commit adding o/file.{1,2}.txt (not merged)
68 # 54 total blobs reachable from --all
69 test_expect_success 'setup branched repo for revision tests' '
70 git clone src src-revs &&
71
72 # Side branch from tip of main with unique files
73 git -C src-revs checkout -b side HEAD &&
74 mkdir -p src-revs/s &&
75 echo "Side version 1 of file 1" >src-revs/s/file.1.txt &&
76 echo "Side version 1 of file 2" >src-revs/s/file.2.txt &&
77 test_tick &&
78 git -C src-revs add . &&
79 git -C src-revs commit -m "Side commit 1" &&
80
81 echo "Side version 2 of file 1" >src-revs/s/file.1.txt &&
82 echo "Side version 2 of file 2" >src-revs/s/file.2.txt &&
83 test_tick &&
84 git -C src-revs add . &&
85 git -C src-revs commit -m "Side commit 2" &&
86
87 # Merge side into main
88 git -C src-revs checkout main &&
89 test_tick &&
90 git -C src-revs merge side --no-ff -m "Merge side branch" &&
91
92 # Other branch (not merged) for --all testing
93 git -C src-revs checkout -b other main~1 &&
94 mkdir -p src-revs/o &&
95 echo "Other content 1" >src-revs/o/file.1.txt &&
96 echo "Other content 2" >src-revs/o/file.2.txt &&
97 test_tick &&
98 git -C src-revs add . &&
99 git -C src-revs commit -m "Other commit" &&
100
101 git -C src-revs checkout main &&
102
103 git clone --bare "file://$(pwd)/src-revs" srv-revs.bare &&
104 git -C srv-revs.bare config --local uploadpack.allowfilter 1 &&
105 git -C srv-revs.bare config --local uploadpack.allowanysha1inwant 1
106 '
107
108 # do basic partial clone from "srv.bare"
109 test_expect_success 'do partial clone 1, backfill gets all objects' '
110 git clone --no-checkout --filter=blob:none \
111 --single-branch --branch=main \
112 "file://$(pwd)/srv.bare" backfill1 &&
113
114 # Backfill with no options gets everything reachable from HEAD.
115 GIT_TRACE2_EVENT="$(pwd)/backfill-file-trace" git \
116 -C backfill1 backfill &&
117
118 # We should have engaged the partial clone machinery
119 test_trace2_data promisor fetch_count 48 <backfill-file-trace &&
120
121 # No more missing objects!
122 git -C backfill1 rev-list --quiet --objects --missing=print HEAD >revs2 &&
123 test_line_count = 0 revs2
124 '
125
126 test_expect_success 'do partial clone 2, backfill min batch size' '
127 git clone --no-checkout --filter=blob:none \
128 --single-branch --branch=main \
129 "file://$(pwd)/srv.bare" backfill2 &&
130
131 GIT_TRACE2_EVENT="$(pwd)/batch-trace" git \
132 -C backfill2 backfill --min-batch-size=20 &&
133
134 # Batches were used
135 test_trace2_data promisor fetch_count 20 <batch-trace >matches &&
136 test_line_count = 2 matches &&
137 test_trace2_data promisor fetch_count 8 <batch-trace &&
138
139 # No more missing objects!
140 git -C backfill2 rev-list --quiet --objects --missing=print HEAD >revs2 &&
141 test_line_count = 0 revs2
142 '
143
144 test_expect_success 'backfill --sparse without sparse-checkout fails' '
145 git init not-sparse &&
146 test_must_fail git -C not-sparse backfill --sparse 2>err &&
147 grep "problem loading sparse-checkout" err
148 '
149
150 test_expect_success 'backfill --sparse' '
151 git clone --sparse --filter=blob:none \
152 --single-branch --branch=main \
153 "file://$(pwd)/srv.bare" backfill3 &&
154
155 # Initial checkout includes four files at root.
156 git -C backfill3 rev-list --quiet --objects --missing=print HEAD >missing &&
157 test_line_count = 44 missing &&
158
159 # Initial sparse-checkout is just the files at root, so we get the
160 # older versions of the four files at tip.
161 GIT_TRACE2_EVENT="$(pwd)/sparse-trace1" git \
162 -C backfill3 backfill --sparse &&
163 test_trace2_data promisor fetch_count 4 <sparse-trace1 &&
164 test_trace2_data path-walk paths 5 <sparse-trace1 &&
165 git -C backfill3 rev-list --quiet --objects --missing=print HEAD >missing &&
166 test_line_count = 40 missing &&
167
168 # Expand the sparse-checkout to include 'd' recursively. This
169 # engages the algorithm to skip the trees for 'a'. Note that
170 # the "sparse-checkout set" command downloads the objects at tip
171 # to satisfy the current checkout.
172 git -C backfill3 sparse-checkout set d &&
173 GIT_TRACE2_EVENT="$(pwd)/sparse-trace2" git \
174 -C backfill3 backfill --sparse &&
175 test_trace2_data promisor fetch_count 8 <sparse-trace2 &&
176 test_trace2_data path-walk paths 15 <sparse-trace2 &&
177 git -C backfill3 rev-list --quiet --objects --missing=print HEAD >missing &&
178 test_line_count = 24 missing &&
179
180 # Disabling the --sparse option (on by default) will download everything
181 git -C backfill3 backfill --no-sparse &&
182 git -C backfill3 rev-list --quiet --objects --missing=print HEAD >missing &&
183 test_line_count = 0 missing
184 '
185
186 test_expect_success 'backfill auto-detects sparse-checkout from config' '
187 git clone --sparse --filter=blob:none \
188 --single-branch --branch=main \
189 "file://$(pwd)/srv.bare" backfill-auto-sparse &&
190
191 git -C backfill-auto-sparse rev-list --quiet --objects --missing=print HEAD >missing &&
192 test_line_count = 44 missing &&
193
194 GIT_TRACE2_EVENT="$(pwd)/auto-sparse-trace" git \
195 -C backfill-auto-sparse backfill &&
196
197 test_trace2_data promisor fetch_count 4 <auto-sparse-trace &&
198 test_trace2_data path-walk paths 5 <auto-sparse-trace
199 '
200
201 test_expect_success 'backfill --sparse without cone mode (positive)' '
202 git clone --no-checkout --filter=blob:none \
203 --single-branch --branch=main \
204 "file://$(pwd)/srv.bare" backfill4 &&
205
206 # No blobs yet
207 git -C backfill4 rev-list --quiet --objects --missing=print HEAD >missing &&
208 test_line_count = 48 missing &&
209
210 # Define sparse-checkout by filename regardless of parent directory.
211 # This downloads 6 blobs to satisfy the checkout.
212 git -C backfill4 sparse-checkout set --no-cone "**/file.1.txt" &&
213 git -C backfill4 checkout main &&
214
215 # Track new blob count
216 git -C backfill4 rev-list --quiet --objects --missing=print HEAD >missing &&
217 test_line_count = 42 missing &&
218
219 GIT_TRACE2_EVENT="$(pwd)/no-cone-trace1" git \
220 -C backfill4 backfill --sparse &&
221 test_trace2_data promisor fetch_count 6 <no-cone-trace1 &&
222
223 # This walk needed to visit all directories to search for these paths.
224 test_trace2_data path-walk paths 12 <no-cone-trace1 &&
225 git -C backfill4 rev-list --quiet --objects --missing=print HEAD >missing &&
226 test_line_count = 36 missing
227 '
228
229 test_expect_success 'backfill --sparse without cone mode (negative)' '
230 git clone --no-checkout --filter=blob:none \
231 --single-branch --branch=main \
232 "file://$(pwd)/srv.bare" backfill5 &&
233
234 # No blobs yet
235 git -C backfill5 rev-list --quiet --objects --missing=print HEAD >missing &&
236 test_line_count = 48 missing &&
237
238 # Define sparse-checkout by filename regardless of parent directory.
239 # This downloads 18 blobs to satisfy the checkout
240 git -C backfill5 sparse-checkout set --no-cone "**/file*" "!**/file.1.txt" &&
241 git -C backfill5 checkout main &&
242
243 # Track new blob count
244 git -C backfill5 rev-list --quiet --objects --missing=print HEAD >missing &&
245 test_line_count = 30 missing &&
246
247 GIT_TRACE2_EVENT="$(pwd)/no-cone-trace2" git \
248 -C backfill5 backfill --sparse &&
249 test_trace2_data promisor fetch_count 18 <no-cone-trace2 &&
250
251 # This walk needed to visit all directories to search for these paths, plus
252 # 12 extra "file.?.txt" paths than the previous test.
253 test_trace2_data path-walk paths 24 <no-cone-trace2 &&
254 git -C backfill5 rev-list --quiet --objects --missing=print HEAD >missing &&
255 test_line_count = 12 missing
256 '
257
258 test_expect_success 'backfill with revision range' '
259 test_when_finished rm -rf backfill-revs &&
260 git clone --no-checkout --filter=blob:none \
261 --single-branch --branch=main \
262 "file://$(pwd)/srv.bare" backfill-revs &&
263
264 # No blobs yet
265 git -C backfill-revs rev-list --quiet --objects --missing=print HEAD >missing &&
266 test_line_count = 48 missing &&
267
268 GIT_TRACE2_EVENT="$(pwd)/backfill-trace" git -C backfill-revs backfill HEAD~2..HEAD &&
269
270 # 36 objects downloaded, 12 still missing
271 test_trace2_data promisor fetch_count 36 <backfill-trace &&
272 git -C backfill-revs rev-list --quiet --objects --missing=print HEAD >missing &&
273 test_line_count = 12 missing
274 '
275
276 test_expect_success 'backfill with revisions over stdin' '
277 test_when_finished rm -rf backfill-revs &&
278 git clone --no-checkout --filter=blob:none \
279 --single-branch --branch=main \
280 "file://$(pwd)/srv.bare" backfill-revs &&
281
282 # No blobs yet
283 git -C backfill-revs rev-list --quiet --objects --missing=print HEAD >missing &&
284 test_line_count = 48 missing &&
285
286 cat >in <<-EOF &&
287 HEAD
288 ^HEAD~2
289 EOF
290
291 GIT_TRACE2_EVENT="$(pwd)/backfill-trace" git -C backfill-revs backfill --stdin <in &&
292
293 # 36 objects downloaded, 12 still missing
294 test_trace2_data promisor fetch_count 36 <backfill-trace &&
295 git -C backfill-revs rev-list --quiet --objects --missing=print HEAD >missing &&
296 test_line_count = 12 missing
297 '
298
299 test_expect_success 'backfill with prefix pathspec' '
300 test_when_finished rm -rf backfill-path &&
301 git clone --bare --filter=blob:none \
302 --single-branch --branch=main \
303 "file://$(pwd)/srv.bare" backfill-path &&
304
305 # No blobs yet
306 git -C backfill-path rev-list --quiet --objects --missing=print HEAD >missing &&
307 test_line_count = 48 missing &&
308
309 git -C backfill-path backfill HEAD -- d/f 2>err &&
310 test_must_be_empty err &&
311
312 git -C backfill-path rev-list --quiet --objects --missing=print HEAD >missing &&
313 test_line_count = 40 missing
314 '
315
316 test_expect_success 'backfill with multiple pathspecs' '
317 test_when_finished rm -rf backfill-path &&
318 git clone --bare --filter=blob:none \
319 --single-branch --branch=main \
320 "file://$(pwd)/srv.bare" backfill-path &&
321
322 # No blobs yet
323 git -C backfill-path rev-list --quiet --objects --missing=print HEAD >missing &&
324 test_line_count = 48 missing &&
325
326 git -C backfill-path backfill HEAD -- d/f a 2>err &&
327 test_must_be_empty err &&
328
329 git -C backfill-path rev-list --quiet --objects --missing=print HEAD >missing &&
330 test_line_count = 16 missing
331 '
332
333 test_expect_success 'backfill with wildcard pathspec' '
334 test_when_finished rm -rf backfill-path &&
335 git clone --bare --filter=blob:none \
336 --single-branch --branch=main \
337 "file://$(pwd)/srv.bare" backfill-path &&
338
339 # No blobs yet
340 git -C backfill-path rev-list --quiet --objects --missing=print HEAD >missing &&
341 test_line_count = 48 missing &&
342
343 git -C backfill-path backfill HEAD -- "d/file.*.txt" 2>err &&
344 test_must_be_empty err &&
345
346 git -C backfill-path rev-list --quiet --objects --missing=print HEAD >missing &&
347 test_line_count = 40 missing
348 '
349
350 test_expect_success 'backfill with --all' '
351 test_when_finished rm -rf backfill-all &&
352 git clone --no-checkout --filter=blob:none \
353 "file://$(pwd)/srv-revs.bare" backfill-all &&
354
355 # All blobs from all refs are missing
356 git -C backfill-all rev-list --quiet --objects --all --missing=print >missing &&
357 test_line_count = 54 missing &&
358
359 # Backfill from HEAD gets main blobs only
360 git -C backfill-all backfill HEAD &&
361
362 # Other branch blobs still missing
363 git -C backfill-all rev-list --quiet --objects --all --missing=print >missing &&
364 test_line_count = 2 missing &&
365
366 # Backfill with --all gets everything
367 git -C backfill-all backfill --all &&
368
369 git -C backfill-all rev-list --quiet --objects --all --missing=print >missing &&
370 test_line_count = 0 missing
371 '
372
373 test_expect_success 'backfill with --first-parent' '
374 test_when_finished rm -rf backfill-fp &&
375 git clone --no-checkout --filter=blob:none \
376 --single-branch --branch=main \
377 "file://$(pwd)/srv-revs.bare" backfill-fp &&
378
379 git -C backfill-fp rev-list --quiet --objects --missing=print HEAD >missing &&
380 test_line_count = 52 missing &&
381
382 # --first-parent skips the side branch commits, so
383 # s/file.{1,2}.txt v1 blobs (only in side commit 1) are missed.
384 git -C backfill-fp backfill --first-parent HEAD &&
385
386 git -C backfill-fp rev-list --quiet --objects --missing=print HEAD >missing &&
387 test_line_count = 2 missing
388 '
389
390 test_expect_success 'backfill with --since' '
391 test_when_finished rm -rf backfill-since &&
392 git clone --no-checkout --filter=blob:none \
393 --single-branch --branch=main \
394 "file://$(pwd)/srv-revs.bare" backfill-since &&
395
396 git -C backfill-since rev-list --quiet --objects --missing=print HEAD >missing &&
397 test_line_count = 52 missing &&
398
399 # Use a cutoff between commits 4 and 5 (between v1 and v2
400 # iterations). Commits 5-8 still carry v1 of files 2-4 in
401 # their trees, but v1 of file.1.txt is only in commits 1-4.
402 SINCE=$(git -C backfill-since log --first-parent --reverse \
403 --format=%ct HEAD~1 | sed -n 5p) &&
404 git -C backfill-since backfill --since="@$((SINCE - 1))" HEAD &&
405
406 # 6 missing: v1 of file.1.txt in all 6 directories
407 git -C backfill-since rev-list --quiet --objects --missing=print HEAD >missing &&
408 test_line_count = 6 missing
409 '
410
411 test_expect_success 'backfill range with include-edges enables fetch-free git-log' '
412 git clone --no-checkout --filter=blob:none \
413 --single-branch --branch=main \
414 "file://$(pwd)/srv.bare" backfill-log &&
415
416 # Backfill the range with default include edges.
417 git -C backfill-log backfill HEAD~2..HEAD &&
418
419 # git log -p needs edge blobs for the "before" side of
420 # diffs. With edge inclusion, all needed blobs are local.
421 GIT_TRACE2_EVENT="$(pwd)/log-trace" git \
422 -C backfill-log log -p HEAD~2..HEAD >log-output &&
423
424 # No promisor fetches should have been needed.
425 ! grep "fetch_count" log-trace
426 '
427
428 test_expect_success 'backfill range without include edges causes on-demand fetches in git-log' '
429 git clone --no-checkout --filter=blob:none \
430 --single-branch --branch=main \
431 "file://$(pwd)/srv.bare" backfill-log-no-bdy &&
432
433 # Backfill WITHOUT include edges -- file.3 v1 blobs are missing.
434 git -C backfill-log-no-bdy backfill --no-include-edges HEAD~2..HEAD &&
435
436 # git log -p HEAD~2..HEAD computes diff of commit 7 against
437 # commit 6. It needs file.3 v1 (the "before" side), which was
438 # not backfilled. This triggers on-demand promisor fetches.
439 GIT_TRACE2_EVENT="$(pwd)/log-no-bdy-trace" git \
440 -C backfill-log-no-bdy log -p HEAD~2..HEAD >log-output &&
441
442 grep "fetch_count" log-no-bdy-trace
443 '
444
445 test_expect_success 'backfill range enables fetch-free replay' '
446 # Create a repo with a branch to replay.
447 git init replay-src &&
448 (
449 cd replay-src &&
450 git config uploadpack.allowfilter 1 &&
451 git config uploadpack.allowanysha1inwant 1 &&
452 test_commit base &&
453 git checkout -b topic &&
454 test_commit topic-change &&
455 git checkout main &&
456 test_commit main-change
457 ) &&
458 git clone --bare --filter=blob:none \
459 "file://$(pwd)/replay-src" replay-dest.git &&
460
461 # Backfill the replay range: --onto main, replaying topic~1..topic.
462 # For replay, we need TARGET^! plus the range.
463 main_oid=$(git -C replay-dest.git rev-parse main) &&
464 topic_oid=$(git -C replay-dest.git rev-parse topic) &&
465 base_oid=$(git -C replay-dest.git rev-parse topic~1) &&
466 git -C replay-dest.git backfill \
467 "$main_oid^!" "$base_oid..$topic_oid" &&
468
469 # Now replay should complete without any promisor fetches.
470 GIT_TRACE2_EVENT="$(pwd)/replay-trace" git -C replay-dest.git \
471 replay --onto main topic~1..topic >replay-out &&
472
473 ! grep "fetch_count" replay-trace
474 '
475
476 test_expect_success 'backfill enables fetch-free merge' '
477 # Create a repo with two branches to merge.
478 git init merge-src &&
479 (
480 cd merge-src &&
481 git config uploadpack.allowfilter 1 &&
482 git config uploadpack.allowanysha1inwant 1 &&
483 test_commit merge-base &&
484 git checkout -b side &&
485 test_commit side-change &&
486 git checkout main &&
487 test_commit main-side-change
488 ) &&
489 git clone --filter=blob:none \
490 "file://$(pwd)/merge-src" merge-dest &&
491
492 # The clone checked out main, fetching its blobs.
493 # Backfill the three endpoint commits needed for merge.
494 main_oid=$(git -C merge-dest rev-parse origin/main) &&
495 side_oid=$(git -C merge-dest rev-parse origin/side) &&
496 mbase=$(git -C merge-dest merge-base origin/main origin/side) &&
497 git -C merge-dest backfill --no-include-edges \
498 "$main_oid^!" "$side_oid^!" "$mbase^!" &&
499
500 # Merge should complete without promisor fetches.
501 GIT_TRACE2_EVENT="$(pwd)/merge-trace" git -C merge-dest \
502 merge origin/side -m "test merge" &&
503
504 ! grep "fetch_count" merge-trace
505 '
506
507 . "$TEST_DIRECTORY"/lib-httpd.sh
508 start_httpd
509
510 test_expect_success 'create a partial clone over HTTP' '
511 SERVER="$HTTPD_DOCUMENT_ROOT_PATH/server" &&
512 rm -rf "$SERVER" repo &&
513 git clone --bare "file://$(pwd)/src" "$SERVER" &&
514 test_config -C "$SERVER" uploadpack.allowfilter 1 &&
515 test_config -C "$SERVER" uploadpack.allowanysha1inwant 1 &&
516
517 git clone --no-checkout --filter=blob:none \
518 "$HTTPD_URL/smart/server" backfill-http
519 '
520
521 test_expect_success 'backfilling over HTTP succeeds' '
522 GIT_TRACE2_EVENT="$(pwd)/backfill-http-trace" git \
523 -C backfill-http backfill &&
524
525 # We should have engaged the partial clone machinery
526 test_trace2_data promisor fetch_count 48 <backfill-http-trace &&
527
528 # Confirm all objects are present, none missing.
529 git -C backfill-http rev-list --objects --all >rev-list-out &&
530 awk "{print \$1;}" <rev-list-out >oids &&
531 GIT_TRACE2_EVENT="$(pwd)/walk-trace" git -C backfill-http \
532 cat-file --batch-check <oids >batch-out &&
533 ! grep missing batch-out
534 '
535
536 # DO NOT add non-httpd-specific tests here, because the last part of this
537 # test script is only executed when httpd is available and enabled.
538
539 test_done