Skip to content

Commit 4dda60c

Browse files
committed
Merge branch 'ps/maintenance-missing-tasks'
Make repository clean-up tasks "gc" can do available to "git maintenance" front-end. * ps/maintenance-missing-tasks: builtin/maintenance: introduce "rerere-gc" task builtin/gc: move rerere garbage collection into separate function builtin/maintenance: introduce "worktree-prune" task builtin/gc: move pruning of worktrees into a separate function builtin/gc: remove global variables where it is trivial to do builtin/gc: fix indentation of `cmd_gc()` parameters
2 parents 1d01042 + 283621a commit 4dda60c

File tree

4 files changed

+257
-31
lines changed

4 files changed

+257
-31
lines changed

Documentation/config/maintenance.adoc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,20 @@ maintenance.reflog-expire.auto::
8383
positive value implies the command should run when the number of
8484
expired reflog entries in the "HEAD" reflog is at least the value of
8585
`maintenance.loose-objects.auto`. The default value is 100.
86+
87+
maintenance.rerere-gc.auto::
88+
This integer config option controls how often the `rerere-gc` task
89+
should be run as part of `git maintenance run --auto`. If zero, then
90+
the `rerere-gc` task will not run with the `--auto` option. A negative
91+
value will force the task to run every time. Otherwise, any positive
92+
value implies the command will run when the "rr-cache" directory exists
93+
and has at least one entry, regardless of whether it is stale or not.
94+
This heuristic may be refined in the future. The default value is 1.
95+
96+
maintenance.worktree-prune.auto::
97+
This integer config option controls how often the `worktree-prune` task
98+
should be run as part of `git maintenance run --auto`. If zero, then
99+
the `worktree-prune` task will not run with the `--auto` option. A
100+
negative value will force the task to run every time. Otherwise, a
101+
positive value implies the command should run when the number of
102+
prunable worktrees exceeds the value. The default value is 1.

Documentation/git-maintenance.adoc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,14 @@ reflog-expire::
166166
The `reflog-expire` task deletes any entries in the reflog older than the
167167
expiry threshold. See linkgit:git-reflog[1] for more information.
168168

169+
rerere-gc::
170+
The `rerere-gc` task invokes garbage collection for stale entries in
171+
the rerere cache. See linkgit:git-rerere[1] for more information.
172+
173+
worktree-prune::
174+
The `worktree-prune` task deletes stale or broken worktrees. See
175+
linkit:git-worktree[1] for more information.
176+
169177
OPTIONS
170178
-------
171179
--auto::

builtin/gc.c

Lines changed: 117 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "builtin.h"
1717
#include "abspath.h"
1818
#include "date.h"
19+
#include "dir.h"
1920
#include "environment.h"
2021
#include "hex.h"
2122
#include "config.h"
@@ -33,6 +34,7 @@
3334
#include "pack-objects.h"
3435
#include "path.h"
3536
#include "reflog.h"
37+
#include "rerere.h"
3638
#include "blob.h"
3739
#include "tree.h"
3840
#include "promisor-remote.h"
@@ -43,6 +45,7 @@
4345
#include "hook.h"
4446
#include "setup.h"
4547
#include "trace2.h"
48+
#include "worktree.h"
4649

4750
#define FAILED_RUN "failed to run %s"
4851

@@ -52,15 +55,9 @@ static const char * const builtin_gc_usage[] = {
5255
};
5356

5457
static timestamp_t gc_log_expire_time;
55-
5658
static struct strvec repack = STRVEC_INIT;
57-
static struct strvec prune = STRVEC_INIT;
58-
static struct strvec prune_worktrees = STRVEC_INIT;
59-
static struct strvec rerere = STRVEC_INIT;
60-
6159
static struct tempfile *pidfile;
6260
static struct lock_file log_lock;
63-
6461
static struct string_list pack_garbage = STRING_LIST_INIT_DUP;
6562

6663
static void clean_pack_garbage(void)
@@ -339,6 +336,94 @@ static int maintenance_task_reflog_expire(struct maintenance_run_opts *opts UNUS
339336
return run_command(&cmd);
340337
}
341338

339+
static int maintenance_task_worktree_prune(struct maintenance_run_opts *opts UNUSED,
340+
struct gc_config *cfg)
341+
{
342+
struct child_process prune_worktrees_cmd = CHILD_PROCESS_INIT;
343+
344+
prune_worktrees_cmd.git_cmd = 1;
345+
strvec_pushl(&prune_worktrees_cmd.args, "worktree", "prune", "--expire", NULL);
346+
strvec_push(&prune_worktrees_cmd.args, cfg->prune_worktrees_expire);
347+
348+
return run_command(&prune_worktrees_cmd);
349+
}
350+
351+
static int worktree_prune_condition(struct gc_config *cfg)
352+
{
353+
struct strbuf buf = STRBUF_INIT;
354+
int should_prune = 0, limit = 1;
355+
timestamp_t expiry_date;
356+
struct dirent *d;
357+
DIR *dir = NULL;
358+
359+
git_config_get_int("maintenance.worktree-prune.auto", &limit);
360+
if (limit <= 0) {
361+
should_prune = limit < 0;
362+
goto out;
363+
}
364+
365+
if (parse_expiry_date(cfg->prune_worktrees_expire, &expiry_date))
366+
goto out;
367+
368+
dir = opendir(repo_git_path_replace(the_repository, &buf, "worktrees"));
369+
if (!dir)
370+
goto out;
371+
372+
while (limit && (d = readdir_skip_dot_and_dotdot(dir))) {
373+
char *wtpath;
374+
strbuf_reset(&buf);
375+
if (should_prune_worktree(d->d_name, &buf, &wtpath, expiry_date))
376+
limit--;
377+
free(wtpath);
378+
}
379+
380+
should_prune = !limit;
381+
382+
out:
383+
if (dir)
384+
closedir(dir);
385+
strbuf_release(&buf);
386+
return should_prune;
387+
}
388+
389+
static int maintenance_task_rerere_gc(struct maintenance_run_opts *opts UNUSED,
390+
struct gc_config *cfg UNUSED)
391+
{
392+
struct child_process rerere_cmd = CHILD_PROCESS_INIT;
393+
rerere_cmd.git_cmd = 1;
394+
strvec_pushl(&rerere_cmd.args, "rerere", "gc", NULL);
395+
return run_command(&rerere_cmd);
396+
}
397+
398+
static int rerere_gc_condition(struct gc_config *cfg UNUSED)
399+
{
400+
struct strbuf path = STRBUF_INIT;
401+
int should_gc = 0, limit = 1;
402+
DIR *dir = NULL;
403+
404+
git_config_get_int("maintenance.rerere-gc.auto", &limit);
405+
if (limit <= 0) {
406+
should_gc = limit < 0;
407+
goto out;
408+
}
409+
410+
/*
411+
* We skip garbage collection in case we either have no "rr-cache"
412+
* directory or when it doesn't contain at least one entry.
413+
*/
414+
repo_git_path_replace(the_repository, &path, "rr-cache");
415+
dir = opendir(path.buf);
416+
if (!dir)
417+
goto out;
418+
should_gc = !!readdir_skip_dot_and_dotdot(dir);
419+
420+
out:
421+
strbuf_release(&path);
422+
if (dir)
423+
closedir(dir);
424+
return should_gc;
425+
}
426+
342427
static int too_many_loose_objects(struct gc_config *cfg)
343428
{
344429
/*
@@ -728,9 +813,9 @@ static void gc_before_repack(struct maintenance_run_opts *opts,
728813
}
729814

730815
int cmd_gc(int argc,
731-
const char **argv,
732-
const char *prefix,
733-
struct repository *repo UNUSED)
816+
const char **argv,
817+
const char *prefix,
818+
struct repository *repo UNUSED)
734819
{
735820
int aggressive = 0;
736821
int quiet = 0;
@@ -740,7 +825,6 @@ struct repository *repo UNUSED)
740825
int daemonized = 0;
741826
int keep_largest_pack = -1;
742827
timestamp_t dummy;
743-
struct child_process rerere_cmd = CHILD_PROCESS_INIT;
744828
struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT;
745829
struct gc_config cfg = GC_CONFIG_INIT;
746830
const char *prune_expire_sentinel = "sentinel";
@@ -779,9 +863,6 @@ struct repository *repo UNUSED)
779863
builtin_gc_usage, builtin_gc_options);
780864

781865
strvec_pushl(&repack, "repack", "-d", "-l", NULL);
782-
strvec_pushl(&prune, "prune", "--expire", NULL);
783-
strvec_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL);
784-
strvec_pushl(&rerere, "rerere", "gc", NULL);
785866

786867
gc_config(&cfg);
787868

@@ -907,34 +988,27 @@ struct repository *repo UNUSED)
907988
if (cfg.prune_expire) {
908989
struct child_process prune_cmd = CHILD_PROCESS_INIT;
909990

991+
strvec_pushl(&prune_cmd.args, "prune", "--expire", NULL);
910992
/* run `git prune` even if using cruft packs */
911-
strvec_push(&prune, cfg.prune_expire);
993+
strvec_push(&prune_cmd.args, cfg.prune_expire);
912994
if (quiet)
913-
strvec_push(&prune, "--no-progress");
995+
strvec_push(&prune_cmd.args, "--no-progress");
914996
if (repo_has_promisor_remote(the_repository))
915-
strvec_push(&prune,
997+
strvec_push(&prune_cmd.args,
916998
"--exclude-promisor-objects");
917999
prune_cmd.git_cmd = 1;
918-
strvec_pushv(&prune_cmd.args, prune.v);
1000+
9191001
if (run_command(&prune_cmd))
920-
die(FAILED_RUN, prune.v[0]);
1002+
die(FAILED_RUN, prune_cmd.args.v[0]);
9211003
}
9221004
}
9231005

924-
if (cfg.prune_worktrees_expire) {
925-
struct child_process prune_worktrees_cmd = CHILD_PROCESS_INIT;
926-
927-
strvec_push(&prune_worktrees, cfg.prune_worktrees_expire);
928-
prune_worktrees_cmd.git_cmd = 1;
929-
strvec_pushv(&prune_worktrees_cmd.args, prune_worktrees.v);
930-
if (run_command(&prune_worktrees_cmd))
931-
die(FAILED_RUN, prune_worktrees.v[0]);
932-
}
1006+
if (cfg.prune_worktrees_expire &&
1007+
maintenance_task_worktree_prune(&opts, &cfg))
1008+
die(FAILED_RUN, "worktree");
9331009

934-
rerere_cmd.git_cmd = 1;
935-
strvec_pushv(&rerere_cmd.args, rerere.v);
936-
if (run_command(&rerere_cmd))
937-
die(FAILED_RUN, rerere.v[0]);
1010+
if (maintenance_task_rerere_gc(&opts, &cfg))
1011+
die(FAILED_RUN, "rerere");
9381012

9391013
report_garbage = report_pack_garbage;
9401014
reprepare_packed_git(the_repository);
@@ -1467,6 +1541,8 @@ enum maintenance_task_label {
14671541
TASK_COMMIT_GRAPH,
14681542
TASK_PACK_REFS,
14691543
TASK_REFLOG_EXPIRE,
1544+
TASK_WORKTREE_PRUNE,
1545+
TASK_RERERE_GC,
14701546

14711547
/* Leave as final value */
14721548
TASK__COUNT
@@ -1508,6 +1584,16 @@ static struct maintenance_task tasks[] = {
15081584
maintenance_task_reflog_expire,
15091585
reflog_expire_condition,
15101586
},
1587+
[TASK_WORKTREE_PRUNE] = {
1588+
"worktree-prune",
1589+
maintenance_task_worktree_prune,
1590+
worktree_prune_condition,
1591+
},
1592+
[TASK_RERERE_GC] = {
1593+
"rerere-gc",
1594+
maintenance_task_rerere_gc,
1595+
rerere_gc_condition,
1596+
},
15111597
};
15121598

15131599
static int compare_tasks_by_selection(const void *a_, const void *b_)

t/t7900-maintenance.sh

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,121 @@ test_expect_success 'reflog-expire task --auto only packs when exceeding limits'
493493
test_subcommand git reflog expire --all <reflog-expire-auto.txt
494494
'
495495

496+
test_expect_worktree_prune () {
497+
negate=
498+
if test "$1" = "!"
499+
then
500+
negate="!"
501+
shift
502+
fi
503+
504+
rm -f "worktree-prune.txt" &&
505+
GIT_TRACE2_EVENT="$(pwd)/worktree-prune.txt" "$@" &&
506+
test_subcommand $negate git worktree prune --expire 3.months.ago <worktree-prune.txt
507+
}
508+
509+
test_expect_success 'worktree-prune task without --auto always prunes' '
510+
test_expect_worktree_prune git maintenance run --task=worktree-prune
511+
'
512+
513+
test_expect_success 'worktree-prune task --auto only prunes with prunable worktree' '
514+
test_expect_worktree_prune ! git maintenance run --auto --task=worktree-prune &&
515+
mkdir .git/worktrees &&
516+
: >.git/worktrees/abc &&
517+
test_expect_worktree_prune git maintenance run --auto --task=worktree-prune
518+
'
519+
520+
test_expect_success 'worktree-prune task with --auto honors maintenance.worktree-prune.auto' '
521+
# A negative value should always prune.
522+
test_expect_worktree_prune git -c maintenance.worktree-prune.auto=-1 maintenance run --auto --task=worktree-prune &&
523+
524+
mkdir .git/worktrees &&
525+
: >.git/worktrees/first &&
526+
: >.git/worktrees/second &&
527+
: >.git/worktrees/third &&
528+
529+
# Zero should never prune.
530+
test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=0 maintenance run --auto --task=worktree-prune &&
531+
# A positive value should require at least this many prunable worktrees.
532+
test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=4 maintenance run --auto --task=worktree-prune &&
533+
test_expect_worktree_prune git -c maintenance.worktree-prune.auto=3 maintenance run --auto --task=worktree-prune
534+
'
535+
536+
test_expect_success 'worktree-prune task with --auto honors maintenance.worktree-prune.auto' '
537+
# A negative value should always prune.
538+
test_expect_worktree_prune git -c maintenance.worktree-prune.auto=-1 maintenance run --auto --task=worktree-prune &&
539+
540+
mkdir .git/worktrees &&
541+
: >.git/worktrees/first &&
542+
: >.git/worktrees/second &&
543+
: >.git/worktrees/third &&
544+
545+
# Zero should never prune.
546+
test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=0 maintenance run --auto --task=worktree-prune &&
547+
# A positive value should require at least this many prunable worktrees.
548+
test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=4 maintenance run --auto --task=worktree-prune &&
549+
test_expect_worktree_prune git -c maintenance.worktree-prune.auto=3 maintenance run --auto --task=worktree-prune
550+
'
551+
552+
test_expect_success 'worktree-prune task honors gc.worktreePruneExpire' '
553+
git worktree add worktree &&
554+
rm -rf worktree &&
555+
556+
rm -f worktree-prune.txt &&
557+
GIT_TRACE2_EVENT="$(pwd)/worktree-prune.txt" git -c gc.worktreePruneExpire=1.week.ago maintenance run --auto --task=worktree-prune &&
558+
test_subcommand ! git worktree prune --expire 1.week.ago <worktree-prune.txt &&
559+
test_path_is_dir .git/worktrees/worktree &&
560+
561+
rm -f worktree-prune.txt &&
562+
GIT_TRACE2_EVENT="$(pwd)/worktree-prune.txt" git -c gc.worktreePruneExpire=now maintenance run --auto --task=worktree-prune &&
563+
test_subcommand git worktree prune --expire now <worktree-prune.txt &&
564+
test_path_is_missing .git/worktrees/worktree
565+
'
566+
567+
test_expect_rerere_gc () {
568+
negate=
569+
if test "$1" = "!"
570+
then
571+
negate="!"
572+
shift
573+
fi
574+
575+
rm -f "rerere-gc.txt" &&
576+
GIT_TRACE2_EVENT="$(pwd)/rerere-gc.txt" "$@" &&
577+
test_subcommand $negate git rerere gc <rerere-gc.txt
578+
}
579+
580+
test_expect_success 'rerere-gc task without --auto always collects garbage' '
581+
test_expect_rerere_gc git maintenance run --task=rerere-gc
582+
'
583+
584+
test_expect_success 'rerere-gc task with --auto only prunes with prunable entries' '
585+
test_when_finished "rm -rf .git/rr-cache" &&
586+
test_expect_rerere_gc ! git maintenance run --auto --task=rerere-gc &&
587+
mkdir .git/rr-cache &&
588+
test_expect_rerere_gc ! git maintenance run --auto --task=rerere-gc &&
589+
: >.git/rr-cache/entry &&
590+
test_expect_rerere_gc git maintenance run --auto --task=rerere-gc
591+
'
592+
593+
test_expect_success 'rerere-gc task with --auto honors maintenance.rerere-gc.auto' '
594+
test_when_finished "rm -rf .git/rr-cache" &&
595+
596+
# A negative value should always prune.
597+
test_expect_rerere_gc git -c maintenance.rerere-gc.auto=-1 maintenance run --auto --task=rerere-gc &&
598+
599+
# A positive value prunes when there is at least one entry.
600+
test_expect_rerere_gc ! git -c maintenance.rerere-gc.auto=9000 maintenance run --auto --task=rerere-gc &&
601+
mkdir .git/rr-cache &&
602+
test_expect_rerere_gc ! git -c maintenance.rerere-gc.auto=9000 maintenance run --auto --task=rerere-gc &&
603+
: >.git/rr-cache/entry-1 &&
604+
test_expect_rerere_gc git -c maintenance.rerere-gc.auto=9000 maintenance run --auto --task=rerere-gc &&
605+
606+
# Zero should never prune.
607+
: >.git/rr-cache/entry-1 &&
608+
test_expect_rerere_gc ! git -c maintenance.rerere-gc.auto=0 maintenance run --auto --task=rerere-gc
609+
'
610+
496611
test_expect_success '--auto and --schedule incompatible' '
497612
test_must_fail git maintenance run --auto --schedule=daily 2>err &&
498613
test_grep "at most one" err

0 commit comments

Comments
 (0)