Merge branch 'ps/maintenance-detach-fix'

Maintenance tasks other than "gc" now properly go background when
"git maintenance" runs them.

* ps/maintenance-detach-fix:
  run-command: fix detaching when running auto maintenance
  builtin/maintenance: add a `--detach` flag
  builtin/gc: add a `--detach` flag
  builtin/gc: stop processing log file on signal
  builtin/gc: fix leaking config values
  builtin/gc: refactor to read config into structure
  config: fix constness of out parameter for `git_config_get_expiry()`
This commit is contained in:
Junio C Hamano
2024-08-26 11:32:20 -07:00
12 changed files with 397 additions and 171 deletions

View File

@ -40,7 +40,8 @@ use, it'll affect how the auto pack limit works.
gc.autoDetach::
Make `git gc --auto` return immediately and run in the background
if the system supports it. Default is true.
if the system supports it. Default is true. This config variable acts
as a fallback in case `maintenance.autoDetach` is not set.
gc.bigPackThreshold::
If non-zero, all non-cruft packs larger than this limit are kept

View File

@ -3,6 +3,17 @@ maintenance.auto::
`git maintenance run --auto` after doing their normal work. Defaults
to true.
maintenance.autoDetach::
Many Git commands trigger automatic maintenance after they have
written data into the repository. This boolean config option
controls whether this automatic maintenance shall happen in the
foreground or whether the maintenance process shall detach and
continue to run in the background.
+
If unset, the value of `gc.autoDetach` is used as a fallback. Defaults
to true if both are unset, meaning that the maintenance process will
detach.
maintenance.strategy::
This string config option provides a way to specify one of a few
recommended schedules for background maintenance. This only affects

View File

@ -9,7 +9,7 @@ git-gc - Cleanup unnecessary files and optimize the local repository
SYNOPSIS
--------
[verse]
'git gc' [--aggressive] [--auto] [--quiet] [--prune=<date> | --no-prune] [--force] [--keep-largest-pack]
'git gc' [--aggressive] [--auto] [--[no-]detach] [--quiet] [--prune=<date> | --no-prune] [--force] [--keep-largest-pack]
DESCRIPTION
-----------
@ -53,6 +53,9 @@ configuration options such as `gc.auto` and `gc.autoPackLimit`, all
other housekeeping tasks (e.g. rerere, working trees, reflog...) will
be performed as well.
--[no-]detach::
Run in the background if the system supports it. This option overrides
the `gc.autoDetach` config.
--[no-]cruft::
When expiring unreachable objects, pack them separately into a

View File

@ -49,23 +49,7 @@ static const char * const builtin_gc_usage[] = {
NULL
};
static int pack_refs = 1;
static int prune_reflogs = 1;
static int cruft_packs = 1;
static unsigned long max_cruft_size;
static int aggressive_depth = 50;
static int aggressive_window = 250;
static int gc_auto_threshold = 6700;
static int gc_auto_pack_limit = 50;
static int detach_auto = 1;
static timestamp_t gc_log_expire_time;
static const char *gc_log_expire = "1.day.ago";
static const char *prune_expire = "2.weeks.ago";
static const char *prune_worktrees_expire = "3.months.ago";
static char *repack_filter;
static char *repack_filter_to;
static unsigned long big_pack_threshold;
static unsigned long max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE;
static struct strvec reflog = STRVEC_INIT;
static struct strvec repack = STRVEC_INIT;
@ -125,13 +109,6 @@ static void process_log_file_at_exit(void)
process_log_file();
}
static void process_log_file_on_signal(int signo)
{
process_log_file();
sigchain_pop(signo);
raise(signo);
}
static int gc_config_is_timestamp_never(const char *var)
{
const char *value;
@ -145,37 +122,100 @@ static int gc_config_is_timestamp_never(const char *var)
return 0;
}
static void gc_config(void)
struct gc_config {
int pack_refs;
int prune_reflogs;
int cruft_packs;
unsigned long max_cruft_size;
int aggressive_depth;
int aggressive_window;
int gc_auto_threshold;
int gc_auto_pack_limit;
int detach_auto;
char *gc_log_expire;
char *prune_expire;
char *prune_worktrees_expire;
char *repack_filter;
char *repack_filter_to;
unsigned long big_pack_threshold;
unsigned long max_delta_cache_size;
};
#define GC_CONFIG_INIT { \
.pack_refs = 1, \
.prune_reflogs = 1, \
.cruft_packs = 1, \
.aggressive_depth = 50, \
.aggressive_window = 250, \
.gc_auto_threshold = 6700, \
.gc_auto_pack_limit = 50, \
.detach_auto = 1, \
.gc_log_expire = xstrdup("1.day.ago"), \
.prune_expire = xstrdup("2.weeks.ago"), \
.prune_worktrees_expire = xstrdup("3.months.ago"), \
.max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE, \
}
static void gc_config_release(struct gc_config *cfg)
{
free(cfg->gc_log_expire);
free(cfg->prune_expire);
free(cfg->prune_worktrees_expire);
free(cfg->repack_filter);
free(cfg->repack_filter_to);
}
static void gc_config(struct gc_config *cfg)
{
const char *value;
char *owned = NULL;
if (!git_config_get_value("gc.packrefs", &value)) {
if (value && !strcmp(value, "notbare"))
pack_refs = -1;
cfg->pack_refs = -1;
else
pack_refs = git_config_bool("gc.packrefs", value);
cfg->pack_refs = git_config_bool("gc.packrefs", value);
}
if (gc_config_is_timestamp_never("gc.reflogexpire") &&
gc_config_is_timestamp_never("gc.reflogexpireunreachable"))
prune_reflogs = 0;
cfg->prune_reflogs = 0;
git_config_get_int("gc.aggressivewindow", &aggressive_window);
git_config_get_int("gc.aggressivedepth", &aggressive_depth);
git_config_get_int("gc.auto", &gc_auto_threshold);
git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit);
git_config_get_bool("gc.autodetach", &detach_auto);
git_config_get_bool("gc.cruftpacks", &cruft_packs);
git_config_get_ulong("gc.maxcruftsize", &max_cruft_size);
repo_config_get_expiry(the_repository, "gc.pruneexpire", &prune_expire);
repo_config_get_expiry(the_repository, "gc.worktreepruneexpire", &prune_worktrees_expire);
repo_config_get_expiry(the_repository, "gc.logexpiry", &gc_log_expire);
git_config_get_int("gc.aggressivewindow", &cfg->aggressive_window);
git_config_get_int("gc.aggressivedepth", &cfg->aggressive_depth);
git_config_get_int("gc.auto", &cfg->gc_auto_threshold);
git_config_get_int("gc.autopacklimit", &cfg->gc_auto_pack_limit);
git_config_get_bool("gc.autodetach", &cfg->detach_auto);
git_config_get_bool("gc.cruftpacks", &cfg->cruft_packs);
git_config_get_ulong("gc.maxcruftsize", &cfg->max_cruft_size);
git_config_get_ulong("gc.bigpackthreshold", &big_pack_threshold);
git_config_get_ulong("pack.deltacachesize", &max_delta_cache_size);
if (!repo_config_get_expiry(the_repository, "gc.pruneexpire", &owned)) {
free(cfg->prune_expire);
cfg->prune_expire = owned;
}
git_config_get_string("gc.repackfilter", &repack_filter);
git_config_get_string("gc.repackfilterto", &repack_filter_to);
if (!repo_config_get_expiry(the_repository, "gc.worktreepruneexpire", &owned)) {
free(cfg->prune_worktrees_expire);
cfg->prune_worktrees_expire = owned;
}
if (!repo_config_get_expiry(the_repository, "gc.logexpiry", &owned)) {
free(cfg->gc_log_expire);
cfg->gc_log_expire = owned;
}
git_config_get_ulong("gc.bigpackthreshold", &cfg->big_pack_threshold);
git_config_get_ulong("pack.deltacachesize", &cfg->max_delta_cache_size);
if (!git_config_get_string("gc.repackfilter", &owned)) {
free(cfg->repack_filter);
cfg->repack_filter = owned;
}
if (!git_config_get_string("gc.repackfilterto", &owned)) {
free(cfg->repack_filter_to);
cfg->repack_filter_to = owned;
}
git_config(git_default_config, NULL);
}
@ -202,11 +242,15 @@ static enum schedule_priority parse_schedule(const char *value)
struct maintenance_run_opts {
int auto_flag;
int detach;
int quiet;
enum schedule_priority schedule;
};
#define MAINTENANCE_RUN_OPTS_INIT { \
.detach = -1, \
}
static int pack_refs_condition(void)
static int pack_refs_condition(UNUSED struct gc_config *cfg)
{
/*
* The auto-repacking logic for refs is handled by the ref backends and
@ -216,7 +260,8 @@ static int pack_refs_condition(void)
return 1;
}
static int maintenance_task_pack_refs(MAYBE_UNUSED struct maintenance_run_opts *opts)
static int maintenance_task_pack_refs(MAYBE_UNUSED struct maintenance_run_opts *opts,
UNUSED struct gc_config *cfg)
{
struct child_process cmd = CHILD_PROCESS_INIT;
@ -228,7 +273,7 @@ static int maintenance_task_pack_refs(MAYBE_UNUSED struct maintenance_run_opts *
return run_command(&cmd);
}
static int too_many_loose_objects(void)
static int too_many_loose_objects(struct gc_config *cfg)
{
/*
* Quickly check if a "gc" is needed, by estimating how
@ -247,7 +292,7 @@ static int too_many_loose_objects(void)
if (!dir)
return 0;
auto_threshold = DIV_ROUND_UP(gc_auto_threshold, 256);
auto_threshold = DIV_ROUND_UP(cfg->gc_auto_threshold, 256);
while ((ent = readdir(dir)) != NULL) {
if (strspn(ent->d_name, "0123456789abcdef") != hexsz_loose ||
ent->d_name[hexsz_loose] != '\0')
@ -283,12 +328,12 @@ static struct packed_git *find_base_packs(struct string_list *packs,
return base;
}
static int too_many_packs(void)
static int too_many_packs(struct gc_config *cfg)
{
struct packed_git *p;
int cnt;
if (gc_auto_pack_limit <= 0)
if (cfg->gc_auto_pack_limit <= 0)
return 0;
for (cnt = 0, p = get_all_packs(the_repository); p; p = p->next) {
@ -302,7 +347,7 @@ static int too_many_packs(void)
*/
cnt++;
}
return gc_auto_pack_limit < cnt;
return cfg->gc_auto_pack_limit < cnt;
}
static uint64_t total_ram(void)
@ -336,7 +381,8 @@ static uint64_t total_ram(void)
return 0;
}
static uint64_t estimate_repack_memory(struct packed_git *pack)
static uint64_t estimate_repack_memory(struct gc_config *cfg,
struct packed_git *pack)
{
unsigned long nr_objects = repo_approximate_object_count(the_repository);
size_t os_cache, heap;
@ -373,7 +419,7 @@ static uint64_t estimate_repack_memory(struct packed_git *pack)
*/
heap += delta_base_cache_limit;
/* and of course pack-objects has its own delta cache */
heap += max_delta_cache_size;
heap += cfg->max_delta_cache_size;
return os_cache + heap;
}
@ -384,30 +430,31 @@ static int keep_one_pack(struct string_list_item *item, void *data UNUSED)
return 0;
}
static void add_repack_all_option(struct string_list *keep_pack)
static void add_repack_all_option(struct gc_config *cfg,
struct string_list *keep_pack)
{
if (prune_expire && !strcmp(prune_expire, "now"))
if (cfg->prune_expire && !strcmp(cfg->prune_expire, "now"))
strvec_push(&repack, "-a");
else if (cruft_packs) {
else if (cfg->cruft_packs) {
strvec_push(&repack, "--cruft");
if (prune_expire)
strvec_pushf(&repack, "--cruft-expiration=%s", prune_expire);
if (max_cruft_size)
if (cfg->prune_expire)
strvec_pushf(&repack, "--cruft-expiration=%s", cfg->prune_expire);
if (cfg->max_cruft_size)
strvec_pushf(&repack, "--max-cruft-size=%lu",
max_cruft_size);
cfg->max_cruft_size);
} else {
strvec_push(&repack, "-A");
if (prune_expire)
strvec_pushf(&repack, "--unpack-unreachable=%s", prune_expire);
if (cfg->prune_expire)
strvec_pushf(&repack, "--unpack-unreachable=%s", cfg->prune_expire);
}
if (keep_pack)
for_each_string_list(keep_pack, keep_one_pack, NULL);
if (repack_filter && *repack_filter)
strvec_pushf(&repack, "--filter=%s", repack_filter);
if (repack_filter_to && *repack_filter_to)
strvec_pushf(&repack, "--filter-to=%s", repack_filter_to);
if (cfg->repack_filter && *cfg->repack_filter)
strvec_pushf(&repack, "--filter=%s", cfg->repack_filter);
if (cfg->repack_filter_to && *cfg->repack_filter_to)
strvec_pushf(&repack, "--filter-to=%s", cfg->repack_filter_to);
}
static void add_repack_incremental_option(void)
@ -415,13 +462,13 @@ static void add_repack_incremental_option(void)
strvec_push(&repack, "--no-write-bitmap-index");
}
static int need_to_gc(void)
static int need_to_gc(struct gc_config *cfg)
{
/*
* Setting gc.auto to 0 or negative can disable the
* automatic gc.
*/
if (gc_auto_threshold <= 0)
if (cfg->gc_auto_threshold <= 0)
return 0;
/*
@ -430,13 +477,13 @@ static int need_to_gc(void)
* we run "repack -A -d -l". Otherwise we tell the caller
* there is no need.
*/
if (too_many_packs()) {
if (too_many_packs(cfg)) {
struct string_list keep_pack = STRING_LIST_INIT_NODUP;
if (big_pack_threshold) {
find_base_packs(&keep_pack, big_pack_threshold);
if (keep_pack.nr >= gc_auto_pack_limit) {
big_pack_threshold = 0;
if (cfg->big_pack_threshold) {
find_base_packs(&keep_pack, cfg->big_pack_threshold);
if (keep_pack.nr >= cfg->gc_auto_pack_limit) {
cfg->big_pack_threshold = 0;
string_list_clear(&keep_pack, 0);
find_base_packs(&keep_pack, 0);
}
@ -445,7 +492,7 @@ static int need_to_gc(void)
uint64_t mem_have, mem_want;
mem_have = total_ram();
mem_want = estimate_repack_memory(p);
mem_want = estimate_repack_memory(cfg, p);
/*
* Only allow 1/2 of memory for pack-objects, leave
@ -456,9 +503,9 @@ static int need_to_gc(void)
string_list_clear(&keep_pack, 0);
}
add_repack_all_option(&keep_pack);
add_repack_all_option(cfg, &keep_pack);
string_list_clear(&keep_pack, 0);
} else if (too_many_loose_objects())
} else if (too_many_loose_objects(cfg))
add_repack_incremental_option();
else
return 0;
@ -585,7 +632,8 @@ done:
return ret;
}
static void gc_before_repack(struct maintenance_run_opts *opts)
static void gc_before_repack(struct maintenance_run_opts *opts,
struct gc_config *cfg)
{
/*
* We may be called twice, as both the pre- and
@ -596,10 +644,10 @@ static void gc_before_repack(struct maintenance_run_opts *opts)
if (done++)
return;
if (pack_refs && maintenance_task_pack_refs(opts))
if (cfg->pack_refs && maintenance_task_pack_refs(opts, cfg))
die(FAILED_RUN, "pack-refs");
if (prune_reflogs) {
if (cfg->prune_reflogs) {
struct child_process cmd = CHILD_PROCESS_INIT;
cmd.git_cmd = 1;
@ -620,19 +668,25 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
int keep_largest_pack = -1;
timestamp_t dummy;
struct child_process rerere_cmd = CHILD_PROCESS_INIT;
struct maintenance_run_opts opts = {0};
struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT;
struct gc_config cfg = GC_CONFIG_INIT;
const char *prune_expire_sentinel = "sentinel";
const char *prune_expire_arg = prune_expire_sentinel;
int ret;
struct option builtin_gc_options[] = {
OPT__QUIET(&quiet, N_("suppress progress reporting")),
{ OPTION_STRING, 0, "prune", &prune_expire, N_("date"),
{ OPTION_STRING, 0, "prune", &prune_expire_arg, N_("date"),
N_("prune unreferenced objects"),
PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire },
OPT_BOOL(0, "cruft", &cruft_packs, N_("pack unreferenced objects separately")),
OPT_MAGNITUDE(0, "max-cruft-size", &max_cruft_size,
PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire_arg },
OPT_BOOL(0, "cruft", &cfg.cruft_packs, N_("pack unreferenced objects separately")),
OPT_MAGNITUDE(0, "max-cruft-size", &cfg.max_cruft_size,
N_("with --cruft, limit the size of new cruft packs")),
OPT_BOOL(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")),
OPT_BOOL_F(0, "auto", &opts.auto_flag, N_("enable auto-gc mode"),
PARSE_OPT_NOCOMPLETE),
OPT_BOOL(0, "detach", &opts.detach,
N_("perform garbage collection in the background")),
OPT_BOOL_F(0, "force", &force,
N_("force running gc even if there may be another gc running"),
PARSE_OPT_NOCOMPLETE),
@ -650,58 +704,87 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
strvec_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL);
strvec_pushl(&rerere, "rerere", "gc", NULL);
/* default expiry time, overwritten in gc_config */
gc_config();
if (parse_expiry_date(gc_log_expire, &gc_log_expire_time))
die(_("failed to parse gc.logExpiry value %s"), gc_log_expire);
gc_config(&cfg);
if (pack_refs < 0)
pack_refs = !is_bare_repository();
if (parse_expiry_date(cfg.gc_log_expire, &gc_log_expire_time))
die(_("failed to parse gc.logExpiry value %s"), cfg.gc_log_expire);
if (cfg.pack_refs < 0)
cfg.pack_refs = !is_bare_repository();
argc = parse_options(argc, argv, prefix, builtin_gc_options,
builtin_gc_usage, 0);
if (argc > 0)
usage_with_options(builtin_gc_usage, builtin_gc_options);
if (prune_expire && parse_expiry_date(prune_expire, &dummy))
die(_("failed to parse prune expiry value %s"), prune_expire);
if (prune_expire_arg != prune_expire_sentinel) {
free(cfg.prune_expire);
cfg.prune_expire = xstrdup_or_null(prune_expire_arg);
}
if (cfg.prune_expire && parse_expiry_date(cfg.prune_expire, &dummy))
die(_("failed to parse prune expiry value %s"), cfg.prune_expire);
if (aggressive) {
strvec_push(&repack, "-f");
if (aggressive_depth > 0)
strvec_pushf(&repack, "--depth=%d", aggressive_depth);
if (aggressive_window > 0)
strvec_pushf(&repack, "--window=%d", aggressive_window);
if (cfg.aggressive_depth > 0)
strvec_pushf(&repack, "--depth=%d", cfg.aggressive_depth);
if (cfg.aggressive_window > 0)
strvec_pushf(&repack, "--window=%d", cfg.aggressive_window);
}
if (quiet)
strvec_push(&repack, "-q");
if (opts.auto_flag) {
if (cfg.detach_auto && opts.detach < 0)
opts.detach = 1;
/*
* Auto-gc should be least intrusive as possible.
*/
if (!need_to_gc())
return 0;
if (!need_to_gc(&cfg)) {
ret = 0;
goto out;
}
if (!quiet) {
if (detach_auto)
if (opts.detach > 0)
fprintf(stderr, _("Auto packing the repository in background for optimum performance.\n"));
else
fprintf(stderr, _("Auto packing the repository for optimum performance.\n"));
fprintf(stderr, _("See \"git help gc\" for manual housekeeping.\n"));
}
if (detach_auto) {
int ret = report_last_gc_error();
} else {
struct string_list keep_pack = STRING_LIST_INIT_NODUP;
if (ret == 1)
if (keep_largest_pack != -1) {
if (keep_largest_pack)
find_base_packs(&keep_pack, 0);
} else if (cfg.big_pack_threshold) {
find_base_packs(&keep_pack, cfg.big_pack_threshold);
}
add_repack_all_option(&cfg, &keep_pack);
string_list_clear(&keep_pack, 0);
}
if (opts.detach > 0) {
ret = report_last_gc_error();
if (ret == 1) {
/* Last gc --auto failed. Skip this one. */
return 0;
else if (ret)
/* an I/O error occurred, already reported */
return ret;
ret = 0;
goto out;
if (lock_repo_for_gc(force, &pid))
return 0;
gc_before_repack(&opts); /* dies on failure */
} else if (ret) {
/* an I/O error occurred, already reported */
goto out;
}
if (lock_repo_for_gc(force, &pid)) {
ret = 0;
goto out;
}
gc_before_repack(&opts, &cfg); /* dies on failure */
delete_tempfile(&pidfile);
/*
@ -710,24 +793,14 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
*/
daemonized = !daemonize();
}
} else {
struct string_list keep_pack = STRING_LIST_INIT_NODUP;
if (keep_largest_pack != -1) {
if (keep_largest_pack)
find_base_packs(&keep_pack, 0);
} else if (big_pack_threshold) {
find_base_packs(&keep_pack, big_pack_threshold);
}
add_repack_all_option(&keep_pack);
string_list_clear(&keep_pack, 0);
}
name = lock_repo_for_gc(force, &pid);
if (name) {
if (opts.auto_flag)
return 0; /* be quiet on --auto */
if (opts.auto_flag) {
ret = 0;
goto out; /* be quiet on --auto */
}
die(_("gc is already running on machine '%s' pid %"PRIuMAX" (use --force if not)"),
name, (uintmax_t)pid);
}
@ -737,11 +810,10 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
git_path("gc.log"),
LOCK_DIE_ON_ERROR);
dup2(get_lock_file_fd(&log_lock), 2);
sigchain_push_common(process_log_file_on_signal);
atexit(process_log_file_at_exit);
}
gc_before_repack(&opts);
gc_before_repack(&opts, &cfg);
if (!repository_format_precious_objects) {
struct child_process repack_cmd = CHILD_PROCESS_INIT;
@ -752,11 +824,11 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
if (run_command(&repack_cmd))
die(FAILED_RUN, repack.v[0]);
if (prune_expire) {
if (cfg.prune_expire) {
struct child_process prune_cmd = CHILD_PROCESS_INIT;
/* run `git prune` even if using cruft packs */
strvec_push(&prune, prune_expire);
strvec_push(&prune, cfg.prune_expire);
if (quiet)
strvec_push(&prune, "--no-progress");
if (repo_has_promisor_remote(the_repository))
@ -769,10 +841,10 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
}
}
if (prune_worktrees_expire) {
if (cfg.prune_worktrees_expire) {
struct child_process prune_worktrees_cmd = CHILD_PROCESS_INIT;
strvec_push(&prune_worktrees, prune_worktrees_expire);
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))
@ -796,13 +868,15 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
!quiet && !daemonized ? COMMIT_GRAPH_WRITE_PROGRESS : 0,
NULL);
if (opts.auto_flag && too_many_loose_objects())
if (opts.auto_flag && too_many_loose_objects(&cfg))
warning(_("There are too many unreachable loose objects; "
"run 'git prune' to remove them."));
if (!daemonized)
unlink(git_path("gc.log"));
out:
gc_config_release(&cfg);
return 0;
}
@ -893,7 +967,7 @@ static int dfs_on_ref(const char *refname UNUSED,
return result;
}
static int should_write_commit_graph(void)
static int should_write_commit_graph(struct gc_config *cfg)
{
int result;
struct cg_auto_data data;
@ -930,7 +1004,8 @@ static int run_write_commit_graph(struct maintenance_run_opts *opts)
return !!run_command(&child);
}
static int maintenance_task_commit_graph(struct maintenance_run_opts *opts)
static int maintenance_task_commit_graph(struct maintenance_run_opts *opts,
struct gc_config *cfg)
{
prepare_repo_settings(the_repository);
if (!the_repository->settings.core_commit_graph)
@ -964,7 +1039,8 @@ static int fetch_remote(struct remote *remote, void *cbdata)
return !!run_command(&child);
}
static int maintenance_task_prefetch(struct maintenance_run_opts *opts)
static int maintenance_task_prefetch(struct maintenance_run_opts *opts,
struct gc_config *cfg)
{
if (for_each_remote(fetch_remote, opts)) {
error(_("failed to prefetch remotes"));
@ -974,7 +1050,8 @@ static int maintenance_task_prefetch(struct maintenance_run_opts *opts)
return 0;
}
static int maintenance_task_gc(struct maintenance_run_opts *opts)
static int maintenance_task_gc(struct maintenance_run_opts *opts,
struct gc_config *cfg)
{
struct child_process child = CHILD_PROCESS_INIT;
@ -987,6 +1064,7 @@ static int maintenance_task_gc(struct maintenance_run_opts *opts)
strvec_push(&child.args, "--quiet");
else
strvec_push(&child.args, "--no-quiet");
strvec_push(&child.args, "--no-detach");
return run_command(&child);
}
@ -1022,7 +1100,7 @@ static int loose_object_count(const struct object_id *oid UNUSED,
return 0;
}
static int loose_object_auto_condition(void)
static int loose_object_auto_condition(struct gc_config *cfg)
{
int count = 0;
@ -1107,12 +1185,13 @@ static int pack_loose(struct maintenance_run_opts *opts)
return result;
}
static int maintenance_task_loose_objects(struct maintenance_run_opts *opts)
static int maintenance_task_loose_objects(struct maintenance_run_opts *opts,
struct gc_config *cfg)
{
return prune_packed(opts) || pack_loose(opts);
}
static int incremental_repack_auto_condition(void)
static int incremental_repack_auto_condition(struct gc_config *cfg)
{
struct packed_git *p;
int incremental_repack_auto_limit = 10;
@ -1231,7 +1310,8 @@ static int multi_pack_index_repack(struct maintenance_run_opts *opts)
return 0;
}
static int maintenance_task_incremental_repack(struct maintenance_run_opts *opts)
static int maintenance_task_incremental_repack(struct maintenance_run_opts *opts,
struct gc_config *cfg)
{
prepare_repo_settings(the_repository);
if (!the_repository->settings.core_multi_pack_index) {
@ -1248,14 +1328,15 @@ static int maintenance_task_incremental_repack(struct maintenance_run_opts *opts
return 0;
}
typedef int maintenance_task_fn(struct maintenance_run_opts *opts);
typedef int maintenance_task_fn(struct maintenance_run_opts *opts,
struct gc_config *cfg);
/*
* An auto condition function returns 1 if the task should run
* and 0 if the task should NOT run. See needs_to_gc() for an
* example.
*/
typedef int maintenance_auto_fn(void);
typedef int maintenance_auto_fn(struct gc_config *cfg);
struct maintenance_task {
const char *name;
@ -1322,7 +1403,8 @@ static int compare_tasks_by_selection(const void *a_, const void *b_)
return b->selected_order - a->selected_order;
}
static int maintenance_run_tasks(struct maintenance_run_opts *opts)
static int maintenance_run_tasks(struct maintenance_run_opts *opts,
struct gc_config *cfg)
{
int i, found_selected = 0;
int result = 0;
@ -1346,6 +1428,10 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)
}
free(lock_path);
/* Failure to daemonize is ok, we'll continue in foreground. */
if (opts->detach > 0)
daemonize();
for (i = 0; !found_selected && i < TASK__COUNT; i++)
found_selected = tasks[i].selected_order >= 0;
@ -1361,14 +1447,14 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)
if (opts->auto_flag &&
(!tasks[i].auto_condition ||
!tasks[i].auto_condition()))
!tasks[i].auto_condition(cfg)))
continue;
if (opts->schedule && tasks[i].schedule < opts->schedule)
continue;
trace2_region_enter("maintenance", tasks[i].name, r);
if (tasks[i].fn(opts)) {
if (tasks[i].fn(opts, cfg)) {
error(_("task '%s' failed"), tasks[i].name);
result = 1;
}
@ -1405,7 +1491,6 @@ static void initialize_task_config(int schedule)
{
int i;
struct strbuf config_name = STRBUF_INIT;
gc_config();
if (schedule)
initialize_maintenance_strategy();
@ -1468,10 +1553,13 @@ static int task_option_parse(const struct option *opt UNUSED,
static int maintenance_run(int argc, const char **argv, const char *prefix)
{
int i;
struct maintenance_run_opts opts;
struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT;
struct gc_config cfg = GC_CONFIG_INIT;
struct option builtin_maintenance_run_options[] = {
OPT_BOOL(0, "auto", &opts.auto_flag,
N_("run tasks based on the state of the repository")),
OPT_BOOL(0, "detach", &opts.detach,
N_("perform maintenance in the background")),
OPT_CALLBACK(0, "schedule", &opts.schedule, N_("frequency"),
N_("run tasks based on frequency"),
maintenance_opt_schedule),
@ -1482,7 +1570,7 @@ static int maintenance_run(int argc, const char **argv, const char *prefix)
PARSE_OPT_NONEG, task_option_parse),
OPT_END()
};
memset(&opts, 0, sizeof(opts));
int ret;
opts.quiet = !isatty(2);
@ -1497,12 +1585,16 @@ static int maintenance_run(int argc, const char **argv, const char *prefix)
if (opts.auto_flag && opts.schedule)
die(_("use at most one of --auto and --schedule=<frequency>"));
gc_config(&cfg);
initialize_task_config(opts.schedule);
if (argc != 0)
usage_with_options(builtin_maintenance_run_usage,
builtin_maintenance_run_options);
return maintenance_run_tasks(&opts);
ret = maintenance_run_tasks(&opts, &cfg);
gc_config_release(&cfg);
return ret;
}
static char *get_maintpath(void)

View File

@ -2694,9 +2694,10 @@ void git_protected_config(config_fn_t fn, void *data)
configset_iter(&protected_config, fn, data);
}
int repo_config_get_expiry(struct repository *r, const char *key, const char **output)
int repo_config_get_expiry(struct repository *r, const char *key, char **output)
{
int ret = repo_config_get_string(r, key, (char **)output);
int ret = repo_config_get_string(r, key, output);
if (ret)
return ret;
if (strcmp(*output, "now")) {

View File

@ -672,7 +672,7 @@ int repo_config_get_split_index(struct repository *r);
int repo_config_get_max_percent_split_change(struct repository *r);
/* This dies if the configured or default date is in the future */
int repo_config_get_expiry(struct repository *r, const char *key, const char **output);
int repo_config_get_expiry(struct repository *r, const char *key, char **output);
/* parse either "this many days" integer, or "5.days.ago" approxidate */
int repo_config_get_expiry_in_days(struct repository *r, const char *key,

View File

@ -3195,18 +3195,24 @@ static int write_split_index(struct index_state *istate,
return ret;
}
static const char *shared_index_expire = "2.weeks.ago";
static unsigned long get_shared_index_expire_date(void)
{
static unsigned long shared_index_expire_date;
static int shared_index_expire_date_prepared;
if (!shared_index_expire_date_prepared) {
const char *shared_index_expire = "2.weeks.ago";
char *value = NULL;
repo_config_get_expiry(the_repository, "splitindex.sharedindexexpire",
&shared_index_expire);
&value);
if (value)
shared_index_expire = value;
shared_index_expire_date = approxidate(shared_index_expire);
shared_index_expire_date_prepared = 1;
free(value);
}
return shared_index_expire_date;

View File

@ -1808,16 +1808,26 @@ void run_processes_parallel(const struct run_process_parallel_opts *opts)
int prepare_auto_maintenance(int quiet, struct child_process *maint)
{
int enabled;
int enabled, auto_detach;
if (!git_config_get_bool("maintenance.auto", &enabled) &&
!enabled)
return 0;
/*
* When `maintenance.autoDetach` isn't set, then we fall back to
* honoring `gc.autoDetach`. This is somewhat weird, but required to
* retain behaviour from when we used to run git-gc(1) here.
*/
if (git_config_get_bool("maintenance.autodetach", &auto_detach) &&
git_config_get_bool("gc.autodetach", &auto_detach))
auto_detach = 1;
maint->git_cmd = 1;
maint->close_object_store = 1;
strvec_pushl(&maint->args, "maintenance", "run", "--auto", NULL);
strvec_push(&maint->args, quiet ? "--quiet" : "--no-quiet");
strvec_push(&maint->args, auto_detach ? "--detach" : "--no-detach");
return 1;
}

View File

@ -7,6 +7,7 @@ test_description='prune'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
day=$((60*60*24))

View File

@ -229,7 +229,7 @@ test_expect_success 'fetch --refetch triggers repacking' '
GIT_TRACE2_EVENT="$PWD/trace1.event" \
git -C pc1 fetch --refetch origin &&
test_subcommand git maintenance run --auto --no-quiet <trace1.event &&
test_subcommand git maintenance run --auto --no-quiet --detach <trace1.event &&
grep \"param\":\"gc.autopacklimit\",\"value\":\"1\" trace1.event &&
grep \"param\":\"maintenance.incremental-repack.auto\",\"value\":\"-1\" trace1.event &&
@ -238,7 +238,7 @@ test_expect_success 'fetch --refetch triggers repacking' '
-c gc.autoPackLimit=0 \
-c maintenance.incremental-repack.auto=1234 \
-C pc1 fetch --refetch origin &&
test_subcommand git maintenance run --auto --no-quiet <trace2.event &&
test_subcommand git maintenance run --auto --no-quiet --detach <trace2.event &&
grep \"param\":\"gc.autopacklimit\",\"value\":\"0\" trace2.event &&
grep \"param\":\"maintenance.incremental-repack.auto\",\"value\":\"-1\" trace2.event &&
@ -247,7 +247,7 @@ test_expect_success 'fetch --refetch triggers repacking' '
-c gc.autoPackLimit=1234 \
-c maintenance.incremental-repack.auto=0 \
-C pc1 fetch --refetch origin &&
test_subcommand git maintenance run --auto --no-quiet <trace3.event &&
test_subcommand git maintenance run --auto --no-quiet --detach <trace3.event &&
grep \"param\":\"gc.autopacklimit\",\"value\":\"1\" trace3.event &&
grep \"param\":\"maintenance.incremental-repack.auto\",\"value\":\"0\" trace3.event
'

View File

@ -338,14 +338,14 @@ test_expect_success 'gc.maxCruftSize sets appropriate repack options' '
test_subcommand $cruft_max_size_opts --max-cruft-size=3145728 <trace2.txt
'
run_and_wait_for_auto_gc () {
run_and_wait_for_gc () {
# We read stdout from gc for the side effect of waiting until the
# background gc process exits, closing its fd 9. Furthermore, the
# variable assignment from a command substitution preserves the
# exit status of the main gc process.
# Note: this fd trickery doesn't work on Windows, but there is no
# need to, because on Win the auto gc always runs in the foreground.
doesnt_matter=$(git gc --auto 9>&1)
doesnt_matter=$(git gc "$@" 9>&1)
}
test_expect_success 'background auto gc does not run if gc.log is present and recent but does if it is old' '
@ -361,7 +361,7 @@ test_expect_success 'background auto gc does not run if gc.log is present and re
test-tool chmtime =-345600 .git/gc.log &&
git gc --auto &&
test_config gc.logexpiry 2.days &&
run_and_wait_for_auto_gc &&
run_and_wait_for_gc --auto &&
ls .git/objects/pack/pack-*.pack >packs &&
test_line_count = 1 packs
'
@ -391,11 +391,48 @@ test_expect_success 'background auto gc respects lock for all operations' '
printf "%d %s" "$shell_pid" "$hostname" >.git/gc.pid &&
# our gc should exit zero without doing anything
run_and_wait_for_auto_gc &&
run_and_wait_for_gc --auto &&
(ls -1 .git/refs/heads .git/reftable >actual || true) &&
test_cmp expect actual
'
test_expect_success '--detach overrides gc.autoDetach=false' '
test_when_finished "rm -rf repo" &&
git init repo &&
(
cd repo &&
# Prepare the repository such that git-gc(1) ends up repacking.
test_commit "$(test_oid blob17_1)" &&
test_commit "$(test_oid blob17_2)" &&
git config gc.autodetach false &&
git config gc.auto 2 &&
# Note that we cannot use `test_cmp` here to compare stderr
# because it may contain output from `set -x`.
run_and_wait_for_gc --auto --detach 2>actual &&
test_grep "Auto packing the repository in background for optimum performance." actual
)
'
test_expect_success '--no-detach overrides gc.autoDetach=true' '
test_when_finished "rm -rf repo" &&
git init repo &&
(
cd repo &&
# Prepare the repository such that git-gc(1) ends up repacking.
test_commit "$(test_oid blob17_1)" &&
test_commit "$(test_oid blob17_2)" &&
git config gc.autodetach true &&
git config gc.auto 2 &&
GIT_PROGRESS_DELAY=0 git gc --auto --no-detach 2>output &&
test_grep "Auto packing the repository for optimum performance." output &&
test_grep "Collecting referenced commits: 2, done." output
)
'
# DO NOT leave a detached auto gc process running near the end of the
# test script: it can run long enough in the background to racily
# interfere with the cleanup in 'test_done'.

View File

@ -49,22 +49,47 @@ test_expect_success 'run [--auto|--quiet]' '
git maintenance run --auto 2>/dev/null &&
GIT_TRACE2_EVENT="$(pwd)/run-no-quiet.txt" \
git maintenance run --no-quiet 2>/dev/null &&
test_subcommand git gc --quiet <run-no-auto.txt &&
test_subcommand ! git gc --auto --quiet <run-auto.txt &&
test_subcommand git gc --no-quiet <run-no-quiet.txt
test_subcommand git gc --quiet --no-detach <run-no-auto.txt &&
test_subcommand ! git gc --auto --quiet --no-detach <run-auto.txt &&
test_subcommand git gc --no-quiet --no-detach <run-no-quiet.txt
'
test_expect_success 'maintenance.auto config option' '
GIT_TRACE2_EVENT="$(pwd)/default" git commit --quiet --allow-empty -m 1 &&
test_subcommand git maintenance run --auto --quiet <default &&
test_subcommand git maintenance run --auto --quiet --detach <default &&
GIT_TRACE2_EVENT="$(pwd)/true" \
git -c maintenance.auto=true \
commit --quiet --allow-empty -m 2 &&
test_subcommand git maintenance run --auto --quiet <true &&
test_subcommand git maintenance run --auto --quiet --detach <true &&
GIT_TRACE2_EVENT="$(pwd)/false" \
git -c maintenance.auto=false \
commit --quiet --allow-empty -m 3 &&
test_subcommand ! git maintenance run --auto --quiet <false
test_subcommand ! git maintenance run --auto --quiet --detach <false
'
for cfg in maintenance.autoDetach gc.autoDetach
do
test_expect_success "$cfg=true config option" '
test_when_finished "rm -f trace" &&
test_config $cfg true &&
GIT_TRACE2_EVENT="$(pwd)/trace" git commit --quiet --allow-empty -m 1 &&
test_subcommand git maintenance run --auto --quiet --detach <trace
'
test_expect_success "$cfg=false config option" '
test_when_finished "rm -f trace" &&
test_config $cfg false &&
GIT_TRACE2_EVENT="$(pwd)/trace" git commit --quiet --allow-empty -m 1 &&
test_subcommand git maintenance run --auto --quiet --no-detach <trace
'
done
test_expect_success "maintenance.autoDetach overrides gc.autoDetach" '
test_when_finished "rm -f trace" &&
test_config maintenance.autoDetach false &&
test_config gc.autoDetach true &&
GIT_TRACE2_EVENT="$(pwd)/trace" git commit --quiet --allow-empty -m 1 &&
test_subcommand git maintenance run --auto --quiet --no-detach <trace
'
test_expect_success 'register uses XDG_CONFIG_HOME config if it exists' '
@ -129,9 +154,9 @@ test_expect_success 'run --task=<task>' '
git maintenance run --task=commit-graph 2>/dev/null &&
GIT_TRACE2_EVENT="$(pwd)/run-both.txt" \
git maintenance run --task=commit-graph --task=gc 2>/dev/null &&
test_subcommand ! git gc --quiet <run-commit-graph.txt &&
test_subcommand git gc --quiet <run-gc.txt &&
test_subcommand git gc --quiet <run-both.txt &&
test_subcommand ! git gc --quiet --no-detach <run-commit-graph.txt &&
test_subcommand git gc --quiet --no-detach <run-gc.txt &&
test_subcommand git gc --quiet --no-detach <run-both.txt &&
test_subcommand git commit-graph write --split --reachable --no-progress <run-commit-graph.txt &&
test_subcommand ! git commit-graph write --split --reachable --no-progress <run-gc.txt &&
test_subcommand git commit-graph write --split --reachable --no-progress <run-both.txt
@ -908,4 +933,43 @@ test_expect_success 'failed schedule prevents config change' '
done
'
test_expect_success '--no-detach causes maintenance to not run in background' '
test_when_finished "rm -rf repo" &&
git init repo &&
(
cd repo &&
# Prepare the repository such that git-maintenance(1) ends up
# outputting something.
test_commit something &&
git config set maintenance.gc.enabled false &&
git config set maintenance.loose-objects.enabled true &&
git config set maintenance.loose-objects.auto 1 &&
git config set maintenance.incremental-repack.enabled true &&
# We have no better way to check whether or not the task ran in
# the background than to verify whether it output anything. The
# next testcase checks the reverse, making this somewhat safer.
git maintenance run --no-detach >out 2>&1 &&
test_line_count = 1 out
)
'
test_expect_success '--detach causes maintenance to run in background' '
test_when_finished "rm -rf repo" &&
git init repo &&
(
cd repo &&
test_commit something &&
git config set maintenance.gc.enabled false &&
git config set maintenance.loose-objects.enabled true &&
git config set maintenance.loose-objects.auto 1 &&
git config set maintenance.incremental-repack.enabled true &&
git maintenance run --detach >out 2>&1 &&
test_must_be_empty out
)
'
test_done