Raw
1 #!/bin/sh
2
3 test_description='git maintenance builtin'
4
5 . ./test-lib.sh
6
7 GIT_TEST_COMMIT_GRAPH=0
8 GIT_TEST_MULTI_PACK_INDEX=0
9
10 # Ensure that auto-maintenance detaches as usual.
11 sane_unset GIT_TEST_MAINT_AUTO_DETACH
12
13 test_lazy_prereq XMLLINT '
14 xmllint --version
15 '
16
17 test_xmllint () {
18 if test_have_prereq XMLLINT
19 then
20 xmllint --noout "$@"
21 else
22 true
23 fi
24 }
25
26 test_maintenance_tasks () {
27 cat >expect &&
28 sed -ne "s/.*\"region_enter\".*\"category\":\"maintenance\([^\"]*\)\".*\"label\":\"\([^\"][^\"]*\)\".*/\2\1/p" "$1" >actual &&
29 test_cmp expect actual
30 }
31
32 test_lazy_prereq SYSTEMD_ANALYZE '
33 systemd-analyze verify /lib/systemd/system/basic.target
34 '
35
36 test_systemd_analyze_verify () {
37 if test_have_prereq SYSTEMD_ANALYZE
38 then
39 systemd-analyze verify "$@"
40 fi
41 }
42
43 test_expect_success 'help text' '
44 git maintenance -h >actual &&
45 test_grep "usage: git maintenance <subcommand>" actual &&
46 test_expect_code 129 git maintenance barf 2>err &&
47 test_grep "unknown subcommand: \`barf'\''" err &&
48 test_grep "usage: git maintenance" err &&
49 test_expect_code 129 git maintenance 2>err &&
50 test_grep "error: need a subcommand" err &&
51 test_grep "usage: git maintenance" err
52 '
53
54 test_expect_success 'run [--auto|--quiet] with gc strategy' '
55 test_config maintenance.strategy gc &&
56 GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" \
57 git maintenance run 2>/dev/null &&
58 GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" \
59 git maintenance run --auto 2>/dev/null &&
60 GIT_TRACE2_EVENT="$(pwd)/run-no-quiet.txt" \
61 git maintenance run --no-quiet 2>/dev/null &&
62 git maintenance is-needed &&
63 test_subcommand git gc --quiet --no-detach --skip-foreground-tasks <run-no-auto.txt &&
64 ! git maintenance is-needed --auto &&
65 test_subcommand ! git gc --auto --quiet --no-detach --skip-foreground-tasks <run-auto.txt &&
66 test_subcommand git gc --no-quiet --no-detach --skip-foreground-tasks <run-no-quiet.txt
67 '
68
69 test_expect_success 'maintenance.auto config option' '
70 GIT_TRACE2_EVENT="$(pwd)/default" git commit --quiet --allow-empty -m 1 &&
71 test_subcommand git maintenance run --auto --quiet --detach <default &&
72 GIT_TRACE2_EVENT="$(pwd)/true" \
73 git -c maintenance.auto=true \
74 commit --quiet --allow-empty -m 2 &&
75 test_subcommand git maintenance run --auto --quiet --detach <true &&
76 GIT_TRACE2_EVENT="$(pwd)/false" \
77 git -c maintenance.auto=false \
78 commit --quiet --allow-empty -m 3 &&
79 test_subcommand ! git maintenance run --auto --quiet --detach <false
80 '
81
82 test_expect_success 'gc.auto config option' '
83 GIT_TRACE2_EVENT="$(pwd)/default" git commit --quiet --allow-empty -m 1 &&
84 test_subcommand git maintenance run --auto --quiet --detach <default &&
85 GIT_TRACE2_EVENT="$(pwd)/true" \
86 git -c gc.auto=1 commit --quiet --allow-empty -m 2 &&
87 test_subcommand git maintenance run --auto --quiet --detach <true &&
88 GIT_TRACE2_EVENT="$(pwd)/false" \
89 git -c gc.auto=0 commit --quiet --allow-empty -m 3 &&
90 test_subcommand ! git maintenance run --auto --quiet --detach <false
91 '
92
93 test_expect_success 'maintenance.auto overrides gc.auto' '
94 test_when_finished "rm -f trace" &&
95
96 test_config maintenance.auto false &&
97 test_config gc.auto 1 &&
98 GIT_TRACE2_EVENT="$(pwd)/trace" git commit --quiet --allow-empty -m 1 &&
99 test_subcommand ! git maintenance run --auto --quiet --detach <trace &&
100
101 test_config maintenance.auto true &&
102 test_config gc.auto 0 &&
103 GIT_TRACE2_EVENT="$(pwd)/trace" git commit --quiet --allow-empty -m 1 &&
104 test_subcommand git maintenance run --auto --quiet --detach <trace
105 '
106
107 for cfg in maintenance.autoDetach gc.autoDetach
108 do
109 test_expect_success "$cfg=true config option" '
110 test_when_finished "rm -f trace" &&
111 test_config $cfg true &&
112 GIT_TRACE2_EVENT="$(pwd)/trace" git commit --quiet --allow-empty -m 1 &&
113 test_subcommand git maintenance run --auto --quiet --detach <trace
114 '
115
116 test_expect_success "$cfg=false config option" '
117 test_when_finished "rm -f trace" &&
118 test_config $cfg false &&
119 GIT_TRACE2_EVENT="$(pwd)/trace" git commit --quiet --allow-empty -m 1 &&
120 test_subcommand git maintenance run --auto --quiet --no-detach <trace
121 '
122 done
123
124 test_expect_success "maintenance.autoDetach overrides gc.autoDetach" '
125 test_when_finished "rm -f trace" &&
126 test_config maintenance.autoDetach false &&
127 test_config gc.autoDetach true &&
128 GIT_TRACE2_EVENT="$(pwd)/trace" git commit --quiet --allow-empty -m 1 &&
129 test_subcommand git maintenance run --auto --quiet --no-detach <trace
130 '
131
132 test_expect_success 'register uses XDG_CONFIG_HOME config if it exists' '
133 test_when_finished rm -r .config/git/config &&
134 (
135 # Override HOME so that .gitconfig (which test-lib.sh may
136 # have created, e.g. to set safe.bareRepository) does not
137 # take precedence over the XDG location.
138 HOME=$PWD/must-not-exist &&
139 XDG_CONFIG_HOME=.config &&
140 export HOME XDG_CONFIG_HOME &&
141 mkdir -p $XDG_CONFIG_HOME/git &&
142 >$XDG_CONFIG_HOME/git/config &&
143 git maintenance register &&
144 git config --file=$XDG_CONFIG_HOME/git/config --get maintenance.repo >actual &&
145 pwd >expect &&
146 test_cmp expect actual
147 )
148 '
149
150 test_expect_success 'register does not need XDG_CONFIG_HOME config to exist' '
151 test_when_finished git maintenance unregister &&
152 test_path_is_missing $XDG_CONFIG_HOME/git/config &&
153 git maintenance register &&
154 git config --global --get maintenance.repo >actual &&
155 pwd >expect &&
156 test_cmp expect actual
157 '
158
159 test_expect_success 'unregister uses XDG_CONFIG_HOME config if it exists' '
160 test_when_finished rm -r .config/git/config &&
161 (
162 # Override HOME so that .gitconfig (which test-lib.sh may
163 # have created, e.g. to set safe.bareRepository) does not
164 # take precedence over the XDG location.
165 HOME=$PWD/must-not-exist &&
166 XDG_CONFIG_HOME=.config &&
167 export HOME XDG_CONFIG_HOME &&
168 mkdir -p $XDG_CONFIG_HOME/git &&
169 >$XDG_CONFIG_HOME/git/config &&
170 git maintenance register &&
171 git maintenance unregister &&
172 test_must_fail git config --file=$XDG_CONFIG_HOME/git/config --get maintenance.repo >actual &&
173 test_must_be_empty actual
174 )
175 '
176
177 test_expect_success 'unregister does not need XDG_CONFIG_HOME config to exist' '
178 test_path_is_missing $XDG_CONFIG_HOME/git/config &&
179 git maintenance register &&
180 git maintenance unregister &&
181 test_must_fail git config --global --get maintenance.repo >actual &&
182 test_must_be_empty actual
183 '
184
185 test_expect_success 'maintenance.<task>.enabled' '
186 git config maintenance.gc.enabled false &&
187 git config maintenance.commit-graph.enabled true &&
188 GIT_TRACE2_EVENT="$(pwd)/run-config.txt" git maintenance run 2>err &&
189 test_maintenance_tasks run-config.txt <<-\EOF
190 commit-graph
191 EOF
192 '
193
194 test_expect_success 'run --task=<task>' '
195 GIT_TRACE2_EVENT="$(pwd)/run-commit-graph.txt" \
196 git maintenance run --task=commit-graph 2>/dev/null &&
197 GIT_TRACE2_EVENT="$(pwd)/run-gc.txt" \
198 git maintenance run --task=gc 2>/dev/null &&
199 GIT_TRACE2_EVENT="$(pwd)/run-both.txt" \
200 git maintenance run --task=commit-graph --task=gc 2>/dev/null &&
201 test_maintenance_tasks run-commit-graph.txt <<-\EOF &&
202 commit-graph
203 EOF
204 test_maintenance_tasks run-gc.txt <<-\EOF &&
205 gc foreground
206 gc
207 EOF
208 test_maintenance_tasks run-both.txt <<-\EOF
209 gc foreground
210 commit-graph
211 gc
212 EOF
213 '
214
215 test_expect_success 'core.commitGraph=false prevents write process' '
216 GIT_TRACE2_EVENT="$(pwd)/no-commit-graph.txt" \
217 git -c core.commitGraph=false maintenance run \
218 --task=commit-graph 2>/dev/null &&
219 test_subcommand ! git commit-graph write --split --reachable --no-progress \
220 <no-commit-graph.txt
221 '
222
223 test_expect_success 'commit-graph auto condition' '
224 COMMAND="maintenance run --task=commit-graph --auto --quiet" &&
225
226 GIT_TRACE2_EVENT="$(pwd)/cg-no.txt" \
227 git -c maintenance.commit-graph.auto=1 $COMMAND &&
228 GIT_TRACE2_EVENT="$(pwd)/cg-negative-means-yes.txt" \
229 git -c maintenance.commit-graph.auto="-1" $COMMAND &&
230
231 test_commit first &&
232
233 ! git -c maintenance.commit-graph.auto=0 \
234 maintenance is-needed --auto --task=commit-graph &&
235 git -c maintenance.commit-graph.auto=1 \
236 maintenance is-needed --auto --task=commit-graph &&
237
238 GIT_TRACE2_EVENT="$(pwd)/cg-zero-means-no.txt" \
239 git -c maintenance.commit-graph.auto=0 $COMMAND &&
240 GIT_TRACE2_EVENT="$(pwd)/cg-one-satisfied.txt" \
241 git -c maintenance.commit-graph.auto=1 $COMMAND &&
242
243 git commit --allow-empty -m "second" &&
244 git commit --allow-empty -m "third" &&
245
246 GIT_TRACE2_EVENT="$(pwd)/cg-two-satisfied.txt" \
247 git -c maintenance.commit-graph.auto=2 $COMMAND &&
248
249 test_maintenance_tasks cg-no.txt <<-\EOF &&
250 EOF
251 test_maintenance_tasks cg-negative-means-yes.txt <<-\EOF &&
252 commit-graph
253 EOF
254 test_maintenance_tasks cg-zero-means-no.txt <<-\EOF &&
255 EOF
256 test_maintenance_tasks cg-one-satisfied.txt <<-\EOF &&
257 commit-graph
258 EOF
259 test_maintenance_tasks cg-two-satisfied.txt <<-\EOF
260 commit-graph
261 EOF
262 '
263
264 test_expect_success 'commit-graph auto condition with merges' '
265 test_when_finished "rm -rf repo" &&
266 git init repo &&
267 (
268 cd repo &&
269 git config set maintenance.auto false &&
270 test_commit initial &&
271 git switch --create feature &&
272 test_commit feature-1 &&
273 test_commit feature-2 &&
274 git switch - &&
275 test_commit main-1 &&
276 test_commit main-2 &&
277 git merge feature &&
278
279 # We have 6 commits, none of which are covered by a commit
280 # graph. So this must be the boundary at which we start to
281 # perform maintenance.
282 test_must_fail git -c maintenance.commit-graph.auto=7 \
283 maintenance is-needed --auto --task=commit-graph &&
284 git -c maintenance.commit-graph.auto=6 \
285 maintenance is-needed --auto --task=commit-graph
286 )
287 '
288
289 test_expect_success 'run --task=bogus' '
290 test_must_fail git maintenance run --task=bogus 2>err &&
291 test_grep "is not a valid task" err
292 '
293
294 test_expect_success 'run --task duplicate' '
295 test_must_fail git maintenance run --task=gc --task=gc 2>err &&
296 test_grep "cannot be selected multiple times" err
297 '
298
299 test_expect_success 'run --task=prefetch with no remotes' '
300 git maintenance run --task=prefetch 2>err &&
301 test_must_be_empty err
302 '
303
304 test_expect_success 'prefetch multiple remotes' '
305 git clone . clone1 &&
306 git clone . clone2 &&
307 git remote add remote1 "file://$(pwd)/clone1" &&
308 git remote add remote2 "file://$(pwd)/clone2" &&
309 git -C clone1 switch -c one &&
310 git -C clone2 switch -c two &&
311 test_commit -C clone1 one &&
312 test_commit -C clone2 two &&
313 GIT_TRACE2_EVENT="$(pwd)/run-prefetch.txt" git maintenance run --task=prefetch 2>/dev/null &&
314 fetchargs="--prefetch --prune --no-tags --no-write-fetch-head --recurse-submodules=no --quiet" &&
315 test_subcommand git fetch remote1 $fetchargs <run-prefetch.txt &&
316 test_subcommand git fetch remote2 $fetchargs <run-prefetch.txt &&
317 git for-each-ref refs/remotes >actual &&
318 test_must_be_empty actual &&
319 git log prefetch/remotes/remote1/one &&
320 git log prefetch/remotes/remote2/two &&
321 git fetch --all &&
322 test_cmp_rev refs/remotes/remote1/one refs/prefetch/remotes/remote1/one &&
323 test_cmp_rev refs/remotes/remote2/two refs/prefetch/remotes/remote2/two &&
324
325 git log --oneline --decorate --all >log &&
326 test_grep ! "prefetch" log &&
327
328 test_when_finished git config --unset remote.remote1.skipFetchAll &&
329 git config remote.remote1.skipFetchAll true &&
330 GIT_TRACE2_EVENT="$(pwd)/skip-remote1.txt" git maintenance run --task=prefetch 2>/dev/null &&
331 test_subcommand ! git fetch remote1 $fetchargs <skip-remote1.txt &&
332 test_subcommand git fetch remote2 $fetchargs <skip-remote1.txt
333 '
334
335 test_expect_success 'loose-objects task' '
336 # Repack everything so we know the state of the object dir
337 git repack -adk &&
338
339 # Hack to stop maintenance from running during "git commit"
340 echo in use >.git/objects/maintenance.lock &&
341
342 # Assuming that "git commit" creates at least one loose object
343 test_commit create-loose-object &&
344 rm .git/objects/maintenance.lock &&
345
346 ls .git/objects >obj-dir-before &&
347 test_file_not_empty obj-dir-before &&
348 ls .git/objects/pack/*.pack >packs-before &&
349 test_line_count = 1 packs-before &&
350
351 # The first run creates a pack-file
352 # but does not delete loose objects.
353 git maintenance run --task=loose-objects &&
354 ls .git/objects >obj-dir-between &&
355 test_cmp obj-dir-before obj-dir-between &&
356 ls .git/objects/pack/*.pack >packs-between &&
357 test_line_count = 2 packs-between &&
358 ls .git/objects/pack/loose-*.pack >loose-packs &&
359 test_line_count = 1 loose-packs &&
360
361 # The second run deletes loose objects
362 # but does not create a pack-file.
363 git maintenance run --task=loose-objects &&
364 ls .git/objects >obj-dir-after &&
365 cat >expect <<-\EOF &&
366 info
367 pack
368 EOF
369 test_cmp expect obj-dir-after &&
370 ls .git/objects/pack/*.pack >packs-after &&
371 test_cmp packs-between packs-after
372 '
373
374 test_expect_success 'maintenance.loose-objects.auto' '
375 git repack -adk &&
376 GIT_TRACE2_EVENT="$(pwd)/trace-lo1.txt" \
377 git -c maintenance.loose-objects.auto=1 maintenance \
378 run --auto --task=loose-objects 2>/dev/null &&
379 test_subcommand ! git prune-packed --quiet <trace-lo1.txt &&
380
381 printf data-A | git hash-object -t blob --stdin -w &&
382 ! git -c maintenance.loose-objects.auto=2 \
383 maintenance is-needed --auto --task=loose-objects &&
384 GIT_TRACE2_EVENT="$(pwd)/trace-loA" \
385 git -c maintenance.loose-objects.auto=2 \
386 maintenance run --auto --task=loose-objects 2>/dev/null &&
387 test_subcommand ! git prune-packed --quiet <trace-loA &&
388
389 printf data-B | git hash-object -t blob --stdin -w &&
390 git -c maintenance.loose-objects.auto=2 \
391 maintenance is-needed --auto --task=loose-objects &&
392 GIT_TRACE2_EVENT="$(pwd)/trace-loB" \
393 git -c maintenance.loose-objects.auto=2 \
394 maintenance run --auto --task=loose-objects 2>/dev/null &&
395 test_subcommand git prune-packed --quiet <trace-loB &&
396
397 GIT_TRACE2_EVENT="$(pwd)/trace-loC" \
398 git -c maintenance.loose-objects.auto=2 \
399 maintenance run --auto --task=loose-objects 2>/dev/null &&
400 test_subcommand git prune-packed --quiet <trace-loC
401 '
402
403 test_expect_success 'maintenance.loose-objects.batchSize' '
404 git init loose-batch &&
405
406 # This creates three objects per commit.
407 test_commit_bulk -C loose-batch 34 &&
408 pack=$(ls loose-batch/.git/objects/pack/pack-*.pack) &&
409 index="${pack%pack}idx" &&
410 rm "$index" &&
411 git -C loose-batch unpack-objects <"$pack" &&
412 git -C loose-batch config maintenance.loose-objects.batchSize 50 &&
413
414 GIT_PROGRESS_DELAY=0 \
415 git -C loose-batch maintenance run --no-quiet --task=loose-objects 2>err &&
416 test_grep "Enumerating objects: 50, done." err &&
417
418 GIT_PROGRESS_DELAY=0 \
419 git -C loose-batch maintenance run --no-quiet --task=loose-objects 2>err &&
420 test_grep "Enumerating objects: 50, done." err &&
421
422 GIT_PROGRESS_DELAY=0 \
423 git -C loose-batch maintenance run --no-quiet --task=loose-objects 2>err &&
424 test_grep "Enumerating objects: 2, done." err &&
425
426 GIT_PROGRESS_DELAY=0 \
427 git -C loose-batch maintenance run --no-quiet --task=loose-objects 2>err &&
428 test_must_be_empty err
429 '
430
431 test_expect_success 'incremental-repack task' '
432 packDir=.git/objects/pack &&
433 for i in $(test_seq 1 5)
434 do
435 test_commit $i || return 1
436 done &&
437
438 # Create three disjoint pack-files with size BIG, small, small.
439 echo HEAD~2 | git pack-objects --revs $packDir/test-1 &&
440 test_tick &&
441 git pack-objects --revs $packDir/test-2 <<-\EOF &&
442 HEAD~1
443 ^HEAD~2
444 EOF
445 test_tick &&
446 git pack-objects --revs $packDir/test-3 <<-\EOF &&
447 HEAD
448 ^HEAD~1
449 EOF
450
451 # Delete refs that have not been repacked in these packs.
452 git for-each-ref --format="delete %(refname)" \
453 refs/prefetch refs/tags refs/remotes \
454 --exclude=refs/remotes/*/HEAD >refs &&
455 git update-ref --stdin <refs &&
456
457 # Replace the object directory with this pack layout.
458 rm -f $packDir/pack-* &&
459 rm -f $packDir/loose-* &&
460 ls $packDir/*.pack >packs-before &&
461 test_line_count = 3 packs-before &&
462
463 # make sure we do not have any broken refs that were
464 # missed in the deletion above
465 git for-each-ref &&
466
467 # the job repacks the two into a new pack, but does not
468 # delete the old ones.
469 git maintenance run --task=incremental-repack &&
470 ls $packDir/*.pack >packs-between &&
471 test_line_count = 4 packs-between &&
472
473 # the job deletes the two old packs, and does not write
474 # a new one because the batch size is not high enough to
475 # pack the largest pack-file.
476 git maintenance run --task=incremental-repack &&
477 ls .git/objects/pack/*.pack >packs-after &&
478 test_line_count = 2 packs-after
479 '
480
481 test_expect_success EXPENSIVE 'incremental-repack 2g limit' '
482 test_when_finished rm -rf expensive-repo &&
483 git init expensive-repo &&
484 (
485 cd expensive-repo &&
486 git config set core.compression 0 &&
487 git config set maintenance.auto false &&
488
489 for i in $(test_seq 1 5)
490 do
491 test-tool genrandom foo$i $((512 * 1024 * 1024 + 1)) >>big ||
492 return 1
493 done &&
494 git add big &&
495 git commit -qm "Add big file (1)" &&
496
497 # ensure any possible loose objects are in a pack-file
498 git maintenance run --task=loose-objects &&
499
500 rm big &&
501 for i in $(test_seq 6 10)
502 do
503 test-tool genrandom foo$i $((512 * 1024 * 1024 + 1)) >>big ||
504 return 1
505 done &&
506 git add big &&
507 git commit -qm "Add big file (2)" &&
508
509 # ensure any possible loose objects are in a pack-file
510 git maintenance run --task=loose-objects &&
511
512 # Now run the incremental-repack task and check the batch-size
513 GIT_TRACE2_EVENT="$(pwd)/run-2g.txt" git maintenance run \
514 --task=incremental-repack 2>/dev/null &&
515 test_subcommand git multi-pack-index repack \
516 --no-progress --batch-size=2147483647 <run-2g.txt
517 )
518 '
519
520 run_incremental_repack_and_verify () {
521 test_commit A &&
522 git repack -adk &&
523 git multi-pack-index write &&
524 ! git -c maintenance.incremental-repack.auto=1 \
525 maintenance is-needed --auto --task=incremental-repack &&
526 GIT_TRACE2_EVENT="$(pwd)/midx-init.txt" git \
527 -c maintenance.incremental-repack.auto=1 \
528 maintenance run --auto --task=incremental-repack 2>/dev/null &&
529 test_subcommand ! git multi-pack-index write --no-progress <midx-init.txt &&
530
531 test_commit B &&
532 git pack-objects --revs .git/objects/pack/pack <<-\EOF &&
533 HEAD
534 ^HEAD~1
535 EOF
536 GIT_TRACE2_EVENT=$(pwd)/trace-A git \
537 -c maintenance.incremental-repack.auto=2 \
538 maintenance run --auto --task=incremental-repack 2>/dev/null &&
539 test_subcommand ! git multi-pack-index write --no-progress <trace-A &&
540
541 test_commit C &&
542 git pack-objects --revs .git/objects/pack/pack <<-\EOF &&
543 HEAD
544 ^HEAD~1
545 EOF
546 git -c maintenance.incremental-repack.auto=2 \
547 maintenance is-needed --auto --task=incremental-repack &&
548 GIT_TRACE2_EVENT=$(pwd)/trace-B git \
549 -c maintenance.incremental-repack.auto=2 \
550 maintenance run --auto --task=incremental-repack 2>/dev/null &&
551 test_subcommand git multi-pack-index write --no-progress <trace-B
552 }
553
554 test_expect_success 'maintenance.incremental-repack.auto' '
555 rm -rf incremental-repack-true &&
556 git init incremental-repack-true &&
557 (
558 cd incremental-repack-true &&
559 git config core.multiPackIndex true &&
560 git config maintenance.auto false &&
561 run_incremental_repack_and_verify
562 )
563 '
564
565 test_expect_success 'maintenance.incremental-repack.auto (when config is unset)' '
566 rm -rf incremental-repack-unset &&
567 git init incremental-repack-unset &&
568 (
569 cd incremental-repack-unset &&
570 test_unconfig core.multiPackIndex &&
571 git config maintenance.auto false &&
572 run_incremental_repack_and_verify
573 )
574 '
575
576 run_and_verify_geometric_pack () {
577 EXPECTED_PACKS="$1" &&
578
579 # Verify that we perform a geometric repack.
580 rm -f "trace2.txt" &&
581 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
582 git maintenance run --task=geometric-repack 2>/dev/null &&
583 test_subcommand git repack -d -l -q --geometric=2 \
584 --write-midx <trace2.txt &&
585
586 # Verify that the number of packfiles matches our expectation.
587 ls -l .git/objects/pack/*.pack >packfiles &&
588 test_line_count = "$EXPECTED_PACKS" packfiles &&
589
590 # And verify that there are no loose objects anymore.
591 git count-objects -v >count &&
592 test_grep '^count: 0$' count &&
593
594 # Verify that no orphaned .idx files were left behind. On
595 # Windows, a missing odb_to_close causes the parent to hold
596 # mmap handles on .idx files, silently preventing their
597 # deletion by the child git-repack process.
598 ls .git/objects/pack/pack-*.idx .git/objects/pack/pack-*.pack |
599 sed "s/\.pack$/.idx/" |
600 sort | uniq -u >orphaned-idx &&
601 test_must_be_empty orphaned-idx
602 }
603
604 test_expect_success 'geometric repacking task' '
605 test_when_finished "rm -rf repo" &&
606 git init repo &&
607 (
608 cd repo &&
609 git config set maintenance.auto false &&
610 test_commit initial &&
611
612 # The initial repack causes an all-into-one repack.
613 GIT_TRACE2_EVENT="$(pwd)/initial-repack.txt" \
614 git maintenance run --task=geometric-repack 2>/dev/null &&
615 test_subcommand git repack -d -l -q --cruft --cruft-expiration=2.weeks.ago \
616 --write-midx <initial-repack.txt &&
617
618 # Repacking should now cause a no-op geometric repack because
619 # no packfiles need to be combined.
620 ls -l .git/objects/pack/*.pack >before &&
621 run_and_verify_geometric_pack 1 &&
622 ls -l .git/objects/pack/*.pack >after &&
623 test_cmp before after &&
624
625 # This incremental change creates a new packfile that only
626 # soaks up loose objects. The packfiles are not getting merged
627 # at this point.
628 test_commit loose &&
629 run_and_verify_geometric_pack 2 &&
630
631 # Both packfiles have 3 objects, so the next run would cause us
632 # to merge all packfiles together. This should be turned into
633 # an all-into-one-repack.
634 GIT_TRACE2_EVENT="$(pwd)/all-into-one-repack.txt" \
635 git maintenance run --task=geometric-repack 2>/dev/null &&
636 test_subcommand git repack -d -l -q --cruft --cruft-expiration=2.weeks.ago \
637 --write-midx <all-into-one-repack.txt &&
638
639 # The geometric repack soaks up unreachable objects.
640 echo blob-1 | git hash-object -w --stdin -t blob &&
641 run_and_verify_geometric_pack 2 &&
642
643 # A second unreachable object should be written into another packfile.
644 echo blob-2 | git hash-object -w --stdin -t blob &&
645 run_and_verify_geometric_pack 3 &&
646
647 # And these two small packs should now be merged via the
648 # geometric repack. The large packfile should remain intact.
649 cp -R .git/objects .git/objects.save &&
650 run_and_verify_geometric_pack 2 &&
651
652 # On Windows, verify the same with legacy delete semantics
653 # that reject deletion of mmap-held .idx files.
654 if test_have_prereq MINGW
655 then
656 rm -rf .git/objects &&
657 mv .git/objects.save .git/objects &&
658 test_env GIT_TEST_LEGACY_DELETE=1 \
659 run_and_verify_geometric_pack 2
660 fi &&
661
662 # If we now add two more objects and repack twice we should
663 # then see another all-into-one repack. This time around
664 # though, as we have unreachable objects, we should also see a
665 # cruft pack.
666 echo blob-3 | git hash-object -w --stdin -t blob &&
667 echo blob-4 | git hash-object -w --stdin -t blob &&
668 run_and_verify_geometric_pack 3 &&
669 GIT_TRACE2_EVENT="$(pwd)/cruft-repack.txt" \
670 git maintenance run --task=geometric-repack 2>/dev/null &&
671 test_subcommand git repack -d -l -q --cruft --cruft-expiration=2.weeks.ago \
672 --write-midx <cruft-repack.txt &&
673 ls .git/objects/pack/*.pack >packs &&
674 test_line_count = 2 packs &&
675 ls .git/objects/pack/*.mtimes >cruft &&
676 test_line_count = 1 cruft
677 )
678 '
679
680 test_geometric_repack_needed () {
681 NEEDED="$1"
682 GEOMETRIC_CONFIG="$2" &&
683 rm -f trace2.txt &&
684 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
685 git ${GEOMETRIC_CONFIG:+-c maintenance.geometric-repack.$GEOMETRIC_CONFIG} \
686 maintenance run --auto --task=geometric-repack 2>/dev/null &&
687 case "$NEEDED" in
688 true)
689 test_grep "\[\"git\",\"repack\"," trace2.txt;;
690 false)
691 ! test_grep "\[\"git\",\"repack\"," trace2.txt;;
692 *)
693 BUG "invalid parameter: $NEEDED";;
694 esac
695 }
696
697 test_expect_success 'geometric repacking with --auto' '
698 test_when_finished "rm -rf repo" &&
699 git init repo &&
700 (
701 cd repo &&
702 git config set maintenance.auto false &&
703
704 # An empty repository does not need repacking, except when
705 # explicitly told to do it.
706 test_geometric_repack_needed false &&
707 test_geometric_repack_needed false auto=0 &&
708 test_geometric_repack_needed false auto=1 &&
709 test_geometric_repack_needed true auto=-1 &&
710
711 test_oid_init &&
712
713 # Loose objects cause a repack when crossing the limit. Note
714 # that the number of objects gets extrapolated by having a look
715 # at the "objects/17/" shard.
716 test_commit "$(test_oid blob17_1)" &&
717 test_geometric_repack_needed false &&
718 test_commit "$(test_oid blob17_2)" &&
719 test_geometric_repack_needed false auto=257 &&
720 test_geometric_repack_needed true auto=256 &&
721
722 # Force another repack.
723 test_commit first &&
724 test_commit second &&
725 test_geometric_repack_needed true auto=-1 &&
726
727 # We now have two packfiles that would be merged together. As
728 # such, the repack should always happen unless the user has
729 # disabled the auto task.
730 test_geometric_repack_needed false auto=0 &&
731 test_geometric_repack_needed true auto=9000
732 )
733 '
734
735 test_expect_success 'geometric repacking honors configured split factor' '
736 test_when_finished "rm -rf repo" &&
737 git init repo &&
738 (
739 cd repo &&
740 git config set maintenance.auto false &&
741
742 # Create three different packs with 9, 2 and 1 object, respectively.
743 # This is done so that only a subset of packs would be merged
744 # together so that we can verify that `git repack` receives the
745 # correct geometric factor.
746 for i in $(test_seq 9)
747 do
748 echo first-$i | git hash-object -w --stdin -t blob || return 1
749 done &&
750 git repack --geometric=2 -d &&
751
752 for i in $(test_seq 2)
753 do
754 echo second-$i | git hash-object -w --stdin -t blob || return 1
755 done &&
756 git repack --geometric=2 -d &&
757
758 echo third | git hash-object -w --stdin -t blob &&
759 git repack --geometric=2 -d &&
760
761 test_geometric_repack_needed false splitFactor=2 &&
762 test_geometric_repack_needed true splitFactor=3 &&
763 test_subcommand git repack -d -l -q --geometric=3 --write-midx <trace2.txt
764 )
765 '
766
767 test_expect_success 'pre-auto-gc hook runs exactly once' '
768 test_when_finished "rm -rf repo" &&
769 git init repo &&
770 (
771 cd repo &&
772 write_script .git/hooks/pre-auto-gc <<-\EOF &&
773 echo hook >>hook.log
774 EOF
775
776 # Satisfy the auto condition for multiple tasks, both in the
777 # foreground and in the background phase.
778 git config set maintenance.reflog-expire.auto -1 &&
779 git config set maintenance.geometric-repack.auto -1 &&
780 git config set maintenance.rerere-gc.auto -1 &&
781
782 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
783 git maintenance run --auto 2>/dev/null &&
784
785 # The successful hook does not inhibit any of the tasks...
786 test_maintenance_tasks trace2.txt <<-\EOF &&
787 reflog-expire foreground
788 geometric-repack
789 rerere-gc
790 EOF
791 # ... but it must only have been executed a single time.
792 test_line_count = 1 hook.log
793 )
794 '
795
796 test_expect_success 'pre-auto-gc hook can inhibit geometric strategy' '
797 test_when_finished "rm -rf repo" &&
798 git init repo &&
799 (
800 cd repo &&
801 write_script .git/hooks/pre-auto-gc <<-\EOF &&
802 echo hook >>hook.log
803 exit 1
804 EOF
805
806 git config set maintenance.reflog-expire.auto -1 &&
807 git config set maintenance.geometric-repack.auto -1 &&
808 git config set maintenance.rerere-gc.auto -1 &&
809
810 # Maintenance would be required...
811 git maintenance is-needed --auto &&
812
813 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
814 git maintenance run --auto 2>/dev/null &&
815
816 # ... but the failing hook inhibits all tasks. The hook itself
817 # is expected to be the only child process being spawned, and
818 # it must only run a single time.
819 test_grep "child_start.*pre-auto-gc" trace2.txt &&
820 test_maintenance_tasks trace2.txt <<-\EOF &&
821 EOF
822 test_line_count = 1 hook.log
823 )
824 '
825
826 test_expect_success 'pre-auto-gc hook can inhibit gc strategy' '
827 test_when_finished "rm -rf repo" &&
828 git init repo &&
829 (
830 cd repo &&
831 write_script .git/hooks/pre-auto-gc <<-\EOF &&
832 echo hook >>hook.log
833 exit 1
834 EOF
835
836 git config set maintenance.strategy gc &&
837 git config set maintenance.auto false &&
838 git config set gc.auto 3 &&
839
840 test_oid_init &&
841
842 # We need to create two objects whose hashes start with 17
843 # since this is what the gc task counts.
844 test_commit "$(test_oid blob17_1)" &&
845 test_commit "$(test_oid blob17_2)" &&
846
847 # Maintenance would be required...
848 git maintenance is-needed --auto &&
849
850 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
851 git maintenance run --auto 2>/dev/null &&
852
853 # ... but the failing hook inhibits all tasks. The hook itself
854 # is expected to be the only child process being spawned, and
855 # it must only run a single time.
856 test_grep "child_start.*pre-auto-gc" trace2.txt &&
857 test_maintenance_tasks trace2.txt <<-\EOF &&
858 EOF
859 test_subcommand_flex ! git trace2 &&
860 test_line_count = 1 hook.log
861 )
862 '
863
864 test_expect_success 'pre-auto-gc hook does not run when no maintenance is needed' '
865 test_when_finished "rm -rf repo" &&
866 git init repo &&
867 (
868 cd repo &&
869 write_script .git/hooks/pre-auto-gc <<-\EOF &&
870 echo hook >>hook.log
871 EOF
872 test_must_fail git maintenance is-needed --auto &&
873 git maintenance run --auto 2>/dev/null &&
874 test_path_is_missing hook.log
875 )
876 '
877
878 test_expect_success 'pre-auto-gc hook does not run without --auto' '
879 test_when_finished "rm -rf repo" &&
880 git init repo &&
881 test_hook -C repo pre-auto-gc <<-\EOF &&
882 echo hook >>hook.log
883 EOF
884 (
885 cd repo &&
886 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
887 git maintenance run 2>/dev/null &&
888 test_grep "\[\"git\",\"repack\"," trace2.txt &&
889 test_path_is_missing hook.log
890 )
891 '
892
893 test_expect_success 'pack-refs task' '
894 for n in $(test_seq 1 5)
895 do
896 git branch -f to-pack/$n HEAD || return 1
897 done &&
898 GIT_TRACE2_EVENT="$(pwd)/pack-refs.txt" \
899 git maintenance run --task=pack-refs &&
900 test_subcommand git pack-refs --all --prune <pack-refs.txt
901 '
902
903 test_expect_success 'reflog-expire task' '
904 GIT_TRACE2_EVENT="$(pwd)/reflog-expire.txt" \
905 git maintenance run --task=reflog-expire &&
906 test_subcommand git reflog expire --all <reflog-expire.txt
907 '
908
909 test_expect_success 'reflog-expire task --auto only packs when exceeding limits' '
910 git reflog expire --all --expire=now &&
911 test_commit reflog-one &&
912 test_commit reflog-two &&
913
914 ! git -c maintenance.reflog-expire.auto=3 \
915 maintenance is-needed --auto --task=reflog-expire &&
916 GIT_TRACE2_EVENT="$(pwd)/reflog-expire-auto.txt" \
917 git -c maintenance.reflog-expire.auto=3 maintenance run --auto --task=reflog-expire &&
918 test_subcommand ! git reflog expire --all <reflog-expire-auto.txt &&
919
920 git -c maintenance.reflog-expire.auto=2 \
921 maintenance is-needed --auto --task=reflog-expire &&
922 GIT_TRACE2_EVENT="$(pwd)/reflog-expire-auto.txt" \
923 git -c maintenance.reflog-expire.auto=2 maintenance run --auto --task=reflog-expire &&
924 test_subcommand git reflog expire --all <reflog-expire-auto.txt
925 '
926
927 test_expect_worktree_prune () {
928 negate=
929 if test "$1" = "!"
930 then
931 negate="!"
932 shift
933 fi
934
935 rm -f "worktree-prune.txt" &&
936 GIT_TRACE2_EVENT="$(pwd)/worktree-prune.txt" "$@" &&
937 test_subcommand $negate git worktree prune --expire 3.months.ago <worktree-prune.txt
938 }
939
940 test_expect_success 'worktree-prune task without --auto always prunes' '
941 test_expect_worktree_prune git maintenance run --task=worktree-prune
942 '
943
944 test_expect_success 'worktree-prune task --auto only prunes with prunable worktree' '
945 test_expect_worktree_prune ! git maintenance run --auto --task=worktree-prune &&
946 mkdir .git/worktrees &&
947 : >.git/worktrees/abc &&
948 git maintenance is-needed --auto --task=worktree-prune &&
949 test_expect_worktree_prune git maintenance run --auto --task=worktree-prune
950 '
951
952 test_expect_success 'worktree-prune task with --auto honors maintenance.worktree-prune.auto' '
953 # A negative value should always prune.
954 test_expect_worktree_prune git -c maintenance.worktree-prune.auto=-1 maintenance run --auto --task=worktree-prune &&
955
956 mkdir .git/worktrees &&
957 : >.git/worktrees/first &&
958 : >.git/worktrees/second &&
959 : >.git/worktrees/third &&
960
961 # Zero should never prune.
962 test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=0 maintenance run --auto --task=worktree-prune &&
963 # A positive value should require at least this many prunable worktrees.
964 test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=4 maintenance run --auto --task=worktree-prune &&
965 git -c maintenance.worktree-prune.auto=3 maintenance is-needed --auto --task=worktree-prune &&
966 test_expect_worktree_prune git -c maintenance.worktree-prune.auto=3 maintenance run --auto --task=worktree-prune
967 '
968
969 test_expect_success 'worktree-prune task honors gc.worktreePruneExpire' '
970 git worktree add worktree &&
971 rm -rf worktree &&
972
973 rm -f worktree-prune.txt &&
974 ! git -c gc.worktreePruneExpire=1.week.ago maintenance is-needed --auto --task=worktree-prune &&
975 GIT_TRACE2_EVENT="$(pwd)/worktree-prune.txt" git -c gc.worktreePruneExpire=1.week.ago maintenance run --auto --task=worktree-prune &&
976 test_subcommand ! git worktree prune --expire 1.week.ago <worktree-prune.txt &&
977 test_path_is_dir .git/worktrees/worktree &&
978
979 rm -f worktree-prune.txt &&
980 git -c gc.worktreePruneExpire=now maintenance is-needed --auto --task=worktree-prune &&
981 GIT_TRACE2_EVENT="$(pwd)/worktree-prune.txt" git -c gc.worktreePruneExpire=now maintenance run --auto --task=worktree-prune &&
982 test_subcommand git worktree prune --expire now <worktree-prune.txt &&
983 test_path_is_missing .git/worktrees/worktree
984 '
985
986 test_expect_rerere_gc () {
987 negate=
988 if test "$1" = "!"
989 then
990 negate="!"
991 shift
992 fi
993
994 rm -f "rerere-gc.txt" &&
995 GIT_TRACE2_EVENT="$(pwd)/rerere-gc.txt" "$@" &&
996 test_subcommand $negate git rerere gc <rerere-gc.txt
997 }
998
999 test_expect_success 'rerere-gc task without --auto always collects garbage' '
1000 test_expect_rerere_gc git maintenance run --task=rerere-gc
1001 '
1002
1003 test_expect_success 'rerere-gc task with --auto only prunes with prunable entries' '
1004 test_when_finished "rm -rf .git/rr-cache" &&
1005 ! git maintenance is-needed --auto --task=rerere-gc &&
1006 test_expect_rerere_gc ! git maintenance run --auto --task=rerere-gc &&
1007 mkdir .git/rr-cache &&
1008 ! git maintenance is-needed --auto --task=rerere-gc &&
1009 test_expect_rerere_gc ! git maintenance run --auto --task=rerere-gc &&
1010 : >.git/rr-cache/entry &&
1011 git maintenance is-needed --auto --task=rerere-gc &&
1012 test_expect_rerere_gc git maintenance run --auto --task=rerere-gc
1013 '
1014
1015 test_expect_success 'rerere-gc task with --auto honors maintenance.rerere-gc.auto' '
1016 test_when_finished "rm -rf .git/rr-cache" &&
1017
1018 # A negative value should always prune.
1019 git -c maintenance.rerere-gc.auto=-1 maintenance is-needed --auto --task=rerere-gc &&
1020 test_expect_rerere_gc git -c maintenance.rerere-gc.auto=-1 maintenance run --auto --task=rerere-gc &&
1021
1022 # A positive value prunes when there is at least one entry.
1023 ! git -c maintenance.rerere-gc.auto=9000 maintenance is-needed --auto --task=rerere-gc &&
1024 test_expect_rerere_gc ! git -c maintenance.rerere-gc.auto=9000 maintenance run --auto --task=rerere-gc &&
1025 mkdir .git/rr-cache &&
1026 ! git -c maintenance.rerere-gc.auto=9000 maintenance is-needed --auto --task=rerere-gc &&
1027 test_expect_rerere_gc ! git -c maintenance.rerere-gc.auto=9000 maintenance run --auto --task=rerere-gc &&
1028 : >.git/rr-cache/entry-1 &&
1029 git -c maintenance.rerere-gc.auto=9000 maintenance is-needed --auto --task=rerere-gc &&
1030 test_expect_rerere_gc git -c maintenance.rerere-gc.auto=9000 maintenance run --auto --task=rerere-gc &&
1031
1032 # Zero should never prune.
1033 : >.git/rr-cache/entry-1 &&
1034 ! git -c maintenance.rerere-gc.auto=0 maintenance is-needed --auto --task=rerere-gc &&
1035 test_expect_rerere_gc ! git -c maintenance.rerere-gc.auto=0 maintenance run --auto --task=rerere-gc
1036 '
1037
1038 test_expect_success '--auto and --schedule incompatible' '
1039 test_must_fail git maintenance run --auto --schedule=daily 2>err &&
1040 test_grep "cannot be used together" err
1041 '
1042
1043 test_expect_success '--task and --schedule incompatible' '
1044 test_must_fail git maintenance run --task=pack-refs --schedule=daily 2>err &&
1045 test_grep "cannot be used together" err
1046 '
1047
1048 test_expect_success 'invalid --schedule value' '
1049 test_must_fail git maintenance run --schedule=annually 2>err &&
1050 test_grep "unrecognized --schedule" err
1051 '
1052
1053 test_expect_success '--schedule inheritance weekly -> daily -> hourly' '
1054 git config maintenance.loose-objects.enabled true &&
1055 git config maintenance.loose-objects.schedule hourly &&
1056 git config maintenance.commit-graph.enabled true &&
1057 git config maintenance.commit-graph.schedule daily &&
1058 git config maintenance.incremental-repack.enabled true &&
1059 git config maintenance.incremental-repack.schedule weekly &&
1060
1061 GIT_TRACE2_EVENT="$(pwd)/hourly.txt" \
1062 git maintenance run --schedule=hourly 2>/dev/null &&
1063 test_maintenance_tasks hourly.txt <<-\EOF &&
1064 prefetch
1065 loose-objects
1066 EOF
1067
1068 GIT_TRACE2_EVENT="$(pwd)/daily.txt" \
1069 git maintenance run --schedule=daily 2>/dev/null &&
1070 test_maintenance_tasks daily.txt <<-\EOF &&
1071 prefetch
1072 loose-objects
1073 commit-graph
1074 EOF
1075
1076 GIT_TRACE2_EVENT="$(pwd)/weekly.txt" \
1077 git maintenance run --schedule=weekly 2>/dev/null &&
1078 test_maintenance_tasks weekly.txt <<-\EOF
1079 pack-refs foreground
1080 prefetch
1081 loose-objects
1082 incremental-repack
1083 commit-graph
1084 EOF
1085 '
1086
1087 test_expect_success 'maintenance.strategy inheritance' '
1088 for task in commit-graph loose-objects incremental-repack
1089 do
1090 git config --unset maintenance.$task.schedule || return 1
1091 done &&
1092
1093 test_when_finished git config --unset maintenance.strategy &&
1094 git config maintenance.strategy incremental &&
1095
1096 GIT_TRACE2_EVENT="$(pwd)/incremental-hourly.txt" \
1097 git maintenance run --schedule=hourly --quiet &&
1098 GIT_TRACE2_EVENT="$(pwd)/incremental-daily.txt" \
1099 git maintenance run --schedule=daily --quiet &&
1100 GIT_TRACE2_EVENT="$(pwd)/incremental-weekly.txt" \
1101 git maintenance run --schedule=weekly --quiet &&
1102
1103 test_maintenance_tasks incremental-hourly.txt <<-\EOF &&
1104 prefetch
1105 commit-graph
1106 EOF
1107
1108 test_maintenance_tasks incremental-daily.txt <<-\EOF &&
1109 prefetch
1110 loose-objects
1111 incremental-repack
1112 commit-graph
1113 EOF
1114
1115 test_maintenance_tasks incremental-weekly.txt <<-\EOF &&
1116 pack-refs foreground
1117 prefetch
1118 loose-objects
1119 incremental-repack
1120 commit-graph
1121 EOF
1122
1123 # Modify defaults
1124 git config maintenance.commit-graph.schedule daily &&
1125 git config maintenance.loose-objects.schedule hourly &&
1126 git config maintenance.incremental-repack.enabled false &&
1127
1128 GIT_TRACE2_EVENT="$(pwd)/modified-hourly.txt" \
1129 git maintenance run --schedule=hourly --quiet &&
1130 GIT_TRACE2_EVENT="$(pwd)/modified-daily.txt" \
1131 git maintenance run --schedule=daily --quiet &&
1132
1133 test_maintenance_tasks modified-hourly.txt <<-\EOF &&
1134 prefetch
1135 loose-objects
1136 EOF
1137
1138 test_maintenance_tasks modified-daily.txt <<-\EOF
1139 prefetch
1140 loose-objects
1141 commit-graph
1142 EOF
1143 '
1144
1145 test_strategy () {
1146 STRATEGY="$1"
1147 shift
1148
1149 rm -f trace2.txt &&
1150 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
1151 git -c maintenance.strategy=$STRATEGY maintenance run --quiet "$@" &&
1152 test_maintenance_tasks trace2.txt
1153 }
1154
1155 test_expect_success 'maintenance.strategy is respected' '
1156 test_when_finished "rm -rf repo" &&
1157 git init repo &&
1158 (
1159 cd repo &&
1160 test_commit initial &&
1161
1162 test_must_fail git -c maintenance.strategy=unknown maintenance run 2>err &&
1163 test_grep "unknown maintenance strategy: .unknown." err &&
1164
1165 test_strategy incremental <<-\EOF &&
1166 gc foreground
1167 gc
1168 EOF
1169
1170 test_strategy incremental --schedule=weekly <<-\EOF &&
1171 pack-refs foreground
1172 prefetch
1173 loose-objects
1174 incremental-repack
1175 commit-graph
1176 EOF
1177
1178 test_strategy gc <<-\EOF &&
1179 gc foreground
1180 gc
1181 EOF
1182
1183 test_strategy gc --schedule=weekly <<-\EOF &&
1184 gc foreground
1185 gc
1186 EOF
1187
1188 test_strategy geometric <<-\EOF &&
1189 pack-refs foreground
1190 reflog-expire foreground
1191 geometric-repack
1192 commit-graph
1193 worktree-prune
1194 rerere-gc
1195 EOF
1196
1197 test_strategy geometric --schedule=weekly <<-\EOF
1198 pack-refs foreground
1199 reflog-expire foreground
1200 geometric-repack
1201 commit-graph
1202 worktree-prune
1203 rerere-gc
1204 EOF
1205 )
1206 '
1207
1208 test_expect_success 'register and unregister' '
1209 test_when_finished git config --global --unset-all maintenance.repo &&
1210
1211 test_must_fail git maintenance unregister 2>err &&
1212 test_grep "is not registered" err &&
1213 git maintenance unregister --force &&
1214
1215 git config --global --add maintenance.repo /existing1 &&
1216 git config --global --add maintenance.repo /existing2 &&
1217 git config --global --get-all maintenance.repo >before &&
1218
1219 git maintenance register &&
1220 test_cmp_config false maintenance.auto &&
1221 git config --global --get-all maintenance.repo >between &&
1222 cp before expect &&
1223 pwd >>expect &&
1224 test_cmp expect between &&
1225
1226 git maintenance unregister &&
1227 git config --global --get-all maintenance.repo >actual &&
1228 test_cmp before actual &&
1229
1230 git config --file ./other --add maintenance.repo /existing1 &&
1231 git config --file ./other --add maintenance.repo /existing2 &&
1232 git config --file ./other --get-all maintenance.repo >before &&
1233
1234 git maintenance register --config-file ./other &&
1235 test_cmp_config false maintenance.auto &&
1236 git config --file ./other --get-all maintenance.repo >between &&
1237 cp before expect &&
1238 pwd >>expect &&
1239 test_cmp expect between &&
1240
1241 git maintenance unregister --config-file ./other &&
1242 git config --file ./other --get-all maintenance.repo >actual &&
1243 test_cmp before actual &&
1244
1245 test_must_fail git maintenance unregister 2>err &&
1246 test_grep "is not registered" err &&
1247 git maintenance unregister --force &&
1248
1249 test_must_fail git maintenance unregister --config-file ./other 2>err &&
1250 test_grep "is not registered" err &&
1251 git maintenance unregister --config-file ./other --force
1252 '
1253
1254 test_expect_success 'register with no value for maintenance.repo' '
1255 cp .git/config .git/config.orig &&
1256 test_when_finished mv .git/config.orig .git/config &&
1257
1258 cat >>.git/config <<-\EOF &&
1259 [maintenance]
1260 repo
1261 EOF
1262 cat >expect <<-\EOF &&
1263 error: missing value for '\''maintenance.repo'\''
1264 EOF
1265 git maintenance register 2>actual &&
1266 test_cmp expect actual &&
1267 git config maintenance.repo
1268 '
1269
1270 test_expect_success 'unregister with no value for maintenance.repo' '
1271 cp .git/config .git/config.orig &&
1272 test_when_finished mv .git/config.orig .git/config &&
1273
1274 cat >>.git/config <<-\EOF &&
1275 [maintenance]
1276 repo
1277 EOF
1278 cat >expect <<-\EOF &&
1279 error: missing value for '\''maintenance.repo'\''
1280 EOF
1281 test_expect_code 128 git maintenance unregister 2>actual.raw &&
1282 grep ^error actual.raw >actual &&
1283 test_cmp expect actual &&
1284 git config maintenance.repo &&
1285
1286 git maintenance unregister --force 2>actual.raw &&
1287 grep ^error actual.raw >actual &&
1288 test_cmp expect actual &&
1289 git config maintenance.repo
1290 '
1291
1292 test_expect_success !MINGW 'register and unregister with regex metacharacters' '
1293 META="a+b*c" &&
1294 git init "$META" &&
1295 git -C "$META" maintenance register &&
1296 git config --get-all --show-origin maintenance.repo &&
1297 git config --get-all --global --fixed-value \
1298 maintenance.repo "$(pwd)/$META" &&
1299 git -C "$META" maintenance unregister &&
1300 test_must_fail git config --get-all --global --fixed-value \
1301 maintenance.repo "$(pwd)/$META"
1302 '
1303
1304 test_expect_success 'start without GIT_TEST_MAINT_SCHEDULER' '
1305 test_when_finished "rm -rf systemctl.log script repo" &&
1306 mkdir script &&
1307 write_script script/systemctl <<-\EOF &&
1308 echo "$*" >>../systemctl.log
1309 EOF
1310 git init repo &&
1311 (
1312 cd repo &&
1313 sane_unset GIT_TEST_MAINT_SCHEDULER &&
1314 PATH="$PWD/../script:$PATH" git maintenance start --scheduler=systemd
1315 ) &&
1316 test_grep -- "--user list-timers" systemctl.log &&
1317 test_grep -- "enable --now git-maintenance@" systemctl.log
1318 '
1319
1320 test_expect_success 'start --scheduler=<scheduler>' '
1321 test_expect_code 129 git maintenance start --scheduler=foo 2>err &&
1322 test_grep "unrecognized --scheduler argument" err &&
1323
1324 test_expect_code 129 git maintenance start --no-scheduler 2>err &&
1325 test_grep "unknown option" err &&
1326
1327 test_expect_code 128 \
1328 env GIT_TEST_MAINT_SCHEDULER="launchctl:true,schtasks:true" \
1329 git maintenance start --scheduler=crontab 2>err &&
1330 test_grep "fatal: crontab scheduler is not available" err
1331 '
1332
1333 test_expect_success 'start from empty cron table' '
1334 GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt" git maintenance start --scheduler=crontab &&
1335
1336 # start registers the repo
1337 git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
1338
1339 test_grep "for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=daily" cron.txt &&
1340 test_grep "for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=hourly" cron.txt &&
1341 test_grep "for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=weekly" cron.txt
1342 '
1343
1344 test_expect_success 'stop from existing schedule' '
1345 GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt" git maintenance stop &&
1346
1347 # stop does not unregister the repo
1348 git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
1349
1350 # Operation is idempotent
1351 GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt" git maintenance stop &&
1352 test_must_be_empty cron.txt
1353 '
1354
1355 test_expect_success 'start preserves existing schedule' '
1356 echo "Important information!" >cron.txt &&
1357 GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt" git maintenance start --scheduler=crontab &&
1358 test_grep "Important information!" cron.txt
1359 '
1360
1361 test_expect_success 'magic markers are correct' '
1362 grep "GIT MAINTENANCE SCHEDULE" cron.txt >actual &&
1363 cat >expect <<-\EOF &&
1364 # BEGIN GIT MAINTENANCE SCHEDULE
1365 # END GIT MAINTENANCE SCHEDULE
1366 EOF
1367 test_cmp actual expect
1368 '
1369
1370 test_expect_success 'stop preserves surrounding schedule' '
1371 echo "Crucial information!" >>cron.txt &&
1372 GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt" git maintenance stop &&
1373 test_grep "Important information!" cron.txt &&
1374 test_grep "Crucial information!" cron.txt
1375 '
1376
1377 test_expect_success 'start and stop macOS maintenance' '
1378 # ensure $HOME can be compared against hook arguments on all platforms
1379 pfx=$(cd "$HOME" && pwd) &&
1380
1381 write_script print-args <<-\EOF &&
1382 echo $* | sed "s:gui/[0-9][0-9]*:gui/[UID]:" >>args
1383 EOF
1384
1385 rm -f args &&
1386 GIT_TEST_MAINT_SCHEDULER=launchctl:./print-args git maintenance start --scheduler=launchctl &&
1387
1388 # start registers the repo
1389 git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
1390
1391 ls "$HOME/Library/LaunchAgents" >actual &&
1392 cat >expect <<-\EOF &&
1393 org.git-scm.git.daily.plist
1394 org.git-scm.git.hourly.plist
1395 org.git-scm.git.weekly.plist
1396 EOF
1397 test_cmp expect actual &&
1398
1399 rm -f expect &&
1400 for frequency in hourly daily weekly
1401 do
1402 PLIST="$pfx/Library/LaunchAgents/org.git-scm.git.$frequency.plist" &&
1403 test_xmllint "$PLIST" &&
1404 test_grep schedule=$frequency "$PLIST" &&
1405 echo "bootout gui/[UID] $PLIST" >>expect &&
1406 echo "bootstrap gui/[UID] $PLIST" >>expect || return 1
1407 done &&
1408 test_cmp expect args &&
1409
1410 rm -f args &&
1411 GIT_TEST_MAINT_SCHEDULER=launchctl:./print-args git maintenance stop &&
1412
1413 # stop does not unregister the repo
1414 git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
1415
1416 printf "bootout gui/[UID] $pfx/Library/LaunchAgents/org.git-scm.git.%s.plist\n" \
1417 hourly daily weekly >expect &&
1418 test_cmp expect args &&
1419 ls "$HOME/Library/LaunchAgents" >actual &&
1420 test_line_count = 0 actual
1421 '
1422
1423 test_expect_success 'use launchctl list to prevent extra work' '
1424 # ensure we are registered
1425 GIT_TEST_MAINT_SCHEDULER=launchctl:./print-args git maintenance start --scheduler=launchctl &&
1426
1427 # do it again on a fresh args file
1428 rm -f args &&
1429 GIT_TEST_MAINT_SCHEDULER=launchctl:./print-args git maintenance start --scheduler=launchctl &&
1430
1431 ls "$HOME/Library/LaunchAgents" >actual &&
1432 cat >expect <<-\EOF &&
1433 list org.git-scm.git.hourly
1434 list org.git-scm.git.daily
1435 list org.git-scm.git.weekly
1436 EOF
1437 test_cmp expect args
1438 '
1439
1440 test_expect_success 'start and stop Windows maintenance' '
1441 write_script print-args <<-\EOF &&
1442 echo $* >>args
1443 while test $# -gt 0
1444 do
1445 case "$1" in
1446 /xml) shift; xmlfile=$1; break ;;
1447 *) shift ;;
1448 esac
1449 done
1450 test -z "$xmlfile" || cp "$xmlfile" "$xmlfile.xml"
1451 EOF
1452
1453 rm -f args &&
1454 GIT_TEST_MAINT_SCHEDULER="schtasks:./print-args" git maintenance start --scheduler=schtasks &&
1455
1456 # start registers the repo
1457 git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
1458
1459 for frequency in hourly daily weekly
1460 do
1461 test_grep "/create /tn Git Maintenance ($frequency) /f /xml" args &&
1462 file=$(ls .git/schedule_${frequency}*.xml) &&
1463 test_xmllint "$file" || return 1
1464 done &&
1465
1466 rm -f args &&
1467 GIT_TEST_MAINT_SCHEDULER="schtasks:./print-args" git maintenance stop &&
1468
1469 # stop does not unregister the repo
1470 git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
1471
1472 printf "/delete /tn Git Maintenance (%s) /f\n" \
1473 hourly daily weekly >expect &&
1474 test_cmp expect args
1475 '
1476
1477 test_expect_success 'start and stop Linux/systemd maintenance' '
1478 write_script print-args <<-\EOF &&
1479 printf "%s\n" "$*" >>args
1480 EOF
1481
1482 XDG_CONFIG_HOME="$PWD" &&
1483 export XDG_CONFIG_HOME &&
1484 rm -f args &&
1485 GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args" git maintenance start --scheduler=systemd-timer &&
1486
1487 # start registers the repo
1488 git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
1489
1490 for schedule in hourly daily weekly
1491 do
1492 test_path_is_file "systemd/user/git-maintenance@$schedule.timer" || return 1
1493 done &&
1494 test_path_is_file "systemd/user/git-maintenance@.service" &&
1495
1496 test_systemd_analyze_verify "systemd/user/git-maintenance@hourly.service" &&
1497 test_systemd_analyze_verify "systemd/user/git-maintenance@daily.service" &&
1498 test_systemd_analyze_verify "systemd/user/git-maintenance@weekly.service" &&
1499
1500 test_grep "core.askPass=true" "systemd/user/git-maintenance@.service" &&
1501 test_grep "credential.interactive=false" "systemd/user/git-maintenance@.service" &&
1502
1503 printf -- "--user enable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
1504 test_cmp expect args &&
1505
1506 rm -f args &&
1507 GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args" git maintenance stop &&
1508
1509 # stop does not unregister the repo
1510 git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
1511
1512 for schedule in hourly daily weekly
1513 do
1514 test_path_is_missing "systemd/user/git-maintenance@$schedule.timer" || return 1
1515 done &&
1516 test_path_is_missing "systemd/user/git-maintenance@.service" &&
1517
1518 printf -- "--user disable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
1519 test_cmp expect args
1520 '
1521
1522 test_expect_success 'start and stop when several schedulers are available' '
1523 write_script print-args <<-\EOF &&
1524 printf "%s\n" "$*" | sed "s:gui/[0-9][0-9]*:gui/[UID]:; s:\(schtasks /create .* /xml\).*:\1:;" >>args
1525 EOF
1526
1527 rm -f args &&
1528 GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args systemctl,launchctl:./print-args launchctl,schtasks:./print-args schtasks" git maintenance start --scheduler=systemd-timer &&
1529 printf "launchctl bootout gui/[UID] $pfx/Library/LaunchAgents/org.git-scm.git.%s.plist\n" \
1530 hourly daily weekly >expect &&
1531 printf "schtasks /delete /tn Git Maintenance (%s) /f\n" \
1532 hourly daily weekly >>expect &&
1533 printf -- "systemctl --user enable --now git-maintenance@%s.timer\n" hourly daily weekly >>expect &&
1534 test_cmp expect args &&
1535
1536 rm -f args &&
1537 GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args systemctl,launchctl:./print-args launchctl,schtasks:./print-args schtasks" git maintenance start --scheduler=launchctl &&
1538 printf -- "systemctl --user disable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
1539 printf "schtasks /delete /tn Git Maintenance (%s) /f\n" \
1540 hourly daily weekly >>expect &&
1541 for frequency in hourly daily weekly
1542 do
1543 PLIST="$pfx/Library/LaunchAgents/org.git-scm.git.$frequency.plist" &&
1544 echo "launchctl bootout gui/[UID] $PLIST" >>expect &&
1545 echo "launchctl bootstrap gui/[UID] $PLIST" >>expect || return 1
1546 done &&
1547 test_cmp expect args &&
1548
1549 rm -f args &&
1550 GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args systemctl,launchctl:./print-args launchctl,schtasks:./print-args schtasks" git maintenance start --scheduler=schtasks &&
1551 printf -- "systemctl --user disable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
1552 printf "launchctl bootout gui/[UID] $pfx/Library/LaunchAgents/org.git-scm.git.%s.plist\n" \
1553 hourly daily weekly >>expect &&
1554 printf "schtasks /create /tn Git Maintenance (%s) /f /xml\n" \
1555 hourly daily weekly >>expect &&
1556 test_cmp expect args &&
1557
1558 rm -f args &&
1559 GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args systemctl,launchctl:./print-args launchctl,schtasks:./print-args schtasks" git maintenance stop &&
1560 printf -- "systemctl --user disable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
1561 printf "launchctl bootout gui/[UID] $pfx/Library/LaunchAgents/org.git-scm.git.%s.plist\n" \
1562 hourly daily weekly >>expect &&
1563 printf "schtasks /delete /tn Git Maintenance (%s) /f\n" \
1564 hourly daily weekly >>expect &&
1565 test_cmp expect args
1566 '
1567
1568 test_expect_success 'register preserves existing strategy' '
1569 git config maintenance.strategy none &&
1570 git maintenance register &&
1571 test_config maintenance.strategy none &&
1572 git config --unset maintenance.strategy &&
1573 git maintenance register &&
1574 test_config maintenance.strategy incremental
1575 '
1576
1577 test_expect_success 'fails when running outside of a repository' '
1578 nongit test_must_fail git maintenance run &&
1579 nongit test_must_fail git maintenance stop &&
1580 nongit test_must_fail git maintenance start &&
1581 nongit test_must_fail git maintenance register &&
1582 nongit test_must_fail git maintenance unregister
1583 '
1584
1585 test_expect_success 'fails when configured to use an invalid strategy' '
1586 test_must_fail git -c maintenance.strategy=invalid maintenance run --schedule=hourly 2>err &&
1587 test_grep "unknown maintenance strategy: .invalid." err
1588 '
1589
1590 test_expect_success 'register and unregister bare repo' '
1591 test_when_finished "git config --global --unset-all maintenance.repo || :" &&
1592 test_might_fail git config --global --unset-all maintenance.repo &&
1593 git init --bare barerepo &&
1594 (
1595 cd barerepo &&
1596 git maintenance register &&
1597 git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
1598 git maintenance unregister &&
1599 test_must_fail git config --global --get-all maintenance.repo
1600 )
1601 '
1602
1603 test_expect_success 'failed schedule prevents config change' '
1604 git init --bare failcase &&
1605
1606 for scheduler in crontab launchctl schtasks systemctl
1607 do
1608 GIT_TEST_MAINT_SCHEDULER="$scheduler:false" &&
1609 export GIT_TEST_MAINT_SCHEDULER &&
1610 test_must_fail \
1611 git -C failcase maintenance start &&
1612 test_must_fail git -C failcase config maintenance.auto || return 1
1613 done
1614 '
1615
1616 test_expect_success '--no-detach causes maintenance to not run in background' '
1617 test_when_finished "rm -rf repo" &&
1618 git init repo &&
1619 (
1620 cd repo &&
1621
1622 # Prepare the repository such that git-maintenance(1) ends up
1623 # outputting something.
1624 test_commit something &&
1625 git config set maintenance.gc.enabled false &&
1626 git config set maintenance.loose-objects.enabled true &&
1627 git config set maintenance.loose-objects.auto 1 &&
1628 git config set maintenance.incremental-repack.enabled true &&
1629
1630 GIT_TRACE2_EVENT="$(pwd)/trace.txt" \
1631 git maintenance run --no-detach >out 2>&1 &&
1632 ! test_region maintenance detach trace.txt
1633 )
1634 '
1635
1636 test_expect_success PIPE '--detach holds maintenance lock until daemonized child exits' '
1637 test_when_finished "rm -rf repo" &&
1638 git init repo &&
1639 (
1640 cd repo &&
1641
1642 git config maintenance.auto false &&
1643 git config core.lockfilepid true &&
1644
1645 git remote add origin /does/not/exist &&
1646 git config set remote.origin.uploadpack "cat fifo-uploadpack" &&
1647
1648 mkfifo fifo-uploadpack fifo-maint &&
1649
1650 # Open the maintenance FIFO, as otherwise spawning
1651 # git-maintenance(1) would block. Note that we need to open it
1652 # as read-write, as otherwise we would block here already.
1653 exec 9<>fifo-maint &&
1654
1655 { git maintenance run --task=prefetch --detach 7>&9 & } &&
1656 parent="$!" &&
1657
1658 # Reap the parent process so that the exec call below will not
1659 # get SIGCHLD.
1660 wait "$parent" &&
1661
1662 # Open the git-upload-pack(1) FIFO for writing, which will
1663 # block until the upload-pack script opens it for reading. Once
1664 # exec returns, we know that the daemonized child is alive and
1665 # pinned.
1666 exec 8>fifo-uploadpack &&
1667
1668 test_path_is_file .git/objects/maintenance.lock &&
1669 test_path_is_file .git/objects/"maintenance~pid.lock" &&
1670
1671 # Verify that the maintenance.lock still exists, and
1672 # that it was created by the parent process, not the
1673 # child.
1674 echo "pid $parent" >expect &&
1675 test_cmp expect .git/objects/"maintenance~pid.lock" &&
1676
1677 # Reopen the maintenance FIFO as read-only so that
1678 # git-maintenance(1) is the only writer. This will cause it to
1679 # close the FIFO once the process exits.
1680 exec 9<&- &&
1681 exec 9<fifo-maint &&
1682
1683 # Close the FIFO used by git-upload-pack(1) to unblock it and
1684 # then wait until the maintenance FIFO is closed by
1685 # git-maintenance(1), indicating that it has exited.
1686 exec 8>&- &&
1687 cat <&9 &&
1688
1689 test_path_is_missing .git/objects/maintenance.lock &&
1690 test_path_is_missing .git/objects/"maintenance~pid.lock"
1691 )
1692 '
1693
1694 test_expect_success '--detach causes maintenance to run in background' '
1695 test_when_finished "rm -rf repo" &&
1696 git init repo &&
1697 (
1698 cd repo &&
1699
1700 test_commit something &&
1701 git config set maintenance.gc.enabled false &&
1702 git config set maintenance.loose-objects.enabled true &&
1703 git config set maintenance.loose-objects.auto 1 &&
1704 git config set maintenance.incremental-repack.enabled true &&
1705
1706 # The extra file descriptor gets inherited to the child
1707 # process, and by reading stdout we thus essentially wait for
1708 # that descriptor to get closed, which indicates that the child
1709 # is done, too.
1710 does_not_matter=$(GIT_TRACE2_EVENT="$(pwd)/trace.txt" \
1711 git maintenance run --detach 9>&1) &&
1712 test_region maintenance detach trace.txt
1713 )
1714 '
1715
1716 test_expect_success 'repacking loose objects is quiet' '
1717 test_when_finished "rm -rf repo" &&
1718 git init repo &&
1719 (
1720 cd repo &&
1721
1722 test_commit something &&
1723 git config set maintenance.gc.enabled false &&
1724 git config set maintenance.loose-objects.enabled true &&
1725 git config set maintenance.loose-objects.auto 1 &&
1726
1727 git maintenance run --quiet >out 2>&1 &&
1728 test_must_be_empty out
1729 )
1730 '
1731
1732 test_expect_success 'maintenance aborts with existing lock file' '
1733 test_when_finished "rm -rf repo script" &&
1734 mkdir script &&
1735 write_script script/systemctl <<-\EOF &&
1736 true
1737 EOF
1738
1739 git init repo &&
1740 : >repo/.git/objects/schedule.lock &&
1741 test_must_fail env PATH="$PWD/script:$PATH" git -C repo maintenance start --scheduler=systemd 2>err &&
1742 test_grep "Another scheduled git-maintenance(1) process seems to be running" err
1743 '
1744
1745 test_done