aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/config/gc.txt3
-rw-r--r--Documentation/config/maintenance.txt11
-rw-r--r--Documentation/git-gc.txt5
-rw-r--r--builtin/gc.c384
-rw-r--r--config.c5
-rw-r--r--config.h2
-rw-r--r--read-cache.c12
-rw-r--r--run-command.c12
-rwxr-xr-xt/t5304-prune.sh1
-rwxr-xr-xt/t5616-partial-clone.sh6
-rwxr-xr-xt/t6500-gc.sh45
-rwxr-xr-xt/t7900-maintenance.sh82
12 files changed, 397 insertions, 171 deletions
diff --git a/Documentation/config/gc.txt b/Documentation/config/gc.txt
index 664a3c2874..1d4f9470ea 100644
--- a/Documentation/config/gc.txt
+++ b/Documentation/config/gc.txt
@@ -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
diff --git a/Documentation/config/maintenance.txt b/Documentation/config/maintenance.txt
index 69a4f05153..72a9d6cf81 100644
--- a/Documentation/config/maintenance.txt
+++ b/Documentation/config/maintenance.txt
@@ -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
diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt
index b5561c458a..370e22faae 100644
--- a/Documentation/git-gc.txt
+++ b/Documentation/git-gc.txt
@@ -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
diff --git a/builtin/gc.c b/builtin/gc.c
index 45f14fb09b..bd0a1b92ba 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -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", &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);
+
+ if (!repo_config_get_expiry(the_repository, "gc.pruneexpire", &owned)) {
+ free(cfg->prune_expire);
+ cfg->prune_expire = owned;
+ }
- 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);
+ 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", &big_pack_threshold);
- git_config_get_ulong("pack.deltacachesize", &max_delta_cache_size);
+ git_config_get_ulong("gc.bigpackthreshold", &cfg->big_pack_threshold);
+ git_config_get_ulong("pack.deltacachesize", &cfg->max_delta_cache_size);
- git_config_get_string("gc.repackfilter", &repack_filter);
- git_config_get_string("gc.repackfilterto", &repack_filter_to);
+ 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,84 +704,103 @@ 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 (parse_expiry_date(cfg.gc_log_expire, &gc_log_expire_time))
+ die(_("failed to parse gc.logExpiry value %s"), cfg.gc_log_expire);
- if (pack_refs < 0)
- pack_refs = !is_bare_repository();
+ 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();
-
- if (ret == 1)
- /* Last gc --auto failed. Skip this one. */
- return 0;
- else if (ret)
- /* an I/O error occurred, already reported */
- return ret;
-
- if (lock_repo_for_gc(force, &pid))
- return 0;
- gc_before_repack(&opts); /* dies on failure */
- delete_tempfile(&pidfile);
-
- /*
- * failure to daemonize is ok, we'll continue
- * in foreground
- */
- 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);
+ } else if (cfg.big_pack_threshold) {
+ find_base_packs(&keep_pack, cfg.big_pack_threshold);
}
- add_repack_all_option(&keep_pack);
+ 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. */
+ ret = 0;
+ goto out;
+
+ } 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);
+
+ /*
+ * failure to daemonize is ok, we'll continue
+ * in foreground
+ */
+ daemonized = !daemonize();
+ }
+
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)
diff --git a/config.c b/config.c
index 9850897524..56b5862e59 100644
--- a/config.c
+++ b/config.c
@@ -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")) {
diff --git a/config.h b/config.h
index 4d319a4193..d0497157c5 100644
--- a/config.h
+++ b/config.h
@@ -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,
diff --git a/read-cache.c b/read-cache.c
index 5464398162..4e67dc182e 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -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;
diff --git a/run-command.c b/run-command.c
index 45ba544932..94f2f3079f 100644
--- a/run-command.c
+++ b/run-command.c
@@ -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;
}
diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh
index 1f1f664871..e641df0116 100755
--- a/t/t5304-prune.sh
+++ b/t/t5304-prune.sh
@@ -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))
diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh
index 2da7291e37..8415884754 100755
--- a/t/t5616-partial-clone.sh
+++ b/t/t5616-partial-clone.sh
@@ -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
'
diff --git a/t/t6500-gc.sh b/t/t6500-gc.sh
index 1b5909d1b7..5378455968 100755
--- a/t/t6500-gc.sh
+++ b/t/t6500-gc.sh
@@ -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'.
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index 8595489ceb..06ab43cfb5 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -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