Merge branch 'ps/maintenance-ref-lock'

"git maintenance" lacked the care "git gc" had to avoid holding
onto the repository lock for too long during packing refs, which
has been remedied.

* ps/maintenance-ref-lock:
  builtin/maintenance: fix locking race when handling "gc" task
  builtin/gc: avoid global state in `gc_before_repack()`
  usage: allow dying without writing an error message
  builtin/maintenance: fix locking race with refs and reflogs tasks
  builtin/maintenance: split into foreground and background tasks
  builtin/maintenance: fix typedef for function pointers
  builtin/maintenance: extract function to run tasks
  builtin/maintenance: stop modifying global array of tasks
  builtin/maintenance: mark "--task=" and "--schedule=" as incompatible
  builtin/maintenance: centralize configuration of explicit tasks
  builtin/gc: drop redundant local variable
  builtin/gc: use designated field initializers for maintenance tasks
maint
Junio C Hamano 2025-06-25 14:07:36 -07:00
commit 4c9a5d7729
7 changed files with 265 additions and 192 deletions

View File

@ -1000,7 +1000,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,


if (!patch_format) { if (!patch_format) {
fprintf_ln(stderr, _("Patch format detection failed.")); fprintf_ln(stderr, _("Patch format detection failed."));
exit(128); die(NULL);
} }


if (mkdir(state->dir, 0777) < 0 && errno != EEXIST) if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
@ -1178,7 +1178,7 @@ static void NORETURN die_user_resolve(const struct am_state *state)
strbuf_release(&sb); strbuf_release(&sb);
} }


exit(128); die(NULL);
} }


/** /**

View File

@ -838,7 +838,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
init_tree_desc(&trees[0], &tree->object.oid, init_tree_desc(&trees[0], &tree->object.oid,
tree->buffer, tree->size); tree->buffer, tree->size);
if (parse_tree(new_tree) < 0) if (parse_tree(new_tree) < 0)
exit(128); die(NULL);
tree = new_tree; tree = new_tree;
init_tree_desc(&trees[1], &tree->object.oid, init_tree_desc(&trees[1], &tree->object.oid,
tree->buffer, tree->size); tree->buffer, tree->size);
@ -913,7 +913,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
work, work,
old_tree); old_tree);
if (ret < 0) if (ret < 0)
exit(128); die(NULL);
ret = reset_tree(new_tree, ret = reset_tree(new_tree,
opts, 0, opts, 0,
writeout_error, new_branch_info); writeout_error, new_branch_info);

View File

@ -992,7 +992,7 @@ static int update_local_ref(struct ref *ref,
fast_forward = repo_in_merge_bases(the_repository, current, fast_forward = repo_in_merge_bases(the_repository, current,
updated); updated);
if (fast_forward < 0) if (fast_forward < 0)
exit(128); die(NULL);
forced_updates_ms += (getnanotime() - t_before) / 1000000; forced_updates_ms += (getnanotime() - t_before) / 1000000;
} else { } else {
fast_forward = 1; fast_forward = 1;

View File

@ -251,7 +251,24 @@ static enum schedule_priority parse_schedule(const char *value)
return SCHEDULE_NONE; return SCHEDULE_NONE;
} }


enum maintenance_task_label {
TASK_PREFETCH,
TASK_LOOSE_OBJECTS,
TASK_INCREMENTAL_REPACK,
TASK_GC,
TASK_COMMIT_GRAPH,
TASK_PACK_REFS,
TASK_REFLOG_EXPIRE,
TASK_WORKTREE_PRUNE,
TASK_RERERE_GC,

/* Leave as final value */
TASK__COUNT
};

struct maintenance_run_opts { struct maintenance_run_opts {
enum maintenance_task_label *tasks;
size_t tasks_nr, tasks_alloc;
int auto_flag; int auto_flag;
int detach; int detach;
int quiet; int quiet;
@ -261,6 +278,11 @@ struct maintenance_run_opts {
.detach = -1, \ .detach = -1, \
} }


static void maintenance_run_opts_release(struct maintenance_run_opts *opts)
{
free(opts->tasks);
}

static int pack_refs_condition(UNUSED struct gc_config *cfg) static int pack_refs_condition(UNUSED struct gc_config *cfg)
{ {
/* /*
@ -796,22 +818,14 @@ done:
return ret; return ret;
} }


static void gc_before_repack(struct maintenance_run_opts *opts, static int gc_foreground_tasks(struct maintenance_run_opts *opts,
struct gc_config *cfg) struct gc_config *cfg)
{ {
/*
* We may be called twice, as both the pre- and
* post-daemonized phases will call us, but running these
* commands more than once is pointless and wasteful.
*/
static int done = 0;
if (done++)
return;

if (cfg->pack_refs && maintenance_task_pack_refs(opts, cfg)) if (cfg->pack_refs && maintenance_task_pack_refs(opts, cfg))
die(FAILED_RUN, "pack-refs"); return error(FAILED_RUN, "pack-refs");
if (cfg->prune_reflogs && maintenance_task_reflog_expire(opts, cfg)) if (cfg->prune_reflogs && maintenance_task_reflog_expire(opts, cfg))
die(FAILED_RUN, "reflog"); return error(FAILED_RUN, "reflog");
return 0;
} }


int cmd_gc(int argc, int cmd_gc(int argc,
@ -820,12 +834,12 @@ int cmd_gc(int argc,
struct repository *repo UNUSED) struct repository *repo UNUSED)
{ {
int aggressive = 0; int aggressive = 0;
int quiet = 0;
int force = 0; int force = 0;
const char *name; const char *name;
pid_t pid; pid_t pid;
int daemonized = 0; int daemonized = 0;
int keep_largest_pack = -1; int keep_largest_pack = -1;
int skip_foreground_tasks = 0;
timestamp_t dummy; timestamp_t dummy;
struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT; struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT;
struct gc_config cfg = GC_CONFIG_INIT; struct gc_config cfg = GC_CONFIG_INIT;
@ -833,7 +847,7 @@ int cmd_gc(int argc,
const char *prune_expire_arg = prune_expire_sentinel; const char *prune_expire_arg = prune_expire_sentinel;
int ret; int ret;
struct option builtin_gc_options[] = { struct option builtin_gc_options[] = {
OPT__QUIET(&quiet, N_("suppress progress reporting")), OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
{ {
.type = OPTION_STRING, .type = OPTION_STRING,
.long_name = "prune", .long_name = "prune",
@ -858,6 +872,8 @@ int cmd_gc(int argc,
N_("repack all other packs except the largest pack")), N_("repack all other packs except the largest pack")),
OPT_STRING(0, "expire-to", &cfg.repack_expire_to, N_("dir"), OPT_STRING(0, "expire-to", &cfg.repack_expire_to, N_("dir"),
N_("pack prefix to store a pack containing pruned objects")), N_("pack prefix to store a pack containing pruned objects")),
OPT_HIDDEN_BOOL(0, "skip-foreground-tasks", &skip_foreground_tasks,
N_("skip maintenance tasks typically done in the foreground")),
OPT_END() OPT_END()
}; };


@ -893,7 +909,7 @@ int cmd_gc(int argc,
if (cfg.aggressive_window > 0) if (cfg.aggressive_window > 0)
strvec_pushf(&repack, "--window=%d", cfg.aggressive_window); strvec_pushf(&repack, "--window=%d", cfg.aggressive_window);
} }
if (quiet) if (opts.quiet)
strvec_push(&repack, "-q"); strvec_push(&repack, "-q");


if (opts.auto_flag) { if (opts.auto_flag) {
@ -908,7 +924,7 @@ int cmd_gc(int argc,
goto out; goto out;
} }


if (!quiet) { if (!opts.quiet) {
if (opts.detach > 0) if (opts.detach > 0)
fprintf(stderr, _("Auto packing the repository in background for optimum performance.\n")); fprintf(stderr, _("Auto packing the repository in background for optimum performance.\n"));
else else
@ -941,13 +957,16 @@ int cmd_gc(int argc,
goto out; goto out;
} }


if (lock_repo_for_gc(force, &pid)) { if (!skip_foreground_tasks) {
ret = 0; if (lock_repo_for_gc(force, &pid)) {
goto out; ret = 0;
} goto out;
}


gc_before_repack(&opts, &cfg); /* dies on failure */ if (gc_foreground_tasks(&opts, &cfg) < 0)
delete_tempfile(&pidfile); die(NULL);
delete_tempfile(&pidfile);
}


/* /*
* failure to daemonize is ok, we'll continue * failure to daemonize is ok, we'll continue
@ -976,7 +995,8 @@ int cmd_gc(int argc,
free(path); free(path);
} }


gc_before_repack(&opts, &cfg); if (opts.detach <= 0 && !skip_foreground_tasks)
gc_foreground_tasks(&opts, &cfg);


if (!repository_format_precious_objects) { if (!repository_format_precious_objects) {
struct child_process repack_cmd = CHILD_PROCESS_INIT; struct child_process repack_cmd = CHILD_PROCESS_INIT;
@ -993,7 +1013,7 @@ int cmd_gc(int argc,
strvec_pushl(&prune_cmd.args, "prune", "--expire", NULL); strvec_pushl(&prune_cmd.args, "prune", "--expire", NULL);
/* run `git prune` even if using cruft packs */ /* run `git prune` even if using cruft packs */
strvec_push(&prune_cmd.args, cfg.prune_expire); strvec_push(&prune_cmd.args, cfg.prune_expire);
if (quiet) if (opts.quiet)
strvec_push(&prune_cmd.args, "--no-progress"); strvec_push(&prune_cmd.args, "--no-progress");
if (repo_has_promisor_remote(the_repository)) if (repo_has_promisor_remote(the_repository))
strvec_push(&prune_cmd.args, strvec_push(&prune_cmd.args,
@ -1021,7 +1041,7 @@ int cmd_gc(int argc,


if (the_repository->settings.gc_write_commit_graph == 1) if (the_repository->settings.gc_write_commit_graph == 1)
write_commit_graph_reachable(the_repository->objects->odb, write_commit_graph_reachable(the_repository->objects->odb,
!quiet && !daemonized ? COMMIT_GRAPH_WRITE_PROGRESS : 0, !opts.quiet && !daemonized ? COMMIT_GRAPH_WRITE_PROGRESS : 0,
NULL); NULL);


if (opts.auto_flag && too_many_loose_objects(&cfg)) if (opts.auto_flag && too_many_loose_objects(&cfg))
@ -1035,6 +1055,7 @@ int cmd_gc(int argc,
} }


out: out:
maintenance_run_opts_release(&opts);
gc_config_release(&cfg); gc_config_release(&cfg);
return 0; return 0;
} }
@ -1211,8 +1232,14 @@ static int maintenance_task_prefetch(struct maintenance_run_opts *opts,
return 0; return 0;
} }


static int maintenance_task_gc(struct maintenance_run_opts *opts, static int maintenance_task_gc_foreground(struct maintenance_run_opts *opts,
struct gc_config *cfg UNUSED) struct gc_config *cfg)
{
return gc_foreground_tasks(opts, cfg);
}

static int maintenance_task_gc_background(struct maintenance_run_opts *opts,
struct gc_config *cfg UNUSED)
{ {
struct child_process child = CHILD_PROCESS_INIT; struct child_process child = CHILD_PROCESS_INIT;


@ -1226,6 +1253,7 @@ static int maintenance_task_gc(struct maintenance_run_opts *opts,
else else
strvec_push(&child.args, "--no-quiet"); strvec_push(&child.args, "--no-quiet");
strvec_push(&child.args, "--no-detach"); strvec_push(&child.args, "--no-detach");
strvec_push(&child.args, "--skip-foreground-tasks");


return run_command(&child); return run_command(&child);
} }
@ -1513,103 +1541,116 @@ static int maintenance_task_incremental_repack(struct maintenance_run_opts *opts
return 0; 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); struct gc_config *cfg);

typedef int (*maintenance_auto_fn)(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(struct gc_config *cfg);


struct maintenance_task { struct maintenance_task {
const char *name; const char *name;
maintenance_task_fn *fn;
maintenance_auto_fn *auto_condition;
unsigned enabled:1;


enum schedule_priority schedule; /*
* Work that will be executed before detaching. This should not include
* tasks that may run for an extended amount of time as it does cause
* auto-maintenance to block until foreground tasks have been run.
*/
maintenance_task_fn foreground;


/* -1 if not selected. */ /*
int selected_order; * Work that will be executed after detaching. When not detaching the
* work will be run in the foreground, as well.
*/
maintenance_task_fn background;

/*
* 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.
*/
maintenance_auto_fn auto_condition;
}; };


enum maintenance_task_label { static const struct maintenance_task tasks[] = {
TASK_PREFETCH,
TASK_LOOSE_OBJECTS,
TASK_INCREMENTAL_REPACK,
TASK_GC,
TASK_COMMIT_GRAPH,
TASK_PACK_REFS,
TASK_REFLOG_EXPIRE,
TASK_WORKTREE_PRUNE,
TASK_RERERE_GC,

/* Leave as final value */
TASK__COUNT
};

static struct maintenance_task tasks[] = {
[TASK_PREFETCH] = { [TASK_PREFETCH] = {
"prefetch", .name = "prefetch",
maintenance_task_prefetch, .background = maintenance_task_prefetch,
}, },
[TASK_LOOSE_OBJECTS] = { [TASK_LOOSE_OBJECTS] = {
"loose-objects", .name = "loose-objects",
maintenance_task_loose_objects, .background = maintenance_task_loose_objects,
loose_object_auto_condition, .auto_condition = loose_object_auto_condition,
}, },
[TASK_INCREMENTAL_REPACK] = { [TASK_INCREMENTAL_REPACK] = {
"incremental-repack", .name = "incremental-repack",
maintenance_task_incremental_repack, .background = maintenance_task_incremental_repack,
incremental_repack_auto_condition, .auto_condition = incremental_repack_auto_condition,
}, },
[TASK_GC] = { [TASK_GC] = {
"gc", .name = "gc",
maintenance_task_gc, .foreground = maintenance_task_gc_foreground,
need_to_gc, .background = maintenance_task_gc_background,
1, .auto_condition = need_to_gc,
}, },
[TASK_COMMIT_GRAPH] = { [TASK_COMMIT_GRAPH] = {
"commit-graph", .name = "commit-graph",
maintenance_task_commit_graph, .background = maintenance_task_commit_graph,
should_write_commit_graph, .auto_condition = should_write_commit_graph,
}, },
[TASK_PACK_REFS] = { [TASK_PACK_REFS] = {
"pack-refs", .name = "pack-refs",
maintenance_task_pack_refs, .foreground = maintenance_task_pack_refs,
pack_refs_condition, .auto_condition = pack_refs_condition,
}, },
[TASK_REFLOG_EXPIRE] = { [TASK_REFLOG_EXPIRE] = {
"reflog-expire", .name = "reflog-expire",
maintenance_task_reflog_expire, .foreground = maintenance_task_reflog_expire,
reflog_expire_condition, .auto_condition = reflog_expire_condition,
}, },
[TASK_WORKTREE_PRUNE] = { [TASK_WORKTREE_PRUNE] = {
"worktree-prune", .name = "worktree-prune",
maintenance_task_worktree_prune, .background = maintenance_task_worktree_prune,
worktree_prune_condition, .auto_condition = worktree_prune_condition,
}, },
[TASK_RERERE_GC] = { [TASK_RERERE_GC] = {
"rerere-gc", .name = "rerere-gc",
maintenance_task_rerere_gc, .background = maintenance_task_rerere_gc,
rerere_gc_condition, .auto_condition = rerere_gc_condition,
}, },
}; };


static int compare_tasks_by_selection(const void *a_, const void *b_) enum task_phase {
{ TASK_PHASE_FOREGROUND,
const struct maintenance_task *a = a_; TASK_PHASE_BACKGROUND,
const struct maintenance_task *b = b_; };


return b->selected_order - a->selected_order; static int maybe_run_task(const struct maintenance_task *task,
struct repository *repo,
struct maintenance_run_opts *opts,
struct gc_config *cfg,
enum task_phase phase)
{
int foreground = (phase == TASK_PHASE_FOREGROUND);
maintenance_task_fn fn = foreground ? task->foreground : task->background;
const char *region = foreground ? "maintenance foreground" : "maintenance";
int ret = 0;

if (!fn)
return 0;
if (opts->auto_flag &&
(!task->auto_condition || !task->auto_condition(cfg)))
return 0;

trace2_region_enter(region, task->name, repo);
if (fn(opts, cfg)) {
error(_("task '%s' failed"), task->name);
ret = 1;
}
trace2_region_leave(region, task->name, repo);

return ret;
} }


static int maintenance_run_tasks(struct maintenance_run_opts *opts, static int maintenance_run_tasks(struct maintenance_run_opts *opts,
struct gc_config *cfg) struct gc_config *cfg)
{ {
int i, found_selected = 0;
int result = 0; int result = 0;
struct lock_file lk; struct lock_file lk;
struct repository *r = the_repository; struct repository *r = the_repository;
@ -1631,6 +1672,11 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts,
} }
free(lock_path); free(lock_path);


for (size_t i = 0; i < opts->tasks_nr; i++)
if (maybe_run_task(&tasks[opts->tasks[i]], r, opts, cfg,
TASK_PHASE_FOREGROUND))
result = 1;

/* Failure to daemonize is ok, we'll continue in foreground. */ /* Failure to daemonize is ok, we'll continue in foreground. */
if (opts->detach > 0) { if (opts->detach > 0) {
trace2_region_enter("maintenance", "detach", the_repository); trace2_region_enter("maintenance", "detach", the_repository);
@ -1638,120 +1684,138 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts,
trace2_region_leave("maintenance", "detach", the_repository); trace2_region_leave("maintenance", "detach", the_repository);
} }


for (i = 0; !found_selected && i < TASK__COUNT; i++) for (size_t i = 0; i < opts->tasks_nr; i++)
found_selected = tasks[i].selected_order >= 0; if (maybe_run_task(&tasks[opts->tasks[i]], r, opts, cfg,

TASK_PHASE_BACKGROUND))
if (found_selected)
QSORT(tasks, TASK__COUNT, compare_tasks_by_selection);

for (i = 0; i < TASK__COUNT; i++) {
if (found_selected && tasks[i].selected_order < 0)
continue;

if (!found_selected && !tasks[i].enabled)
continue;

if (opts->auto_flag &&
(!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, cfg)) {
error(_("task '%s' failed"), tasks[i].name);
result = 1; result = 1;
}
trace2_region_leave("maintenance", tasks[i].name, r);
}


rollback_lock_file(&lk); rollback_lock_file(&lk);
return result; return result;
} }


static void initialize_maintenance_strategy(void) struct maintenance_strategy {
struct {
int enabled;
enum schedule_priority schedule;
} tasks[TASK__COUNT];
};

static const struct maintenance_strategy none_strategy = { 0 };
static const struct maintenance_strategy default_strategy = {
.tasks = {
[TASK_GC].enabled = 1,
},
};
static const struct maintenance_strategy incremental_strategy = {
.tasks = {
[TASK_COMMIT_GRAPH].enabled = 1,
[TASK_COMMIT_GRAPH].schedule = SCHEDULE_HOURLY,
[TASK_PREFETCH].enabled = 1,
[TASK_PREFETCH].schedule = SCHEDULE_HOURLY,
[TASK_INCREMENTAL_REPACK].enabled = 1,
[TASK_INCREMENTAL_REPACK].schedule = SCHEDULE_DAILY,
[TASK_LOOSE_OBJECTS].enabled = 1,
[TASK_LOOSE_OBJECTS].schedule = SCHEDULE_DAILY,
[TASK_PACK_REFS].enabled = 1,
[TASK_PACK_REFS].schedule = SCHEDULE_WEEKLY,
},
};

static void initialize_task_config(struct maintenance_run_opts *opts,
const struct string_list *selected_tasks)
{ {
struct strbuf config_name = STRBUF_INIT;
struct maintenance_strategy strategy;
const char *config_str; const char *config_str;


if (git_config_get_string_tmp("maintenance.strategy", &config_str)) /*
* In case the user has asked us to run tasks explicitly we only use
* those specified tasks. Specifically, we do _not_ want to consult the
* config or maintenance strategy.
*/
if (selected_tasks->nr) {
for (size_t i = 0; i < selected_tasks->nr; i++) {
enum maintenance_task_label label = (intptr_t)selected_tasks->items[i].util;;
ALLOC_GROW(opts->tasks, opts->tasks_nr + 1, opts->tasks_alloc);
opts->tasks[opts->tasks_nr++] = label;
}

return; return;

if (!strcasecmp(config_str, "incremental")) {
tasks[TASK_GC].schedule = SCHEDULE_NONE;
tasks[TASK_COMMIT_GRAPH].enabled = 1;
tasks[TASK_COMMIT_GRAPH].schedule = SCHEDULE_HOURLY;
tasks[TASK_PREFETCH].enabled = 1;
tasks[TASK_PREFETCH].schedule = SCHEDULE_HOURLY;
tasks[TASK_INCREMENTAL_REPACK].enabled = 1;
tasks[TASK_INCREMENTAL_REPACK].schedule = SCHEDULE_DAILY;
tasks[TASK_LOOSE_OBJECTS].enabled = 1;
tasks[TASK_LOOSE_OBJECTS].schedule = SCHEDULE_DAILY;
tasks[TASK_PACK_REFS].enabled = 1;
tasks[TASK_PACK_REFS].schedule = SCHEDULE_WEEKLY;
} }
}


static void initialize_task_config(int schedule) /*
{ * Otherwise, the strategy depends on whether we run as part of a
int i; * scheduled job or not:
struct strbuf config_name = STRBUF_INIT; *
* - Scheduled maintenance does not perform any housekeeping by
* default, but requires the user to pick a maintenance strategy.
*
* - Unscheduled maintenance uses our default strategy.
*
* Both of these are affected by the gitconfig though, which may
* override specific aspects of our strategy.
*/
if (opts->schedule) {
strategy = none_strategy;


if (schedule) if (!git_config_get_string_tmp("maintenance.strategy", &config_str)) {
initialize_maintenance_strategy(); if (!strcasecmp(config_str, "incremental"))
strategy = incremental_strategy;
}
} else {
strategy = default_strategy;
}


for (i = 0; i < TASK__COUNT; i++) { for (size_t i = 0; i < TASK__COUNT; i++) {
int config_value; int config_value;
char *config_str;


strbuf_reset(&config_name); strbuf_reset(&config_name);
strbuf_addf(&config_name, "maintenance.%s.enabled", strbuf_addf(&config_name, "maintenance.%s.enabled",
tasks[i].name); tasks[i].name);

if (!git_config_get_bool(config_name.buf, &config_value)) if (!git_config_get_bool(config_name.buf, &config_value))
tasks[i].enabled = config_value; strategy.tasks[i].enabled = config_value;
if (!strategy.tasks[i].enabled)
continue;


strbuf_reset(&config_name); if (opts->schedule) {
strbuf_addf(&config_name, "maintenance.%s.schedule", strbuf_reset(&config_name);
tasks[i].name); strbuf_addf(&config_name, "maintenance.%s.schedule",

tasks[i].name);
if (!git_config_get_string(config_name.buf, &config_str)) { if (!git_config_get_string_tmp(config_name.buf, &config_str))
tasks[i].schedule = parse_schedule(config_str); strategy.tasks[i].schedule = parse_schedule(config_str);
free(config_str); if (strategy.tasks[i].schedule < opts->schedule)
continue;
} }

ALLOC_GROW(opts->tasks, opts->tasks_nr + 1, opts->tasks_alloc);
opts->tasks[opts->tasks_nr++] = i;
} }


strbuf_release(&config_name); strbuf_release(&config_name);
} }


static int task_option_parse(const struct option *opt UNUSED, static int task_option_parse(const struct option *opt,
const char *arg, int unset) const char *arg, int unset)
{ {
int i, num_selected = 0; struct string_list *selected_tasks = opt->value;
struct maintenance_task *task = NULL; size_t i;


BUG_ON_OPT_NEG(unset); BUG_ON_OPT_NEG(unset);


for (i = 0; i < TASK__COUNT; i++) { for (i = 0; i < TASK__COUNT; i++)
if (tasks[i].selected_order >= 0) if (!strcasecmp(tasks[i].name, arg))
num_selected++; break;
if (!strcasecmp(tasks[i].name, arg)) { if (i >= TASK__COUNT) {
task = &tasks[i];
}
}

if (!task) {
error(_("'%s' is not a valid task"), arg); error(_("'%s' is not a valid task"), arg);
return 1; return 1;
} }


if (task->selected_order >= 0) { if (unsorted_string_list_has_string(selected_tasks, arg)) {
error(_("task '%s' cannot be selected multiple times"), arg); error(_("task '%s' cannot be selected multiple times"), arg);
return 1; return 1;
} }


task->selected_order = num_selected + 1; string_list_append(selected_tasks, arg)->util = (void *)(intptr_t)i;


return 0; return 0;
} }
@ -1759,8 +1823,8 @@ static int task_option_parse(const struct option *opt UNUSED,
static int maintenance_run(int argc, const char **argv, const char *prefix, static int maintenance_run(int argc, const char **argv, const char *prefix,
struct repository *repo UNUSED) struct repository *repo UNUSED)
{ {
int i;
struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT; struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT;
struct string_list selected_tasks = STRING_LIST_INIT_DUP;
struct gc_config cfg = GC_CONFIG_INIT; struct gc_config cfg = GC_CONFIG_INIT;
struct option builtin_maintenance_run_options[] = { struct option builtin_maintenance_run_options[] = {
OPT_BOOL(0, "auto", &opts.auto_flag, OPT_BOOL(0, "auto", &opts.auto_flag,
@ -1772,7 +1836,7 @@ static int maintenance_run(int argc, const char **argv, const char *prefix,
maintenance_opt_schedule), maintenance_opt_schedule),
OPT_BOOL(0, "quiet", &opts.quiet, OPT_BOOL(0, "quiet", &opts.quiet,
N_("do not report progress or other information over stderr")), N_("do not report progress or other information over stderr")),
OPT_CALLBACK_F(0, "task", NULL, N_("task"), OPT_CALLBACK_F(0, "task", &selected_tasks, N_("task"),
N_("run a specific task"), N_("run a specific task"),
PARSE_OPT_NONEG, task_option_parse), PARSE_OPT_NONEG, task_option_parse),
OPT_END() OPT_END()
@ -1781,25 +1845,27 @@ static int maintenance_run(int argc, const char **argv, const char *prefix,


opts.quiet = !isatty(2); opts.quiet = !isatty(2);


for (i = 0; i < TASK__COUNT; i++)
tasks[i].selected_order = -1;

argc = parse_options(argc, argv, prefix, argc = parse_options(argc, argv, prefix,
builtin_maintenance_run_options, builtin_maintenance_run_options,
builtin_maintenance_run_usage, builtin_maintenance_run_usage,
PARSE_OPT_STOP_AT_NON_OPTION); PARSE_OPT_STOP_AT_NON_OPTION);


if (opts.auto_flag && opts.schedule) die_for_incompatible_opt2(opts.auto_flag, "--auto",
die(_("use at most one of --auto and --schedule=<frequency>")); opts.schedule, "--schedule=");
die_for_incompatible_opt2(selected_tasks.nr, "--task=",
opts.schedule, "--schedule=");


gc_config(&cfg); gc_config(&cfg);
initialize_task_config(opts.schedule); initialize_task_config(&opts, &selected_tasks);


if (argc != 0) if (argc != 0)
usage_with_options(builtin_maintenance_run_usage, usage_with_options(builtin_maintenance_run_usage,
builtin_maintenance_run_options); builtin_maintenance_run_options);


ret = maintenance_run_tasks(&opts, &cfg); ret = maintenance_run_tasks(&opts, &cfg);

string_list_clear(&selected_tasks, 0);
maintenance_run_opts_release(&opts);
gc_config_release(&cfg); gc_config_release(&cfg);
return ret; return ret;
} }

View File

@ -303,7 +303,7 @@ static void runcommand_in_submodule_cb(const struct cache_entry *list_item,
char *displaypath; char *displaypath;


if (validate_submodule_path(path) < 0) if (validate_submodule_path(path) < 0)
exit(128); die(NULL);


displaypath = get_submodule_displaypath(path, info->prefix, displaypath = get_submodule_displaypath(path, info->prefix,
info->super_prefix); info->super_prefix);
@ -643,7 +643,7 @@ static void status_submodule(const char *path, const struct object_id *ce_oid,
}; };


if (validate_submodule_path(path) < 0) if (validate_submodule_path(path) < 0)
exit(128); die(NULL);


if (!submodule_from_path(the_repository, null_oid(the_hash_algo), path)) if (!submodule_from_path(the_repository, null_oid(the_hash_algo), path))
die(_("no submodule mapping found in .gitmodules for path '%s'"), die(_("no submodule mapping found in .gitmodules for path '%s'"),
@ -1257,7 +1257,7 @@ static void sync_submodule(const char *path, const char *prefix,
return; return;


if (validate_submodule_path(path) < 0) if (validate_submodule_path(path) < 0)
exit(128); die(NULL);


sub = submodule_from_path(the_repository, null_oid(the_hash_algo), path); sub = submodule_from_path(the_repository, null_oid(the_hash_algo), path);


@ -1402,7 +1402,7 @@ static void deinit_submodule(const char *path, const char *prefix,
char *sub_git_dir = xstrfmt("%s/.git", path); char *sub_git_dir = xstrfmt("%s/.git", path);


if (validate_submodule_path(path) < 0) if (validate_submodule_path(path) < 0)
exit(128); die(NULL);


sub = submodule_from_path(the_repository, null_oid(the_hash_algo), path); sub = submodule_from_path(the_repository, null_oid(the_hash_algo), path);


@ -1724,7 +1724,7 @@ static int clone_submodule(const struct module_clone_data *clone_data,
char *to_free = NULL; char *to_free = NULL;


if (validate_submodule_path(clone_data_path) < 0) if (validate_submodule_path(clone_data_path) < 0)
exit(128); die(NULL);


if (!is_absolute_path(clone_data->path)) if (!is_absolute_path(clone_data->path))
clone_data_path = to_free = xstrfmt("%s/%s", repo_get_work_tree(the_repository), clone_data_path = to_free = xstrfmt("%s/%s", repo_get_work_tree(the_repository),
@ -3526,7 +3526,7 @@ static int module_add(int argc, const char **argv, const char *prefix,
strip_dir_trailing_slashes(add_data.sm_path); strip_dir_trailing_slashes(add_data.sm_path);


if (validate_submodule_path(add_data.sm_path) < 0) if (validate_submodule_path(add_data.sm_path) < 0)
exit(128); die(NULL);


die_on_index_match(add_data.sm_path, force); die_on_index_match(add_data.sm_path, force);
die_on_repo_without_commits(add_data.sm_path); die_on_repo_without_commits(add_data.sm_path);

View File

@ -49,9 +49,9 @@ test_expect_success 'run [--auto|--quiet]' '
git maintenance run --auto 2>/dev/null && git maintenance run --auto 2>/dev/null &&
GIT_TRACE2_EVENT="$(pwd)/run-no-quiet.txt" \ GIT_TRACE2_EVENT="$(pwd)/run-no-quiet.txt" \
git maintenance run --no-quiet 2>/dev/null && git maintenance run --no-quiet 2>/dev/null &&
test_subcommand git gc --quiet --no-detach <run-no-auto.txt && test_subcommand git gc --quiet --no-detach --skip-foreground-tasks <run-no-auto.txt &&
test_subcommand ! git gc --auto --quiet --no-detach <run-auto.txt && test_subcommand ! git gc --auto --quiet --no-detach --skip-foreground-tasks <run-auto.txt &&
test_subcommand git gc --no-quiet --no-detach <run-no-quiet.txt test_subcommand git gc --no-quiet --no-detach --skip-foreground-tasks <run-no-quiet.txt
' '


test_expect_success 'maintenance.auto config option' ' test_expect_success 'maintenance.auto config option' '
@ -154,9 +154,9 @@ test_expect_success 'run --task=<task>' '
git maintenance run --task=commit-graph 2>/dev/null && git maintenance run --task=commit-graph 2>/dev/null &&
GIT_TRACE2_EVENT="$(pwd)/run-both.txt" \ GIT_TRACE2_EVENT="$(pwd)/run-both.txt" \
git maintenance run --task=commit-graph --task=gc 2>/dev/null && git maintenance run --task=commit-graph --task=gc 2>/dev/null &&
test_subcommand ! git gc --quiet --no-detach <run-commit-graph.txt && test_subcommand ! git gc --quiet --no-detach --skip-foreground-tasks <run-commit-graph.txt &&
test_subcommand git gc --quiet --no-detach <run-gc.txt && test_subcommand git gc --quiet --no-detach --skip-foreground-tasks <run-gc.txt &&
test_subcommand git gc --quiet --no-detach <run-both.txt && test_subcommand git gc --quiet --no-detach --skip-foreground-tasks <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-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-gc.txt &&
test_subcommand git commit-graph write --split --reachable --no-progress <run-both.txt test_subcommand git commit-graph write --split --reachable --no-progress <run-both.txt
@ -610,7 +610,12 @@ test_expect_success 'rerere-gc task with --auto honors maintenance.rerere-gc.aut


test_expect_success '--auto and --schedule incompatible' ' test_expect_success '--auto and --schedule incompatible' '
test_must_fail git maintenance run --auto --schedule=daily 2>err && test_must_fail git maintenance run --auto --schedule=daily 2>err &&
test_grep "at most one" err test_grep "cannot be used together" err
'

test_expect_success '--task and --schedule incompatible' '
test_must_fail git maintenance run --task=pack-refs --schedule=daily 2>err &&
test_grep "cannot be used together" err
' '


test_expect_success 'invalid --schedule value' ' test_expect_success 'invalid --schedule value' '

View File

@ -67,6 +67,8 @@ static NORETURN void usage_builtin(const char *err, va_list params)


static void die_message_builtin(const char *err, va_list params) static void die_message_builtin(const char *err, va_list params)
{ {
if (!err)
return;
trace2_cmd_error_va(err, params); trace2_cmd_error_va(err, params);
vreportf(_("fatal: "), err, params); vreportf(_("fatal: "), err, params);
} }