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()` parametersmaint
commit
4dda60c9df
|
@ -83,3 +83,20 @@ maintenance.reflog-expire.auto::
|
|||
positive value implies the command should run when the number of
|
||||
expired reflog entries in the "HEAD" reflog is at least the value of
|
||||
`maintenance.loose-objects.auto`. The default value is 100.
|
||||
|
||||
maintenance.rerere-gc.auto::
|
||||
This integer config option controls how often the `rerere-gc` task
|
||||
should be run as part of `git maintenance run --auto`. If zero, then
|
||||
the `rerere-gc` task will not run with the `--auto` option. A negative
|
||||
value will force the task to run every time. Otherwise, any positive
|
||||
value implies the command will run when the "rr-cache" directory exists
|
||||
and has at least one entry, regardless of whether it is stale or not.
|
||||
This heuristic may be refined in the future. The default value is 1.
|
||||
|
||||
maintenance.worktree-prune.auto::
|
||||
This integer config option controls how often the `worktree-prune` task
|
||||
should be run as part of `git maintenance run --auto`. If zero, then
|
||||
the `worktree-prune` task will not run with the `--auto` option. A
|
||||
negative value will force the task to run every time. Otherwise, a
|
||||
positive value implies the command should run when the number of
|
||||
prunable worktrees exceeds the value. The default value is 1.
|
||||
|
|
|
@ -166,6 +166,14 @@ reflog-expire::
|
|||
The `reflog-expire` task deletes any entries in the reflog older than the
|
||||
expiry threshold. See linkgit:git-reflog[1] for more information.
|
||||
|
||||
rerere-gc::
|
||||
The `rerere-gc` task invokes garbage collection for stale entries in
|
||||
the rerere cache. See linkgit:git-rerere[1] for more information.
|
||||
|
||||
worktree-prune::
|
||||
The `worktree-prune` task deletes stale or broken worktrees. See
|
||||
linkit:git-worktree[1] for more information.
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
--auto::
|
||||
|
|
148
builtin/gc.c
148
builtin/gc.c
|
@ -16,6 +16,7 @@
|
|||
#include "builtin.h"
|
||||
#include "abspath.h"
|
||||
#include "date.h"
|
||||
#include "dir.h"
|
||||
#include "environment.h"
|
||||
#include "hex.h"
|
||||
#include "config.h"
|
||||
|
@ -33,6 +34,7 @@
|
|||
#include "pack-objects.h"
|
||||
#include "path.h"
|
||||
#include "reflog.h"
|
||||
#include "rerere.h"
|
||||
#include "blob.h"
|
||||
#include "tree.h"
|
||||
#include "promisor-remote.h"
|
||||
|
@ -43,6 +45,7 @@
|
|||
#include "hook.h"
|
||||
#include "setup.h"
|
||||
#include "trace2.h"
|
||||
#include "worktree.h"
|
||||
|
||||
#define FAILED_RUN "failed to run %s"
|
||||
|
||||
|
@ -52,15 +55,9 @@ static const char * const builtin_gc_usage[] = {
|
|||
};
|
||||
|
||||
static timestamp_t gc_log_expire_time;
|
||||
|
||||
static struct strvec repack = STRVEC_INIT;
|
||||
static struct strvec prune = STRVEC_INIT;
|
||||
static struct strvec prune_worktrees = STRVEC_INIT;
|
||||
static struct strvec rerere = STRVEC_INIT;
|
||||
|
||||
static struct tempfile *pidfile;
|
||||
static struct lock_file log_lock;
|
||||
|
||||
static struct string_list pack_garbage = STRING_LIST_INIT_DUP;
|
||||
|
||||
static void clean_pack_garbage(void)
|
||||
|
@ -339,6 +336,94 @@ static int maintenance_task_reflog_expire(struct maintenance_run_opts *opts UNUS
|
|||
return run_command(&cmd);
|
||||
}
|
||||
|
||||
static int maintenance_task_worktree_prune(struct maintenance_run_opts *opts UNUSED,
|
||||
struct gc_config *cfg)
|
||||
{
|
||||
struct child_process prune_worktrees_cmd = CHILD_PROCESS_INIT;
|
||||
|
||||
prune_worktrees_cmd.git_cmd = 1;
|
||||
strvec_pushl(&prune_worktrees_cmd.args, "worktree", "prune", "--expire", NULL);
|
||||
strvec_push(&prune_worktrees_cmd.args, cfg->prune_worktrees_expire);
|
||||
|
||||
return run_command(&prune_worktrees_cmd);
|
||||
}
|
||||
|
||||
static int worktree_prune_condition(struct gc_config *cfg)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
int should_prune = 0, limit = 1;
|
||||
timestamp_t expiry_date;
|
||||
struct dirent *d;
|
||||
DIR *dir = NULL;
|
||||
|
||||
git_config_get_int("maintenance.worktree-prune.auto", &limit);
|
||||
if (limit <= 0) {
|
||||
should_prune = limit < 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (parse_expiry_date(cfg->prune_worktrees_expire, &expiry_date))
|
||||
goto out;
|
||||
|
||||
dir = opendir(repo_git_path_replace(the_repository, &buf, "worktrees"));
|
||||
if (!dir)
|
||||
goto out;
|
||||
|
||||
while (limit && (d = readdir_skip_dot_and_dotdot(dir))) {
|
||||
char *wtpath;
|
||||
strbuf_reset(&buf);
|
||||
if (should_prune_worktree(d->d_name, &buf, &wtpath, expiry_date))
|
||||
limit--;
|
||||
free(wtpath);
|
||||
}
|
||||
|
||||
should_prune = !limit;
|
||||
|
||||
out:
|
||||
if (dir)
|
||||
closedir(dir);
|
||||
strbuf_release(&buf);
|
||||
return should_prune;
|
||||
}
|
||||
|
||||
static int maintenance_task_rerere_gc(struct maintenance_run_opts *opts UNUSED,
|
||||
struct gc_config *cfg UNUSED)
|
||||
{
|
||||
struct child_process rerere_cmd = CHILD_PROCESS_INIT;
|
||||
rerere_cmd.git_cmd = 1;
|
||||
strvec_pushl(&rerere_cmd.args, "rerere", "gc", NULL);
|
||||
return run_command(&rerere_cmd);
|
||||
}
|
||||
|
||||
static int rerere_gc_condition(struct gc_config *cfg UNUSED)
|
||||
{
|
||||
struct strbuf path = STRBUF_INIT;
|
||||
int should_gc = 0, limit = 1;
|
||||
DIR *dir = NULL;
|
||||
|
||||
git_config_get_int("maintenance.rerere-gc.auto", &limit);
|
||||
if (limit <= 0) {
|
||||
should_gc = limit < 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* We skip garbage collection in case we either have no "rr-cache"
|
||||
* directory or when it doesn't contain at least one entry.
|
||||
*/
|
||||
repo_git_path_replace(the_repository, &path, "rr-cache");
|
||||
dir = opendir(path.buf);
|
||||
if (!dir)
|
||||
goto out;
|
||||
should_gc = !!readdir_skip_dot_and_dotdot(dir);
|
||||
|
||||
out:
|
||||
strbuf_release(&path);
|
||||
if (dir)
|
||||
closedir(dir);
|
||||
return should_gc;
|
||||
}
|
||||
|
||||
static int too_many_loose_objects(struct gc_config *cfg)
|
||||
{
|
||||
/*
|
||||
|
@ -728,9 +813,9 @@ static void gc_before_repack(struct maintenance_run_opts *opts,
|
|||
}
|
||||
|
||||
int cmd_gc(int argc,
|
||||
const char **argv,
|
||||
const char *prefix,
|
||||
struct repository *repo UNUSED)
|
||||
const char **argv,
|
||||
const char *prefix,
|
||||
struct repository *repo UNUSED)
|
||||
{
|
||||
int aggressive = 0;
|
||||
int quiet = 0;
|
||||
|
@ -740,7 +825,6 @@ struct repository *repo UNUSED)
|
|||
int daemonized = 0;
|
||||
int keep_largest_pack = -1;
|
||||
timestamp_t dummy;
|
||||
struct child_process rerere_cmd = CHILD_PROCESS_INIT;
|
||||
struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT;
|
||||
struct gc_config cfg = GC_CONFIG_INIT;
|
||||
const char *prune_expire_sentinel = "sentinel";
|
||||
|
@ -779,9 +863,6 @@ struct repository *repo UNUSED)
|
|||
builtin_gc_usage, builtin_gc_options);
|
||||
|
||||
strvec_pushl(&repack, "repack", "-d", "-l", NULL);
|
||||
strvec_pushl(&prune, "prune", "--expire", NULL);
|
||||
strvec_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL);
|
||||
strvec_pushl(&rerere, "rerere", "gc", NULL);
|
||||
|
||||
gc_config(&cfg);
|
||||
|
||||
|
@ -907,34 +988,27 @@ struct repository *repo UNUSED)
|
|||
if (cfg.prune_expire) {
|
||||
struct child_process prune_cmd = CHILD_PROCESS_INIT;
|
||||
|
||||
strvec_pushl(&prune_cmd.args, "prune", "--expire", NULL);
|
||||
/* run `git prune` even if using cruft packs */
|
||||
strvec_push(&prune, cfg.prune_expire);
|
||||
strvec_push(&prune_cmd.args, cfg.prune_expire);
|
||||
if (quiet)
|
||||
strvec_push(&prune, "--no-progress");
|
||||
strvec_push(&prune_cmd.args, "--no-progress");
|
||||
if (repo_has_promisor_remote(the_repository))
|
||||
strvec_push(&prune,
|
||||
strvec_push(&prune_cmd.args,
|
||||
"--exclude-promisor-objects");
|
||||
prune_cmd.git_cmd = 1;
|
||||
strvec_pushv(&prune_cmd.args, prune.v);
|
||||
|
||||
if (run_command(&prune_cmd))
|
||||
die(FAILED_RUN, prune.v[0]);
|
||||
die(FAILED_RUN, prune_cmd.args.v[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (cfg.prune_worktrees_expire) {
|
||||
struct child_process prune_worktrees_cmd = CHILD_PROCESS_INIT;
|
||||
if (cfg.prune_worktrees_expire &&
|
||||
maintenance_task_worktree_prune(&opts, &cfg))
|
||||
die(FAILED_RUN, "worktree");
|
||||
|
||||
strvec_push(&prune_worktrees, cfg.prune_worktrees_expire);
|
||||
prune_worktrees_cmd.git_cmd = 1;
|
||||
strvec_pushv(&prune_worktrees_cmd.args, prune_worktrees.v);
|
||||
if (run_command(&prune_worktrees_cmd))
|
||||
die(FAILED_RUN, prune_worktrees.v[0]);
|
||||
}
|
||||
|
||||
rerere_cmd.git_cmd = 1;
|
||||
strvec_pushv(&rerere_cmd.args, rerere.v);
|
||||
if (run_command(&rerere_cmd))
|
||||
die(FAILED_RUN, rerere.v[0]);
|
||||
if (maintenance_task_rerere_gc(&opts, &cfg))
|
||||
die(FAILED_RUN, "rerere");
|
||||
|
||||
report_garbage = report_pack_garbage;
|
||||
reprepare_packed_git(the_repository);
|
||||
|
@ -1467,6 +1541,8 @@ enum maintenance_task_label {
|
|||
TASK_COMMIT_GRAPH,
|
||||
TASK_PACK_REFS,
|
||||
TASK_REFLOG_EXPIRE,
|
||||
TASK_WORKTREE_PRUNE,
|
||||
TASK_RERERE_GC,
|
||||
|
||||
/* Leave as final value */
|
||||
TASK__COUNT
|
||||
|
@ -1508,6 +1584,16 @@ static struct maintenance_task tasks[] = {
|
|||
maintenance_task_reflog_expire,
|
||||
reflog_expire_condition,
|
||||
},
|
||||
[TASK_WORKTREE_PRUNE] = {
|
||||
"worktree-prune",
|
||||
maintenance_task_worktree_prune,
|
||||
worktree_prune_condition,
|
||||
},
|
||||
[TASK_RERERE_GC] = {
|
||||
"rerere-gc",
|
||||
maintenance_task_rerere_gc,
|
||||
rerere_gc_condition,
|
||||
},
|
||||
};
|
||||
|
||||
static int compare_tasks_by_selection(const void *a_, const void *b_)
|
||||
|
|
|
@ -493,6 +493,121 @@ test_expect_success 'reflog-expire task --auto only packs when exceeding limits'
|
|||
test_subcommand git reflog expire --all <reflog-expire-auto.txt
|
||||
'
|
||||
|
||||
test_expect_worktree_prune () {
|
||||
negate=
|
||||
if test "$1" = "!"
|
||||
then
|
||||
negate="!"
|
||||
shift
|
||||
fi
|
||||
|
||||
rm -f "worktree-prune.txt" &&
|
||||
GIT_TRACE2_EVENT="$(pwd)/worktree-prune.txt" "$@" &&
|
||||
test_subcommand $negate git worktree prune --expire 3.months.ago <worktree-prune.txt
|
||||
}
|
||||
|
||||
test_expect_success 'worktree-prune task without --auto always prunes' '
|
||||
test_expect_worktree_prune git maintenance run --task=worktree-prune
|
||||
'
|
||||
|
||||
test_expect_success 'worktree-prune task --auto only prunes with prunable worktree' '
|
||||
test_expect_worktree_prune ! git maintenance run --auto --task=worktree-prune &&
|
||||
mkdir .git/worktrees &&
|
||||
: >.git/worktrees/abc &&
|
||||
test_expect_worktree_prune git maintenance run --auto --task=worktree-prune
|
||||
'
|
||||
|
||||
test_expect_success 'worktree-prune task with --auto honors maintenance.worktree-prune.auto' '
|
||||
# A negative value should always prune.
|
||||
test_expect_worktree_prune git -c maintenance.worktree-prune.auto=-1 maintenance run --auto --task=worktree-prune &&
|
||||
|
||||
mkdir .git/worktrees &&
|
||||
: >.git/worktrees/first &&
|
||||
: >.git/worktrees/second &&
|
||||
: >.git/worktrees/third &&
|
||||
|
||||
# Zero should never prune.
|
||||
test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=0 maintenance run --auto --task=worktree-prune &&
|
||||
# A positive value should require at least this many prunable worktrees.
|
||||
test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=4 maintenance run --auto --task=worktree-prune &&
|
||||
test_expect_worktree_prune git -c maintenance.worktree-prune.auto=3 maintenance run --auto --task=worktree-prune
|
||||
'
|
||||
|
||||
test_expect_success 'worktree-prune task with --auto honors maintenance.worktree-prune.auto' '
|
||||
# A negative value should always prune.
|
||||
test_expect_worktree_prune git -c maintenance.worktree-prune.auto=-1 maintenance run --auto --task=worktree-prune &&
|
||||
|
||||
mkdir .git/worktrees &&
|
||||
: >.git/worktrees/first &&
|
||||
: >.git/worktrees/second &&
|
||||
: >.git/worktrees/third &&
|
||||
|
||||
# Zero should never prune.
|
||||
test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=0 maintenance run --auto --task=worktree-prune &&
|
||||
# A positive value should require at least this many prunable worktrees.
|
||||
test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=4 maintenance run --auto --task=worktree-prune &&
|
||||
test_expect_worktree_prune git -c maintenance.worktree-prune.auto=3 maintenance run --auto --task=worktree-prune
|
||||
'
|
||||
|
||||
test_expect_success 'worktree-prune task honors gc.worktreePruneExpire' '
|
||||
git worktree add worktree &&
|
||||
rm -rf worktree &&
|
||||
|
||||
rm -f worktree-prune.txt &&
|
||||
GIT_TRACE2_EVENT="$(pwd)/worktree-prune.txt" git -c gc.worktreePruneExpire=1.week.ago maintenance run --auto --task=worktree-prune &&
|
||||
test_subcommand ! git worktree prune --expire 1.week.ago <worktree-prune.txt &&
|
||||
test_path_is_dir .git/worktrees/worktree &&
|
||||
|
||||
rm -f worktree-prune.txt &&
|
||||
GIT_TRACE2_EVENT="$(pwd)/worktree-prune.txt" git -c gc.worktreePruneExpire=now maintenance run --auto --task=worktree-prune &&
|
||||
test_subcommand git worktree prune --expire now <worktree-prune.txt &&
|
||||
test_path_is_missing .git/worktrees/worktree
|
||||
'
|
||||
|
||||
test_expect_rerere_gc () {
|
||||
negate=
|
||||
if test "$1" = "!"
|
||||
then
|
||||
negate="!"
|
||||
shift
|
||||
fi
|
||||
|
||||
rm -f "rerere-gc.txt" &&
|
||||
GIT_TRACE2_EVENT="$(pwd)/rerere-gc.txt" "$@" &&
|
||||
test_subcommand $negate git rerere gc <rerere-gc.txt
|
||||
}
|
||||
|
||||
test_expect_success 'rerere-gc task without --auto always collects garbage' '
|
||||
test_expect_rerere_gc git maintenance run --task=rerere-gc
|
||||
'
|
||||
|
||||
test_expect_success 'rerere-gc task with --auto only prunes with prunable entries' '
|
||||
test_when_finished "rm -rf .git/rr-cache" &&
|
||||
test_expect_rerere_gc ! git maintenance run --auto --task=rerere-gc &&
|
||||
mkdir .git/rr-cache &&
|
||||
test_expect_rerere_gc ! git maintenance run --auto --task=rerere-gc &&
|
||||
: >.git/rr-cache/entry &&
|
||||
test_expect_rerere_gc git maintenance run --auto --task=rerere-gc
|
||||
'
|
||||
|
||||
test_expect_success 'rerere-gc task with --auto honors maintenance.rerere-gc.auto' '
|
||||
test_when_finished "rm -rf .git/rr-cache" &&
|
||||
|
||||
# A negative value should always prune.
|
||||
test_expect_rerere_gc git -c maintenance.rerere-gc.auto=-1 maintenance run --auto --task=rerere-gc &&
|
||||
|
||||
# A positive value prunes when there is at least one entry.
|
||||
test_expect_rerere_gc ! git -c maintenance.rerere-gc.auto=9000 maintenance run --auto --task=rerere-gc &&
|
||||
mkdir .git/rr-cache &&
|
||||
test_expect_rerere_gc ! git -c maintenance.rerere-gc.auto=9000 maintenance run --auto --task=rerere-gc &&
|
||||
: >.git/rr-cache/entry-1 &&
|
||||
test_expect_rerere_gc git -c maintenance.rerere-gc.auto=9000 maintenance run --auto --task=rerere-gc &&
|
||||
|
||||
# Zero should never prune.
|
||||
: >.git/rr-cache/entry-1 &&
|
||||
test_expect_rerere_gc ! git -c maintenance.rerere-gc.auto=0 maintenance run --auto --task=rerere-gc
|
||||
'
|
||||
|
||||
test_expect_success '--auto and --schedule incompatible' '
|
||||
test_must_fail git maintenance run --auto --schedule=daily 2>err &&
|
||||
test_grep "at most one" err
|
||||
|
|
Loading…
Reference in New Issue