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) {
fprintf_ln(stderr, _("Patch format detection failed."));
exit(128);
die(NULL);
}

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);
}

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,
tree->buffer, tree->size);
if (parse_tree(new_tree) < 0)
exit(128);
die(NULL);
tree = new_tree;
init_tree_desc(&trees[1], &tree->object.oid,
tree->buffer, tree->size);
@ -913,7 +913,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
work,
old_tree);
if (ret < 0)
exit(128);
die(NULL);
ret = reset_tree(new_tree,
opts, 0,
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,
updated);
if (fast_forward < 0)
exit(128);
die(NULL);
forced_updates_ms += (getnanotime() - t_before) / 1000000;
} else {
fast_forward = 1;

View File

@ -251,7 +251,24 @@ static enum schedule_priority parse_schedule(const char *value)
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 {
enum maintenance_task_label *tasks;
size_t tasks_nr, tasks_alloc;
int auto_flag;
int detach;
int quiet;
@ -261,6 +278,11 @@ struct maintenance_run_opts {
.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)
{
/*
@ -796,22 +818,14 @@ done:
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)
{
/*
* 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))
die(FAILED_RUN, "pack-refs");
return error(FAILED_RUN, "pack-refs");
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,
@ -820,12 +834,12 @@ int cmd_gc(int argc,
struct repository *repo UNUSED)
{
int aggressive = 0;
int quiet = 0;
int force = 0;
const char *name;
pid_t pid;
int daemonized = 0;
int keep_largest_pack = -1;
int skip_foreground_tasks = 0;
timestamp_t dummy;
struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_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;
int ret;
struct option builtin_gc_options[] = {
OPT__QUIET(&quiet, N_("suppress progress reporting")),
OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
{
.type = OPTION_STRING,
.long_name = "prune",
@ -858,6 +872,8 @@ int cmd_gc(int argc,
N_("repack all other packs except the largest pack")),
OPT_STRING(0, "expire-to", &cfg.repack_expire_to, N_("dir"),
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()
};

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

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

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

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

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

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

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

if (!repository_format_precious_objects) {
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);
/* run `git prune` even if using cruft packs */
strvec_push(&prune_cmd.args, cfg.prune_expire);
if (quiet)
if (opts.quiet)
strvec_push(&prune_cmd.args, "--no-progress");
if (repo_has_promisor_remote(the_repository))
strvec_push(&prune_cmd.args,
@ -1021,7 +1041,7 @@ int cmd_gc(int argc,

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

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

out:
maintenance_run_opts_release(&opts);
gc_config_release(&cfg);
return 0;
}
@ -1211,7 +1232,13 @@ 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_foreground(struct maintenance_run_opts *opts,
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;
@ -1226,6 +1253,7 @@ static int maintenance_task_gc(struct maintenance_run_opts *opts,
else
strvec_push(&child.args, "--no-quiet");
strvec_push(&child.args, "--no-detach");
strvec_push(&child.args, "--skip-foreground-tasks");

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

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

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

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,
struct gc_config *cfg)
{
int i, found_selected = 0;
int result = 0;
struct lock_file lk;
struct repository *r = the_repository;
@ -1631,6 +1672,11 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts,
}
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. */
if (opts->detach > 0) {
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);
}

for (i = 0; !found_selected && i < TASK__COUNT; i++)
found_selected = tasks[i].selected_order >= 0;

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);
for (size_t i = 0; i < opts->tasks_nr; i++)
if (maybe_run_task(&tasks[opts->tasks[i]], r, opts, cfg,
TASK_PHASE_BACKGROUND))
result = 1;
}
trace2_region_leave("maintenance", tasks[i].name, r);
}

rollback_lock_file(&lk);
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;

if (git_config_get_string_tmp("maintenance.strategy", &config_str))
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;
/*
* 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;
}
}

static void initialize_task_config(int schedule)
{
int i;
struct strbuf config_name = STRBUF_INIT;
return;
}

if (schedule)
initialize_maintenance_strategy();
/*
* Otherwise, the strategy depends on whether we run as part of a
* scheduled job or not:
*
* - 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;

for (i = 0; i < TASK__COUNT; i++) {
if (!git_config_get_string_tmp("maintenance.strategy", &config_str)) {
if (!strcasecmp(config_str, "incremental"))
strategy = incremental_strategy;
}
} else {
strategy = default_strategy;
}

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

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

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;

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

if (!git_config_get_string(config_name.buf, &config_str)) {
tasks[i].schedule = parse_schedule(config_str);
free(config_str);
if (!git_config_get_string_tmp(config_name.buf, &config_str))
strategy.tasks[i].schedule = parse_schedule(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);
}

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

BUG_ON_OPT_NEG(unset);

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

if (!task) {
for (i = 0; i < TASK__COUNT; i++)
if (!strcasecmp(tasks[i].name, arg))
break;
if (i >= TASK__COUNT) {
error(_("'%s' is not a valid task"), arg);
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);
return 1;
}

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

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,
struct repository *repo UNUSED)
{
int i;
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 option builtin_maintenance_run_options[] = {
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),
OPT_BOOL(0, "quiet", &opts.quiet,
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"),
PARSE_OPT_NONEG, task_option_parse),
OPT_END()
@ -1781,25 +1845,27 @@ static int maintenance_run(int argc, const char **argv, const char *prefix,

opts.quiet = !isatty(2);

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

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

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

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

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

ret = maintenance_run_tasks(&opts, &cfg);

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

View File

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

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

displaypath = get_submodule_displaypath(path, info->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)
exit(128);
die(NULL);

if (!submodule_from_path(the_repository, null_oid(the_hash_algo), path))
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;

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

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);

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

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;

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

if (!is_absolute_path(clone_data->path))
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);

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

die_on_index_match(add_data.sm_path, force);
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_TRACE2_EVENT="$(pwd)/run-no-quiet.txt" \
git maintenance run --no-quiet 2>/dev/null &&
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_subcommand git gc --quiet --no-detach --skip-foreground-tasks <run-no-auto.txt &&
test_subcommand ! git gc --auto --quiet --no-detach --skip-foreground-tasks <run-auto.txt &&
test_subcommand git gc --no-quiet --no-detach --skip-foreground-tasks <run-no-quiet.txt
'

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_TRACE2_EVENT="$(pwd)/run-both.txt" \
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 <run-gc.txt &&
test_subcommand git gc --quiet --no-detach <run-both.txt &&
test_subcommand ! git gc --quiet --no-detach --skip-foreground-tasks <run-commit-graph.txt &&
test_subcommand git gc --quiet --no-detach --skip-foreground-tasks <run-gc.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-gc.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_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' '

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)
{
if (!err)
return;
trace2_cmd_error_va(err, params);
vreportf(_("fatal: "), err, params);
}