maintenance: use launchctl on macOS
[git] / t / t7900-maintenance.sh
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 test_lazy_prereq XMLLINT '
11         xmllint --version
12 '
13
14 test_xmllint () {
15         if test_have_prereq XMLLINT
16         then
17                 xmllint --noout "$@"
18         else
19                 true
20         fi
21 }
22
23 test_expect_success 'help text' '
24         test_expect_code 129 git maintenance -h 2>err &&
25         test_i18ngrep "usage: git maintenance <subcommand>" err &&
26         test_expect_code 128 git maintenance barf 2>err &&
27         test_i18ngrep "invalid subcommand: barf" err &&
28         test_expect_code 129 git maintenance 2>err &&
29         test_i18ngrep "usage: git maintenance" err
30 '
31
32 test_expect_success 'run [--auto|--quiet]' '
33         GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" \
34                 git maintenance run 2>/dev/null &&
35         GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" \
36                 git maintenance run --auto 2>/dev/null &&
37         GIT_TRACE2_EVENT="$(pwd)/run-no-quiet.txt" \
38                 git maintenance run --no-quiet 2>/dev/null &&
39         test_subcommand git gc --quiet <run-no-auto.txt &&
40         test_subcommand ! git gc --auto --quiet <run-auto.txt &&
41         test_subcommand git gc --no-quiet <run-no-quiet.txt
42 '
43
44 test_expect_success 'maintenance.auto config option' '
45         GIT_TRACE2_EVENT="$(pwd)/default" git commit --quiet --allow-empty -m 1 &&
46         test_subcommand git maintenance run --auto --quiet <default &&
47         GIT_TRACE2_EVENT="$(pwd)/true" \
48                 git -c maintenance.auto=true \
49                 commit --quiet --allow-empty -m 2 &&
50         test_subcommand git maintenance run --auto --quiet  <true &&
51         GIT_TRACE2_EVENT="$(pwd)/false" \
52                 git -c maintenance.auto=false \
53                 commit --quiet --allow-empty -m 3 &&
54         test_subcommand ! git maintenance run --auto --quiet  <false
55 '
56
57 test_expect_success 'maintenance.<task>.enabled' '
58         git config maintenance.gc.enabled false &&
59         git config maintenance.commit-graph.enabled true &&
60         GIT_TRACE2_EVENT="$(pwd)/run-config.txt" git maintenance run 2>err &&
61         test_subcommand ! git gc --quiet <run-config.txt &&
62         test_subcommand git commit-graph write --split --reachable --no-progress <run-config.txt
63 '
64
65 test_expect_success 'run --task=<task>' '
66         GIT_TRACE2_EVENT="$(pwd)/run-commit-graph.txt" \
67                 git maintenance run --task=commit-graph 2>/dev/null &&
68         GIT_TRACE2_EVENT="$(pwd)/run-gc.txt" \
69                 git maintenance run --task=gc 2>/dev/null &&
70         GIT_TRACE2_EVENT="$(pwd)/run-commit-graph.txt" \
71                 git maintenance run --task=commit-graph 2>/dev/null &&
72         GIT_TRACE2_EVENT="$(pwd)/run-both.txt" \
73                 git maintenance run --task=commit-graph --task=gc 2>/dev/null &&
74         test_subcommand ! git gc --quiet <run-commit-graph.txt &&
75         test_subcommand git gc --quiet <run-gc.txt &&
76         test_subcommand git gc --quiet <run-both.txt &&
77         test_subcommand git commit-graph write --split --reachable --no-progress <run-commit-graph.txt &&
78         test_subcommand ! git commit-graph write --split --reachable --no-progress <run-gc.txt &&
79         test_subcommand git commit-graph write --split --reachable --no-progress <run-both.txt
80 '
81
82 test_expect_success 'run --task=bogus' '
83         test_must_fail git maintenance run --task=bogus 2>err &&
84         test_i18ngrep "is not a valid task" err
85 '
86
87 test_expect_success 'run --task duplicate' '
88         test_must_fail git maintenance run --task=gc --task=gc 2>err &&
89         test_i18ngrep "cannot be selected multiple times" err
90 '
91
92 test_expect_success 'run --task=prefetch with no remotes' '
93         git maintenance run --task=prefetch 2>err &&
94         test_must_be_empty err
95 '
96
97 test_expect_success 'prefetch multiple remotes' '
98         git clone . clone1 &&
99         git clone . clone2 &&
100         git remote add remote1 "file://$(pwd)/clone1" &&
101         git remote add remote2 "file://$(pwd)/clone2" &&
102         git -C clone1 switch -c one &&
103         git -C clone2 switch -c two &&
104         test_commit -C clone1 one &&
105         test_commit -C clone2 two &&
106         GIT_TRACE2_EVENT="$(pwd)/run-prefetch.txt" git maintenance run --task=prefetch 2>/dev/null &&
107         fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" &&
108         test_subcommand git fetch remote1 $fetchargs +refs/heads/\\*:refs/prefetch/remote1/\\* <run-prefetch.txt &&
109         test_subcommand git fetch remote2 $fetchargs +refs/heads/\\*:refs/prefetch/remote2/\\* <run-prefetch.txt &&
110         test_path_is_missing .git/refs/remotes &&
111         git log prefetch/remote1/one &&
112         git log prefetch/remote2/two &&
113         git fetch --all &&
114         test_cmp_rev refs/remotes/remote1/one refs/prefetch/remote1/one &&
115         test_cmp_rev refs/remotes/remote2/two refs/prefetch/remote2/two
116 '
117
118 test_expect_success 'loose-objects task' '
119         # Repack everything so we know the state of the object dir
120         git repack -adk &&
121
122         # Hack to stop maintenance from running during "git commit"
123         echo in use >.git/objects/maintenance.lock &&
124
125         # Assuming that "git commit" creates at least one loose object
126         test_commit create-loose-object &&
127         rm .git/objects/maintenance.lock &&
128
129         ls .git/objects >obj-dir-before &&
130         test_file_not_empty obj-dir-before &&
131         ls .git/objects/pack/*.pack >packs-before &&
132         test_line_count = 1 packs-before &&
133
134         # The first run creates a pack-file
135         # but does not delete loose objects.
136         git maintenance run --task=loose-objects &&
137         ls .git/objects >obj-dir-between &&
138         test_cmp obj-dir-before obj-dir-between &&
139         ls .git/objects/pack/*.pack >packs-between &&
140         test_line_count = 2 packs-between &&
141         ls .git/objects/pack/loose-*.pack >loose-packs &&
142         test_line_count = 1 loose-packs &&
143
144         # The second run deletes loose objects
145         # but does not create a pack-file.
146         git maintenance run --task=loose-objects &&
147         ls .git/objects >obj-dir-after &&
148         cat >expect <<-\EOF &&
149         info
150         pack
151         EOF
152         test_cmp expect obj-dir-after &&
153         ls .git/objects/pack/*.pack >packs-after &&
154         test_cmp packs-between packs-after
155 '
156
157 test_expect_success 'maintenance.loose-objects.auto' '
158         git repack -adk &&
159         GIT_TRACE2_EVENT="$(pwd)/trace-lo1.txt" \
160                 git -c maintenance.loose-objects.auto=1 maintenance \
161                 run --auto --task=loose-objects 2>/dev/null &&
162         test_subcommand ! git prune-packed --quiet <trace-lo1.txt &&
163         printf data-A | git hash-object -t blob --stdin -w &&
164         GIT_TRACE2_EVENT="$(pwd)/trace-loA" \
165                 git -c maintenance.loose-objects.auto=2 \
166                 maintenance run --auto --task=loose-objects 2>/dev/null &&
167         test_subcommand ! git prune-packed --quiet <trace-loA &&
168         printf data-B | git hash-object -t blob --stdin -w &&
169         GIT_TRACE2_EVENT="$(pwd)/trace-loB" \
170                 git -c maintenance.loose-objects.auto=2 \
171                 maintenance run --auto --task=loose-objects 2>/dev/null &&
172         test_subcommand git prune-packed --quiet <trace-loB &&
173         GIT_TRACE2_EVENT="$(pwd)/trace-loC" \
174                 git -c maintenance.loose-objects.auto=2 \
175                 maintenance run --auto --task=loose-objects 2>/dev/null &&
176         test_subcommand git prune-packed --quiet <trace-loC
177 '
178
179 test_expect_success 'incremental-repack task' '
180         packDir=.git/objects/pack &&
181         for i in $(test_seq 1 5)
182         do
183                 test_commit $i || return 1
184         done &&
185
186         # Create three disjoint pack-files with size BIG, small, small.
187         echo HEAD~2 | git pack-objects --revs $packDir/test-1 &&
188         test_tick &&
189         git pack-objects --revs $packDir/test-2 <<-\EOF &&
190         HEAD~1
191         ^HEAD~2
192         EOF
193         test_tick &&
194         git pack-objects --revs $packDir/test-3 <<-\EOF &&
195         HEAD
196         ^HEAD~1
197         EOF
198         rm -f $packDir/pack-* &&
199         rm -f $packDir/loose-* &&
200         ls $packDir/*.pack >packs-before &&
201         test_line_count = 3 packs-before &&
202
203         # the job repacks the two into a new pack, but does not
204         # delete the old ones.
205         git maintenance run --task=incremental-repack &&
206         ls $packDir/*.pack >packs-between &&
207         test_line_count = 4 packs-between &&
208
209         # the job deletes the two old packs, and does not write
210         # a new one because the batch size is not high enough to
211         # pack the largest pack-file.
212         git maintenance run --task=incremental-repack &&
213         ls .git/objects/pack/*.pack >packs-after &&
214         test_line_count = 2 packs-after
215 '
216
217 test_expect_success EXPENSIVE 'incremental-repack 2g limit' '
218         for i in $(test_seq 1 5)
219         do
220                 test-tool genrandom foo$i $((512 * 1024 * 1024 + 1)) >>big ||
221                 return 1
222         done &&
223         git add big &&
224         git commit -m "Add big file (1)" &&
225
226         # ensure any possible loose objects are in a pack-file
227         git maintenance run --task=loose-objects &&
228
229         rm big &&
230         for i in $(test_seq 6 10)
231         do
232                 test-tool genrandom foo$i $((512 * 1024 * 1024 + 1)) >>big ||
233                 return 1
234         done &&
235         git add big &&
236         git commit -m "Add big file (2)" &&
237
238         # ensure any possible loose objects are in a pack-file
239         git maintenance run --task=loose-objects &&
240
241         # Now run the incremental-repack task and check the batch-size
242         GIT_TRACE2_EVENT="$(pwd)/run-2g.txt" git maintenance run \
243                 --task=incremental-repack 2>/dev/null &&
244         test_subcommand git multi-pack-index repack \
245                  --no-progress --batch-size=2147483647 <run-2g.txt
246 '
247
248 test_expect_success 'maintenance.incremental-repack.auto' '
249         git repack -adk &&
250         git config core.multiPackIndex true &&
251         git multi-pack-index write &&
252         GIT_TRACE2_EVENT="$(pwd)/midx-init.txt" git \
253                 -c maintenance.incremental-repack.auto=1 \
254                 maintenance run --auto --task=incremental-repack 2>/dev/null &&
255         test_subcommand ! git multi-pack-index write --no-progress <midx-init.txt &&
256         test_commit A &&
257         git pack-objects --revs .git/objects/pack/pack <<-\EOF &&
258         HEAD
259         ^HEAD~1
260         EOF
261         GIT_TRACE2_EVENT=$(pwd)/trace-A git \
262                 -c maintenance.incremental-repack.auto=2 \
263                 maintenance run --auto --task=incremental-repack 2>/dev/null &&
264         test_subcommand ! git multi-pack-index write --no-progress <trace-A &&
265         test_commit B &&
266         git pack-objects --revs .git/objects/pack/pack <<-\EOF &&
267         HEAD
268         ^HEAD~1
269         EOF
270         GIT_TRACE2_EVENT=$(pwd)/trace-B git \
271                 -c maintenance.incremental-repack.auto=2 \
272                 maintenance run --auto --task=incremental-repack 2>/dev/null &&
273         test_subcommand git multi-pack-index write --no-progress <trace-B
274 '
275
276 test_expect_success '--auto and --schedule incompatible' '
277         test_must_fail git maintenance run --auto --schedule=daily 2>err &&
278         test_i18ngrep "at most one" err
279 '
280
281 test_expect_success 'invalid --schedule value' '
282         test_must_fail git maintenance run --schedule=annually 2>err &&
283         test_i18ngrep "unrecognized --schedule" err
284 '
285
286 test_expect_success '--schedule inheritance weekly -> daily -> hourly' '
287         git config maintenance.loose-objects.enabled true &&
288         git config maintenance.loose-objects.schedule hourly &&
289         git config maintenance.commit-graph.enabled true &&
290         git config maintenance.commit-graph.schedule daily &&
291         git config maintenance.incremental-repack.enabled true &&
292         git config maintenance.incremental-repack.schedule weekly &&
293
294         GIT_TRACE2_EVENT="$(pwd)/hourly.txt" \
295                 git maintenance run --schedule=hourly 2>/dev/null &&
296         test_subcommand git prune-packed --quiet <hourly.txt &&
297         test_subcommand ! git commit-graph write --split --reachable \
298                 --no-progress <hourly.txt &&
299         test_subcommand ! git multi-pack-index write --no-progress <hourly.txt &&
300
301         GIT_TRACE2_EVENT="$(pwd)/daily.txt" \
302                 git maintenance run --schedule=daily 2>/dev/null &&
303         test_subcommand git prune-packed --quiet <daily.txt &&
304         test_subcommand git commit-graph write --split --reachable \
305                 --no-progress <daily.txt &&
306         test_subcommand ! git multi-pack-index write --no-progress <daily.txt &&
307
308         GIT_TRACE2_EVENT="$(pwd)/weekly.txt" \
309                 git maintenance run --schedule=weekly 2>/dev/null &&
310         test_subcommand git prune-packed --quiet <weekly.txt &&
311         test_subcommand git commit-graph write --split --reachable \
312                 --no-progress <weekly.txt &&
313         test_subcommand git multi-pack-index write --no-progress <weekly.txt
314 '
315
316 test_expect_success 'maintenance.strategy inheritance' '
317         for task in commit-graph loose-objects incremental-repack
318         do
319                 git config --unset maintenance.$task.schedule || return 1
320         done &&
321
322         test_when_finished git config --unset maintenance.strategy &&
323         git config maintenance.strategy incremental &&
324
325         GIT_TRACE2_EVENT="$(pwd)/incremental-hourly.txt" \
326                 git maintenance run --schedule=hourly --quiet &&
327         GIT_TRACE2_EVENT="$(pwd)/incremental-daily.txt" \
328                 git maintenance run --schedule=daily --quiet &&
329
330         test_subcommand git commit-graph write --split --reachable \
331                 --no-progress <incremental-hourly.txt &&
332         test_subcommand ! git prune-packed --quiet <incremental-hourly.txt &&
333         test_subcommand ! git multi-pack-index write --no-progress \
334                 <incremental-hourly.txt &&
335
336         test_subcommand git commit-graph write --split --reachable \
337                 --no-progress <incremental-daily.txt &&
338         test_subcommand git prune-packed --quiet <incremental-daily.txt &&
339         test_subcommand git multi-pack-index write --no-progress \
340                 <incremental-daily.txt &&
341
342         # Modify defaults
343         git config maintenance.commit-graph.schedule daily &&
344         git config maintenance.loose-objects.schedule hourly &&
345         git config maintenance.incremental-repack.enabled false &&
346
347         GIT_TRACE2_EVENT="$(pwd)/modified-hourly.txt" \
348                 git maintenance run --schedule=hourly --quiet &&
349         GIT_TRACE2_EVENT="$(pwd)/modified-daily.txt" \
350                 git maintenance run --schedule=daily --quiet &&
351
352         test_subcommand ! git commit-graph write --split --reachable \
353                 --no-progress <modified-hourly.txt &&
354         test_subcommand git prune-packed --quiet <modified-hourly.txt &&
355         test_subcommand ! git multi-pack-index write --no-progress \
356                 <modified-hourly.txt &&
357
358         test_subcommand git commit-graph write --split --reachable \
359                 --no-progress <modified-daily.txt &&
360         test_subcommand git prune-packed --quiet <modified-daily.txt &&
361         test_subcommand ! git multi-pack-index write --no-progress \
362                 <modified-daily.txt
363 '
364
365 test_expect_success 'register and unregister' '
366         test_when_finished git config --global --unset-all maintenance.repo &&
367         git config --global --add maintenance.repo /existing1 &&
368         git config --global --add maintenance.repo /existing2 &&
369         git config --global --get-all maintenance.repo >before &&
370
371         git maintenance register &&
372         test_cmp_config false maintenance.auto &&
373         git config --global --get-all maintenance.repo >between &&
374         cp before expect &&
375         pwd >>expect &&
376         test_cmp expect between &&
377
378         git maintenance unregister &&
379         git config --global --get-all maintenance.repo >actual &&
380         test_cmp before actual
381 '
382
383 test_expect_success 'start from empty cron table' '
384         GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt" git maintenance start &&
385
386         # start registers the repo
387         git config --get --global maintenance.repo "$(pwd)" &&
388
389         grep "for-each-repo --config=maintenance.repo maintenance run --schedule=daily" cron.txt &&
390         grep "for-each-repo --config=maintenance.repo maintenance run --schedule=hourly" cron.txt &&
391         grep "for-each-repo --config=maintenance.repo maintenance run --schedule=weekly" cron.txt
392 '
393
394 test_expect_success 'stop from existing schedule' '
395         GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt" git maintenance stop &&
396
397         # stop does not unregister the repo
398         git config --get --global maintenance.repo "$(pwd)" &&
399
400         # Operation is idempotent
401         GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt" git maintenance stop &&
402         test_must_be_empty cron.txt
403 '
404
405 test_expect_success 'start preserves existing schedule' '
406         echo "Important information!" >cron.txt &&
407         GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt" git maintenance start &&
408         grep "Important information!" cron.txt
409 '
410
411 test_expect_success 'start and stop macOS maintenance' '
412         # ensure $HOME can be compared against hook arguments on all platforms
413         pfx=$(cd "$HOME" && pwd) &&
414
415         write_script print-args <<-\EOF &&
416         echo $* | sed "s:gui/[0-9][0-9]*:gui/[UID]:" >>args
417         EOF
418
419         rm -f args &&
420         GIT_TEST_MAINT_SCHEDULER=launchctl:./print-args git maintenance start &&
421
422         # start registers the repo
423         git config --get --global maintenance.repo "$(pwd)" &&
424
425         ls "$HOME/Library/LaunchAgents" >actual &&
426         cat >expect <<-\EOF &&
427         org.git-scm.git.daily.plist
428         org.git-scm.git.hourly.plist
429         org.git-scm.git.weekly.plist
430         EOF
431         test_cmp expect actual &&
432
433         rm -f expect &&
434         for frequency in hourly daily weekly
435         do
436                 PLIST="$pfx/Library/LaunchAgents/org.git-scm.git.$frequency.plist" &&
437                 test_xmllint "$PLIST" &&
438                 grep schedule=$frequency "$PLIST" &&
439                 echo "bootout gui/[UID] $PLIST" >>expect &&
440                 echo "bootstrap gui/[UID] $PLIST" >>expect || return 1
441         done &&
442         test_cmp expect args &&
443
444         rm -f args &&
445         GIT_TEST_MAINT_SCHEDULER=launchctl:./print-args git maintenance stop &&
446
447         # stop does not unregister the repo
448         git config --get --global maintenance.repo "$(pwd)" &&
449
450         printf "bootout gui/[UID] $pfx/Library/LaunchAgents/org.git-scm.git.%s.plist\n" \
451                 hourly daily weekly >expect &&
452         test_cmp expect args &&
453         ls "$HOME/Library/LaunchAgents" >actual &&
454         test_line_count = 0 actual
455 '
456
457 test_expect_success 'register preserves existing strategy' '
458         git config maintenance.strategy none &&
459         git maintenance register &&
460         test_config maintenance.strategy none &&
461         git config --unset maintenance.strategy &&
462         git maintenance register &&
463         test_config maintenance.strategy incremental
464 '
465
466 test_done