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 tasksmaint
commit
4c9a5d7729
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
414
builtin/gc.c
414
builtin/gc.c
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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' '
|
||||||
|
|
2
usage.c
2
usage.c
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue