aboutsummaryrefslogtreecommitdiffstats
path: root/builtin/gc.c
diff options
context:
space:
mode:
Diffstat (limited to 'builtin/gc.c')
-rw-r--r--builtin/gc.c329
1 files changed, 228 insertions, 101 deletions
diff --git a/builtin/gc.c b/builtin/gc.c
index b335cffa33..02455fdcd7 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -42,6 +42,7 @@ static const char * const builtin_gc_usage[] = {
static int pack_refs = 1;
static int prune_reflogs = 1;
+static int cruft_packs = -1;
static int aggressive_depth = 50;
static int aggressive_window = 250;
static int gc_auto_threshold = 6700;
@@ -152,6 +153,7 @@ static void gc_config(void)
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_expiry("gc.pruneexpire", &prune_expire);
git_config_get_expiry("gc.worktreepruneexpire", &prune_worktrees_expire);
git_config_get_expiry("gc.logexpiry", &gc_log_expire);
@@ -165,10 +167,11 @@ static void gc_config(void)
struct maintenance_run_opts;
static int maintenance_task_pack_refs(MAYBE_UNUSED struct maintenance_run_opts *opts)
{
- struct strvec pack_refs_cmd = STRVEC_INIT;
- strvec_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL);
+ struct child_process cmd = CHILD_PROCESS_INIT;
- return run_command_v_opt(pack_refs_cmd.v, RUN_GIT_CMD);
+ cmd.git_cmd = 1;
+ strvec_pushl(&cmd.args, "pack-refs", "--all", "--prune", NULL);
+ return run_command(&cmd);
}
static int too_many_loose_objects(void)
@@ -321,7 +324,7 @@ static uint64_t estimate_repack_memory(struct packed_git *pack)
return os_cache + heap;
}
-static int keep_one_pack(struct string_list_item *item, void *data)
+static int keep_one_pack(struct string_list_item *item, void *data UNUSED)
{
strvec_pushf(&repack, "--keep-pack=%s", basename(item->string));
return 0;
@@ -331,7 +334,11 @@ static void add_repack_all_option(struct string_list *keep_pack)
{
if (prune_expire && !strcmp(prune_expire, "now"))
strvec_push(&repack, "-a");
- else {
+ else if (cruft_packs) {
+ strvec_push(&repack, "--cruft");
+ if (prune_expire)
+ strvec_pushf(&repack, "--cruft-expiration=%s", prune_expire);
+ } else {
strvec_push(&repack, "-A");
if (prune_expire)
strvec_pushf(&repack, "--unpack-unreachable=%s", prune_expire);
@@ -446,7 +453,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
fscanf(fp, scan_fmt, &pid, locking_host) == 2 &&
/* be gentle to concurrent "gc" on remote hosts */
(strcmp(locking_host, my_host) || !kill(pid, 0) || errno == EPERM);
- if (fp != NULL)
+ if (fp)
fclose(fp);
if (should_exit) {
if (fd >= 0)
@@ -530,8 +537,14 @@ static void gc_before_repack(void)
if (pack_refs && maintenance_task_pack_refs(NULL))
die(FAILED_RUN, "pack-refs");
- if (prune_reflogs && run_command_v_opt(reflog.v, RUN_GIT_CMD))
- die(FAILED_RUN, reflog.v[0]);
+ if (prune_reflogs) {
+ struct child_process cmd = CHILD_PROCESS_INIT;
+
+ cmd.git_cmd = 1;
+ strvec_pushv(&cmd.args, reflog.v);
+ if (run_command(&cmd))
+ die(FAILED_RUN, reflog.v[0]);
+ }
}
int cmd_gc(int argc, const char **argv, const char *prefix)
@@ -545,12 +558,14 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
int daemonized = 0;
int keep_largest_pack = -1;
timestamp_t dummy;
+ struct child_process rerere_cmd = CHILD_PROCESS_INIT;
struct option builtin_gc_options[] = {
OPT__QUIET(&quiet, N_("suppress progress reporting")),
{ OPTION_STRING, 0, "prune", &prune_expire, 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_BOOL(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")),
OPT_BOOL_F(0, "auto", &auto_gc, N_("enable auto-gc mode"),
PARSE_OPT_NOCOMPLETE),
@@ -574,7 +589,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
/* 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);
+ die(_("failed to parse gc.logExpiry value %s"), gc_log_expire);
if (pack_refs < 0)
pack_refs = !is_bare_repository();
@@ -587,6 +602,10 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
if (prune_expire && parse_expiry_date(prune_expire, &dummy))
die(_("failed to parse prune expiry value %s"), prune_expire);
+ prepare_repo_settings(the_repository);
+ if (cruft_packs < 0)
+ cruft_packs = the_repository->settings.gc_cruft_packs;
+
if (aggressive) {
strvec_push(&repack, "-f");
if (aggressive_depth > 0)
@@ -665,29 +684,44 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
gc_before_repack();
if (!repository_format_precious_objects) {
- if (run_command_v_opt(repack.v,
- RUN_GIT_CMD | RUN_CLOSE_OBJECT_STORE))
+ struct child_process repack_cmd = CHILD_PROCESS_INIT;
+
+ repack_cmd.git_cmd = 1;
+ repack_cmd.close_object_store = 1;
+ strvec_pushv(&repack_cmd.args, repack.v);
+ if (run_command(&repack_cmd))
die(FAILED_RUN, repack.v[0]);
if (prune_expire) {
+ struct child_process prune_cmd = CHILD_PROCESS_INIT;
+
+ /* run `git prune` even if using cruft packs */
strvec_push(&prune, prune_expire);
if (quiet)
strvec_push(&prune, "--no-progress");
if (has_promisor_remote())
strvec_push(&prune,
"--exclude-promisor-objects");
- if (run_command_v_opt(prune.v, RUN_GIT_CMD))
+ prune_cmd.git_cmd = 1;
+ strvec_pushv(&prune_cmd.args, prune.v);
+ if (run_command(&prune_cmd))
die(FAILED_RUN, prune.v[0]);
}
}
if (prune_worktrees_expire) {
+ struct child_process prune_worktrees_cmd = CHILD_PROCESS_INIT;
+
strvec_push(&prune_worktrees, prune_worktrees_expire);
- if (run_command_v_opt(prune_worktrees.v, RUN_GIT_CMD))
+ 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]);
}
- if (run_command_v_opt(rerere.v, RUN_GIT_CMD))
+ rerere_cmd.git_cmd = 1;
+ strvec_pushv(&rerere_cmd.args, rerere.v);
+ if (run_command(&rerere_cmd))
die(FAILED_RUN, rerere.v[0]);
report_garbage = report_pack_garbage;
@@ -697,7 +731,6 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
clean_pack_garbage();
}
- prepare_repo_settings(the_repository);
if (the_repository->settings.gc_write_commit_graph == 1)
write_commit_graph_reachable(the_repository->objects->odb,
!quiet && !daemonized ? COMMIT_GRAPH_WRITE_PROGRESS : 0,
@@ -768,8 +801,9 @@ struct cg_auto_data {
int limit;
};
-static int dfs_on_ref(const char *refname,
- const struct object_id *oid, int flags,
+static int dfs_on_ref(const char *refname UNUSED,
+ const struct object_id *oid,
+ int flags UNUSED,
void *cb_data)
{
struct cg_auto_data *data = (struct cg_auto_data *)cb_data;
@@ -896,12 +930,6 @@ static int fetch_remote(struct remote *remote, void *cbdata)
static int maintenance_task_prefetch(struct maintenance_run_opts *opts)
{
- git_config_set_multivar_gently("log.excludedecoration",
- "refs/prefetch/",
- "refs/prefetch/",
- CONFIG_FLAGS_FIXED_VALUE |
- CONFIG_FLAGS_MULTI_REPLACE);
-
if (for_each_remote(fetch_remote, opts)) {
error(_("failed to prefetch remotes"));
return 1;
@@ -1451,13 +1479,30 @@ static char *get_maintpath(void)
return strbuf_detach(&sb, NULL);
}
-static int maintenance_register(void)
+static char const * const builtin_maintenance_register_usage[] = {
+ "git maintenance register [--config-file <path>]",
+ NULL
+};
+
+static int maintenance_register(int argc, const char **argv, const char *prefix)
{
- int rc;
+ char *config_file = NULL;
+ struct option options[] = {
+ OPT_STRING(0, "config-file", &config_file, N_("file"), N_("use given config file")),
+ OPT_END(),
+ };
+ int found = 0;
+ const char *key = "maintenance.repo";
char *config_value;
- struct child_process config_set = CHILD_PROCESS_INIT;
- struct child_process config_get = CHILD_PROCESS_INIT;
char *maintpath = get_maintpath();
+ struct string_list_item *item;
+ const struct string_list *list;
+
+ argc = parse_options(argc, argv, prefix, options,
+ builtin_maintenance_register_usage, 0);
+ if (argc)
+ usage_with_options(builtin_maintenance_register_usage,
+ options);
/* Disable foreground maintenance */
git_config_set("maintenance.auto", "false");
@@ -1468,46 +1513,112 @@ static int maintenance_register(void)
else
git_config_set("maintenance.strategy", "incremental");
- config_get.git_cmd = 1;
- strvec_pushl(&config_get.args, "config", "--global", "--get",
- "--fixed-value", "maintenance.repo", maintpath, NULL);
- config_get.out = -1;
-
- if (start_command(&config_get)) {
- rc = error(_("failed to run 'git config'"));
- goto done;
- }
-
- /* We already have this value in our config! */
- if (!finish_command(&config_get)) {
- rc = 0;
- goto done;
+ list = git_config_get_value_multi(key);
+ if (list) {
+ for_each_string_list_item(item, list) {
+ if (!strcmp(maintpath, item->string)) {
+ found = 1;
+ break;
+ }
+ }
}
- config_set.git_cmd = 1;
- strvec_pushl(&config_set.args, "config", "--add", "--global", "maintenance.repo",
- maintpath, NULL);
+ if (!found) {
+ int rc;
+ char *user_config = NULL, *xdg_config = NULL;
- rc = run_command(&config_set);
+ if (!config_file) {
+ git_global_config(&user_config, &xdg_config);
+ config_file = user_config;
+ if (!user_config)
+ die(_("$HOME not set"));
+ }
+ rc = git_config_set_multivar_in_file_gently(
+ config_file, "maintenance.repo", maintpath,
+ CONFIG_REGEX_NONE, 0);
+ free(user_config);
+ free(xdg_config);
+
+ if (rc)
+ die(_("unable to add '%s' value of '%s'"),
+ key, maintpath);
+ }
-done:
free(maintpath);
- return rc;
+ return 0;
}
-static int maintenance_unregister(void)
+static char const * const builtin_maintenance_unregister_usage[] = {
+ "git maintenance unregister [--config-file <path>] [--force]",
+ NULL
+};
+
+static int maintenance_unregister(int argc, const char **argv, const char *prefix)
{
- int rc;
- struct child_process config_unset = CHILD_PROCESS_INIT;
+ int force = 0;
+ char *config_file = NULL;
+ struct option options[] = {
+ OPT_STRING(0, "config-file", &config_file, N_("file"), N_("use given config file")),
+ OPT__FORCE(&force,
+ N_("return success even if repository was not registered"),
+ PARSE_OPT_NOCOMPLETE),
+ OPT_END(),
+ };
+ const char *key = "maintenance.repo";
char *maintpath = get_maintpath();
+ int found = 0;
+ struct string_list_item *item;
+ const struct string_list *list;
+ struct config_set cs = { { 0 } };
- config_unset.git_cmd = 1;
- strvec_pushl(&config_unset.args, "config", "--global", "--unset",
- "--fixed-value", "maintenance.repo", maintpath, NULL);
+ argc = parse_options(argc, argv, prefix, options,
+ builtin_maintenance_unregister_usage, 0);
+ if (argc)
+ usage_with_options(builtin_maintenance_unregister_usage,
+ options);
- rc = run_command(&config_unset);
+ if (config_file) {
+ git_configset_init(&cs);
+ git_configset_add_file(&cs, config_file);
+ list = git_configset_get_value_multi(&cs, key);
+ } else {
+ list = git_config_get_value_multi(key);
+ }
+ if (list) {
+ for_each_string_list_item(item, list) {
+ if (!strcmp(maintpath, item->string)) {
+ found = 1;
+ break;
+ }
+ }
+ }
+
+ if (found) {
+ int rc;
+ char *user_config = NULL, *xdg_config = NULL;
+ if (!config_file) {
+ git_global_config(&user_config, &xdg_config);
+ config_file = user_config;
+ if (!user_config)
+ die(_("$HOME not set"));
+ }
+ rc = git_config_set_multivar_in_file_gently(
+ config_file, key, NULL, maintpath,
+ CONFIG_FLAGS_MULTI_REPLACE | CONFIG_FLAGS_FIXED_VALUE);
+ free(user_config);
+ free(xdg_config);
+
+ if (rc &&
+ (!force || rc == CONFIG_NOTHING_SET))
+ die(_("unable to unset '%s' value of '%s'"),
+ key, maintpath);
+ } else if (!force) {
+ die(_("repository '%s' is not registered"), maintpath);
+ }
+
+ git_configset_clear(&cs);
free(maintpath);
- return rc;
+ return 0;
}
static const char *get_frequency(enum schedule_priority schedule)
@@ -1844,20 +1955,16 @@ static char *schtasks_task_name(const char *frequency)
static int schtasks_remove_task(enum schedule_priority schedule)
{
const char *cmd = "schtasks";
- int result;
- struct strvec args = STRVEC_INIT;
+ struct child_process child = CHILD_PROCESS_INIT;
const char *frequency = get_frequency(schedule);
char *name = schtasks_task_name(frequency);
get_schedule_cmd(&cmd, NULL);
- strvec_split(&args, cmd);
- strvec_pushl(&args, "/delete", "/tn", name, "/f", NULL);
-
- result = run_command_v_opt(args.v, 0);
-
- strvec_clear(&args);
+ strvec_split(&child.args, cmd);
+ strvec_pushl(&child.args, "/delete", "/tn", name, "/f", NULL);
free(name);
- return result;
+
+ return run_command(&child);
}
static int schtasks_remove_tasks(void)
@@ -2051,6 +2158,7 @@ static int crontab_update_schedule(int run_maintenance, int fd)
struct child_process crontab_edit = CHILD_PROCESS_INIT;
FILE *cron_list, *cron_in;
struct strbuf line = STRBUF_INIT;
+ struct tempfile *tmpedit = NULL;
get_schedule_cmd(&cmd, NULL);
strvec_split(&crontab_list.args, cmd);
@@ -2065,6 +2173,17 @@ static int crontab_update_schedule(int run_maintenance, int fd)
/* Ignore exit code, as an empty crontab will return error. */
finish_command(&crontab_list);
+ tmpedit = mks_tempfile_t(".git_cron_edit_tmpXXXXXX");
+ if (!tmpedit) {
+ result = error(_("failed to create crontab temporary file"));
+ goto out;
+ }
+ cron_in = fdopen_tempfile(tmpedit, "w");
+ if (!cron_in) {
+ result = error(_("failed to open temporary file"));
+ goto out;
+ }
+
/*
* Read from the .lock file, filtering out the old
* schedule while appending the new schedule.
@@ -2072,19 +2191,6 @@ static int crontab_update_schedule(int run_maintenance, int fd)
cron_list = fdopen(fd, "r");
rewind(cron_list);
- strvec_split(&crontab_edit.args, cmd);
- crontab_edit.in = -1;
- crontab_edit.git_cmd = 0;
-
- if (start_command(&crontab_edit))
- return error(_("failed to run 'crontab'; your system might not support 'cron'"));
-
- cron_in = fdopen(crontab_edit.in, "w");
- if (!cron_in) {
- result = error(_("failed to open stdin of 'crontab'"));
- goto done_editing;
- }
-
while (!strbuf_getline_lf(&line, cron_list)) {
if (!in_old_region && !strcmp(line.buf, BEGIN_LINE))
in_old_region = 1;
@@ -2118,14 +2224,22 @@ static int crontab_update_schedule(int run_maintenance, int fd)
}
fflush(cron_in);
- fclose(cron_in);
- close(crontab_edit.in);
-done_editing:
+ strvec_split(&crontab_edit.args, cmd);
+ strvec_push(&crontab_edit.args, get_tempfile_path(tmpedit));
+ crontab_edit.git_cmd = 0;
+
+ if (start_command(&crontab_edit)) {
+ result = error(_("failed to run 'crontab'; your system might not support 'cron'"));
+ goto out;
+ }
+
if (finish_command(&crontab_edit))
result = error(_("'crontab' died"));
else
fclose(cron_list);
+out:
+ delete_tempfile(&tmpedit);
return result;
}
@@ -2238,7 +2352,7 @@ static int systemd_timer_write_unit_templates(const char *exec_path)
goto error;
}
file = fopen_or_warn(filename, "w");
- if (file == NULL)
+ if (!file)
goto error;
unit = "# This file was created and is maintained by Git.\n"
@@ -2267,7 +2381,7 @@ static int systemd_timer_write_unit_templates(const char *exec_path)
filename = xdg_config_home_systemd("git-maintenance@.service");
file = fopen_or_warn(filename, "w");
- if (file == NULL)
+ if (!file)
goto error;
unit = "# This file was created and is maintained by Git.\n"
@@ -2482,6 +2596,7 @@ static int maintenance_start(int argc, const char **argv, const char *prefix)
PARSE_OPT_NONEG, maintenance_opt_scheduler),
OPT_END()
};
+ const char *register_args[] = { "register", NULL };
argc = parse_options(argc, argv, prefix, options,
builtin_maintenance_start_usage, 0);
@@ -2491,34 +2606,46 @@ static int maintenance_start(int argc, const char **argv, const char *prefix)
opts.scheduler = resolve_scheduler(opts.scheduler);
validate_scheduler(opts.scheduler);
- if (maintenance_register())
+ if (maintenance_register(ARRAY_SIZE(register_args)-1, register_args, NULL))
warning(_("failed to add repo to global config"));
return update_background_schedule(&opts, 1);
}
-static int maintenance_stop(void)
+static const char *const builtin_maintenance_stop_usage[] = {
+ "git maintenance stop",
+ NULL
+};
+
+static int maintenance_stop(int argc, const char **argv, const char *prefix)
{
+ struct option options[] = {
+ OPT_END()
+ };
+ argc = parse_options(argc, argv, prefix, options,
+ builtin_maintenance_stop_usage, 0);
+ if (argc)
+ usage_with_options(builtin_maintenance_stop_usage, options);
return update_background_schedule(NULL, 0);
}
-static const char builtin_maintenance_usage[] = N_("git maintenance <subcommand> [<options>]");
+static const char * const builtin_maintenance_usage[] = {
+ N_("git maintenance <subcommand> [<options>]"),
+ NULL,
+};
int cmd_maintenance(int argc, const char **argv, const char *prefix)
{
- if (argc < 2 ||
- (argc == 2 && !strcmp(argv[1], "-h")))
- usage(builtin_maintenance_usage);
-
- if (!strcmp(argv[1], "run"))
- return maintenance_run(argc - 1, argv + 1, prefix);
- if (!strcmp(argv[1], "start"))
- return maintenance_start(argc - 1, argv + 1, prefix);
- if (!strcmp(argv[1], "stop"))
- return maintenance_stop();
- if (!strcmp(argv[1], "register"))
- return maintenance_register();
- if (!strcmp(argv[1], "unregister"))
- return maintenance_unregister();
-
- die(_("invalid subcommand: %s"), argv[1]);
+ parse_opt_subcommand_fn *fn = NULL;
+ struct option builtin_maintenance_options[] = {
+ OPT_SUBCOMMAND("run", &fn, maintenance_run),
+ OPT_SUBCOMMAND("start", &fn, maintenance_start),
+ OPT_SUBCOMMAND("stop", &fn, maintenance_stop),
+ OPT_SUBCOMMAND("register", &fn, maintenance_register),
+ OPT_SUBCOMMAND("unregister", &fn, maintenance_unregister),
+ OPT_END(),
+ };
+
+ argc = parse_options(argc, argv, prefix, builtin_maintenance_options,
+ builtin_maintenance_usage, 0);
+ return fn(argc, argv, prefix);
}