summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Documentation/config.txt2
-rw-r--r--Documentation/config/maintenance.txt16
-rw-r--r--Documentation/fetch-options.txt6
-rw-r--r--Documentation/git-clone.txt6
-rw-r--r--Documentation/git-maintenance.txt79
-rw-r--r--builtin.h1
-rw-r--r--builtin/am.c2
-rw-r--r--builtin/commit.c2
-rw-r--r--builtin/fetch.c6
-rw-r--r--builtin/gc.c337
-rw-r--r--builtin/merge.c2
-rw-r--r--builtin/rebase.c4
-rw-r--r--command-list.txt1
-rw-r--r--commit-graph.c8
-rw-r--r--commit-graph.h1
-rw-r--r--git.c1
-rw-r--r--object.h1
-rw-r--r--run-command.c16
-rw-r--r--run-command.h2
-rwxr-xr-xt/t5510-fetch.sh2
-rwxr-xr-xt/t5514-fetch-multiple.sh2
-rwxr-xr-xt/t7900-maintenance.sh65
-rw-r--r--t/test-lib-functions.sh33
24 files changed, 568 insertions, 28 deletions
diff --git a/.gitignore b/.gitignore
index d0f692a355..800b812c6a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -90,6 +90,7 @@
/git-ls-tree
/git-mailinfo
/git-mailsplit
+/git-maintenance
/git-merge
/git-merge-base
/git-merge-index
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 3042d80978..f93b6837e4 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -398,6 +398,8 @@ include::config/mailinfo.txt[]
include::config/mailmap.txt[]
+include::config/maintenance.txt[]
+
include::config/man.txt[]
include::config/merge.txt[]
diff --git a/Documentation/config/maintenance.txt b/Documentation/config/maintenance.txt
new file mode 100644
index 0000000000..7cc6700d57
--- /dev/null
+++ b/Documentation/config/maintenance.txt
@@ -0,0 +1,16 @@
+maintenance.<task>.enabled::
+ This boolean config option controls whether the maintenance task
+ with name `<task>` is run when no `--task` option is specified to
+ `git maintenance run`. These config values are ignored if a
+ `--task` option exists. By default, only `maintenance.gc.enabled`
+ is true.
+
+maintenance.commit-graph.auto::
+ This integer config option controls how often the `commit-graph` task
+ should be run as part of `git maintenance run --auto`. If zero, then
+ the `commit-graph` task will not run with the `--auto` option. A
+ negative value will force the task to run every time. Otherwise, a
+ positive value implies the command should run when the number of
+ reachable commits that are not in the commit-graph file is at least
+ the value of `maintenance.commit-graph.auto`. The default value is
+ 100.
diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt
index e8104c082e..2bf77b46fd 100644
--- a/Documentation/fetch-options.txt
+++ b/Documentation/fetch-options.txt
@@ -95,9 +95,11 @@ ifndef::git-pull[]
Allow several <repository> and <group> arguments to be
specified. No <refspec>s may be specified.
+--[no-]auto-maintenance::
--[no-]auto-gc::
- Run `git gc --auto` at the end to perform garbage collection
- if needed. This is enabled by default.
+ Run `git maintenance run --auto` at the end to perform automatic
+ repository maintenance if needed. (`--[no-]auto-gc` is a synonym.)
+ This is enabled by default.
--[no-]write-commit-graph::
Write a commit-graph after fetching. This overrides the config
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index c898310099..097e6a86c5 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -78,9 +78,9 @@ repository using this option and then delete branches (or use any
other Git command that makes any existing commit unreferenced) in the
source repository, some objects may become unreferenced (or dangling).
These objects may be removed by normal Git operations (such as `git commit`)
-which automatically call `git gc --auto`. (See linkgit:git-gc[1].)
-If these objects are removed and were referenced by the cloned repository,
-then the cloned repository will become corrupt.
+which automatically call `git maintenance run --auto`. (See
+linkgit:git-maintenance[1].) If these objects are removed and were referenced
+by the cloned repository, then the cloned repository will become corrupt.
+
Note that running `git repack` without the `--local` option in a repository
cloned with `--shared` will copy objects from the source repository into a pack
diff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt
new file mode 100644
index 0000000000..6abcb8255a
--- /dev/null
+++ b/Documentation/git-maintenance.txt
@@ -0,0 +1,79 @@
+git-maintenance(1)
+==================
+
+NAME
+----
+git-maintenance - Run tasks to optimize Git repository data
+
+
+SYNOPSIS
+--------
+[verse]
+'git maintenance' run [<options>]
+
+
+DESCRIPTION
+-----------
+Run tasks to optimize Git repository data, speeding up other Git commands
+and reducing storage requirements for the repository.
+
+Git commands that add repository data, such as `git add` or `git fetch`,
+are optimized for a responsive user experience. These commands do not take
+time to optimize the Git data, since such optimizations scale with the full
+size of the repository while these user commands each perform a relatively
+small action.
+
+The `git maintenance` command provides flexibility for how to optimize the
+Git repository.
+
+SUBCOMMANDS
+-----------
+
+run::
+ Run one or more maintenance tasks. If one or more `--task` options
+ are specified, then those tasks are run in that order. Otherwise,
+ the tasks are determined by which `maintenance.<task>.enabled`
+ config options are true. By default, only `maintenance.gc.enabled`
+ is true.
+
+TASKS
+-----
+
+commit-graph::
+ The `commit-graph` job updates the `commit-graph` files incrementally,
+ then verifies that the written data is correct. The incremental
+ write is safe to run alongside concurrent Git processes since it
+ will not expire `.graph` files that were in the previous
+ `commit-graph-chain` file. They will be deleted by a later run based
+ on the expiration delay.
+
+gc::
+ Clean up unnecessary files and optimize the local repository. "GC"
+ stands for "garbage collection," but this task performs many
+ smaller tasks. This task can be expensive for large repositories,
+ as it repacks all Git objects into a single pack-file. It can also
+ be disruptive in some situations, as it deletes stale data. See
+ linkgit:git-gc[1] for more details on garbage collection in Git.
+
+OPTIONS
+-------
+--auto::
+ When combined with the `run` subcommand, run maintenance tasks
+ only if certain thresholds are met. For example, the `gc` task
+ runs when the number of loose objects exceeds the number stored
+ in the `gc.auto` config setting, or when the number of pack-files
+ exceeds the `gc.autoPackLimit` config setting.
+
+--quiet::
+ Do not report progress or other information over `stderr`.
+
+--task=<task>::
+ If this option is specified one or more times, then only run the
+ specified tasks in the specified order. If no `--task=<task>`
+ arguments are specified, then only the tasks with
+ `maintenance.<task>.enabled` configured as `true` are considered.
+ See the 'TASKS' section for the list of accepted `<task>` values.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/builtin.h b/builtin.h
index ba954e180c..53fb290963 100644
--- a/builtin.h
+++ b/builtin.h
@@ -172,6 +172,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix);
int cmd_ls_remote(int argc, const char **argv, const char *prefix);
int cmd_mailinfo(int argc, const char **argv, const char *prefix);
int cmd_mailsplit(int argc, const char **argv, const char *prefix);
+int cmd_maintenance(int argc, const char **argv, const char *prefix);
int cmd_merge(int argc, const char **argv, const char *prefix);
int cmd_merge_base(int argc, const char **argv, const char *prefix);
int cmd_merge_index(int argc, const char **argv, const char *prefix);
diff --git a/builtin/am.c b/builtin/am.c
index b5c63ddf1d..7259186408 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1813,7 +1813,7 @@ next:
if (!state->rebasing) {
am_destroy(state);
close_object_store(the_repository->objects);
- run_auto_gc(state->quiet);
+ run_auto_maintenance(state->quiet);
}
}
diff --git a/builtin/commit.c b/builtin/commit.c
index 5d91b13a5c..42b964e0ca 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1700,7 +1700,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
git_test_write_commit_graph_or_die();
repo_rerere(the_repository, 0);
- run_auto_gc(quiet);
+ run_auto_maintenance(quiet);
run_commit_hook(use_editor, get_index_file(), "post-commit", NULL);
if (amend && !no_post_rewrite) {
commit_post_rewrite(the_repository, current_head, &oid);
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 1b00c2bee9..d5bf526277 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -200,8 +200,10 @@ static struct option builtin_fetch_options[] = {
OPT_STRING_LIST(0, "negotiation-tip", &negotiation_tip, N_("revision"),
N_("report that we have only objects reachable from this object")),
OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
+ OPT_BOOL(0, "auto-maintenance", &enable_auto_gc,
+ N_("run 'maintenance --auto' after fetching")),
OPT_BOOL(0, "auto-gc", &enable_auto_gc,
- N_("run 'gc --auto' after fetching")),
+ N_("run 'maintenance --auto' after fetching")),
OPT_BOOL(0, "show-forced-updates", &fetch_show_forced_updates,
N_("check for forced-updates on all updated branches")),
OPT_BOOL(0, "write-commit-graph", &fetch_write_commit_graph,
@@ -1925,7 +1927,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
close_object_store(the_repository->objects);
if (enable_auto_gc)
- run_auto_gc(verbosity < 0);
+ run_auto_maintenance(verbosity < 0);
return result;
}
diff --git a/builtin/gc.c b/builtin/gc.c
index aafa0946f5..090959350e 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -28,6 +28,7 @@
#include "blob.h"
#include "tree.h"
#include "promisor-remote.h"
+#include "refs.h"
#define FAILED_RUN "failed to run %s"
@@ -699,3 +700,339 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
return 0;
}
+
+static const char * const builtin_maintenance_run_usage[] = {
+ N_("git maintenance run [--auto] [--[no-]quiet] [--task=<task>]"),
+ NULL
+};
+
+struct maintenance_run_opts {
+ int auto_flag;
+ int quiet;
+};
+
+/* Remember to update object flag allocation in object.h */
+#define SEEN (1u<<0)
+
+struct cg_auto_data {
+ int num_not_in_graph;
+ int limit;
+};
+
+static int dfs_on_ref(const char *refname,
+ const struct object_id *oid, int flags,
+ void *cb_data)
+{
+ struct cg_auto_data *data = (struct cg_auto_data *)cb_data;
+ int result = 0;
+ struct object_id peeled;
+ struct commit_list *stack = NULL;
+ struct commit *commit;
+
+ if (!peel_ref(refname, &peeled))
+ oid = &peeled;
+ if (oid_object_info(the_repository, oid, NULL) != OBJ_COMMIT)
+ return 0;
+
+ commit = lookup_commit(the_repository, oid);
+ if (!commit)
+ return 0;
+ if (parse_commit(commit))
+ return 0;
+
+ commit_list_append(commit, &stack);
+
+ while (!result && stack) {
+ struct commit_list *parent;
+
+ commit = pop_commit(&stack);
+
+ for (parent = commit->parents; parent; parent = parent->next) {
+ if (parse_commit(parent->item) ||
+ commit_graph_position(parent->item) != COMMIT_NOT_FROM_GRAPH ||
+ parent->item->object.flags & SEEN)
+ continue;
+
+ parent->item->object.flags |= SEEN;
+ data->num_not_in_graph++;
+
+ if (data->num_not_in_graph >= data->limit) {
+ result = 1;
+ break;
+ }
+
+ commit_list_append(parent->item, &stack);
+ }
+ }
+
+ free_commit_list(stack);
+ return result;
+}
+
+static int should_write_commit_graph(void)
+{
+ int result;
+ struct cg_auto_data data;
+
+ data.num_not_in_graph = 0;
+ data.limit = 100;
+ git_config_get_int("maintenance.commit-graph.auto",
+ &data.limit);
+
+ if (!data.limit)
+ return 0;
+ if (data.limit < 0)
+ return 1;
+
+ result = for_each_ref(dfs_on_ref, &data);
+
+ clear_commit_marks_all(SEEN);
+
+ return result;
+}
+
+static int run_write_commit_graph(struct maintenance_run_opts *opts)
+{
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ child.git_cmd = 1;
+ strvec_pushl(&child.args, "commit-graph", "write",
+ "--split", "--reachable", NULL);
+
+ if (opts->quiet)
+ strvec_push(&child.args, "--no-progress");
+
+ return !!run_command(&child);
+}
+
+static int maintenance_task_commit_graph(struct maintenance_run_opts *opts)
+{
+ close_object_store(the_repository->objects);
+ if (run_write_commit_graph(opts)) {
+ error(_("failed to write commit-graph"));
+ return 1;
+ }
+
+ return 0;
+}
+
+static int maintenance_task_gc(struct maintenance_run_opts *opts)
+{
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ child.git_cmd = 1;
+ strvec_push(&child.args, "gc");
+
+ if (opts->auto_flag)
+ strvec_push(&child.args, "--auto");
+ if (opts->quiet)
+ strvec_push(&child.args, "--quiet");
+ else
+ strvec_push(&child.args, "--no-quiet");
+
+ close_object_store(the_repository->objects);
+ return run_command(&child);
+}
+
+typedef int maintenance_task_fn(struct maintenance_run_opts *opts);
+
+/*
+ * An auto condition function returns 1 if the task should run
+ * and 0 if the task should NOT run. See needs_to_gc() for an
+ * example.
+ */
+typedef int maintenance_auto_fn(void);
+
+struct maintenance_task {
+ const char *name;
+ maintenance_task_fn *fn;
+ maintenance_auto_fn *auto_condition;
+ unsigned enabled:1;
+
+ /* -1 if not selected. */
+ int selected_order;
+};
+
+enum maintenance_task_label {
+ TASK_GC,
+ TASK_COMMIT_GRAPH,
+
+ /* Leave as final value */
+ TASK__COUNT
+};
+
+static struct maintenance_task tasks[] = {
+ [TASK_GC] = {
+ "gc",
+ maintenance_task_gc,
+ need_to_gc,
+ 1,
+ },
+ [TASK_COMMIT_GRAPH] = {
+ "commit-graph",
+ maintenance_task_commit_graph,
+ should_write_commit_graph,
+ },
+};
+
+static int compare_tasks_by_selection(const void *a_, const void *b_)
+{
+ const struct maintenance_task *a, *b;
+
+ a = (const struct maintenance_task *)&a_;
+ b = (const struct maintenance_task *)&b_;
+
+ return b->selected_order - a->selected_order;
+}
+
+static int maintenance_run_tasks(struct maintenance_run_opts *opts)
+{
+ int i, found_selected = 0;
+ int result = 0;
+ struct lock_file lk;
+ struct repository *r = the_repository;
+ char *lock_path = xstrfmt("%s/maintenance", r->objects->odb->path);
+
+ if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
+ /*
+ * Another maintenance command is running.
+ *
+ * If --auto was provided, then it is likely due to a
+ * recursive process stack. Do not report an error in
+ * that case.
+ */
+ if (!opts->auto_flag && !opts->quiet)
+ warning(_("lock file '%s' exists, skipping maintenance"),
+ lock_path);
+ free(lock_path);
+ return 0;
+ }
+ free(lock_path);
+
+ 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()))
+ continue;
+
+ trace2_region_enter("maintenance", tasks[i].name, r);
+ if (tasks[i].fn(opts)) {
+ error(_("task '%s' failed"), tasks[i].name);
+ result = 1;
+ }
+ trace2_region_leave("maintenance", tasks[i].name, r);
+ }
+
+ rollback_lock_file(&lk);
+ return result;
+}
+
+static void initialize_task_config(void)
+{
+ int i;
+ struct strbuf config_name = STRBUF_INIT;
+ gc_config();
+
+ for (i = 0; i < TASK__COUNT; i++) {
+ int config_value;
+
+ strbuf_setlen(&config_name, 0);
+ 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;
+ }
+
+ strbuf_release(&config_name);
+}
+
+static int task_option_parse(const struct option *opt,
+ const char *arg, int unset)
+{
+ int i, num_selected = 0;
+ struct maintenance_task *task = NULL;
+
+ 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) {
+ error(_("'%s' is not a valid task"), arg);
+ return 1;
+ }
+
+ if (task->selected_order >= 0) {
+ error(_("task '%s' cannot be selected multiple times"), arg);
+ return 1;
+ }
+
+ task->selected_order = num_selected + 1;
+
+ return 0;
+}
+
+static int maintenance_run(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ struct maintenance_run_opts opts;
+ struct option builtin_maintenance_run_options[] = {
+ OPT_BOOL(0, "auto", &opts.auto_flag,
+ N_("run tasks based on the state of the repository")),
+ OPT_BOOL(0, "quiet", &opts.quiet,
+ N_("do not report progress or other information over stderr")),
+ OPT_CALLBACK_F(0, "task", NULL, N_("task"),
+ N_("run a specific task"),
+ PARSE_OPT_NONEG, task_option_parse),
+ OPT_END()
+ };
+ memset(&opts, 0, sizeof(opts));
+
+ opts.quiet = !isatty(2);
+ initialize_task_config();
+
+ 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 (argc != 0)
+ usage_with_options(builtin_maintenance_run_usage,
+ builtin_maintenance_run_options);
+ return maintenance_run_tasks(&opts);
+}
+
+static const char builtin_maintenance_usage[] = N_("git maintenance run [<options>]");
+
+int cmd_maintenance(int argc, const char **argv, const char *prefix)
+{
+ if (argc < 2 ||
+ (argc == 2 && !strcmp(argv[1], "-h")))
+ usage(builtin_maintenance_usage);
+
+ if (!strcmp(argv[1], "run"))
+ return maintenance_run(argc - 1, argv + 1, prefix);
+
+ die(_("invalid subcommand: %s"), argv[1]);
+}
diff --git a/builtin/merge.c b/builtin/merge.c
index 032a8f5434..9d5359edc2 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -456,7 +456,7 @@ static void finish(struct commit *head_commit,
* user should see them.
*/
close_object_store(the_repository->objects);
- run_auto_gc(verbosity < 0);
+ run_auto_maintenance(verbosity < 0);
}
}
if (new_head && show_diffstat) {
diff --git a/builtin/rebase.c b/builtin/rebase.c
index c4ff2039ef..eeca53382f 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -734,10 +734,10 @@ static int finish_rebase(struct rebase_options *opts)
apply_autostash(state_dir_path("autostash", opts));
close_object_store(the_repository->objects);
/*
- * We ignore errors in 'gc --auto', since the
+ * We ignore errors in 'git maintenance run --auto', since the
* user should see them.
*/
- run_auto_gc(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE)));
+ run_auto_maintenance(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE)));
if (opts->type == REBASE_MERGE) {
struct replay_opts replay = REPLAY_OPTS_INIT;
diff --git a/command-list.txt b/command-list.txt
index e5901f2213..0e3204e7d1 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -117,6 +117,7 @@ git-ls-remote plumbinginterrogators
git-ls-tree plumbinginterrogators
git-mailinfo purehelpers
git-mailsplit purehelpers
+git-maintenance mainporcelain
git-merge mainporcelain history
git-merge-base plumbinginterrogators
git-merge-file plumbingmanipulators
diff --git a/commit-graph.c b/commit-graph.c
index 0ed003e218..695c1cf7a3 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -172,7 +172,7 @@ static char *get_split_graph_filename(struct object_directory *odb,
oid_hex);
}
-static char *get_chain_filename(struct object_directory *odb)
+char *get_commit_graph_chain_filename(struct object_directory *odb)
{
return xstrfmt("%s/info/commit-graphs/commit-graph-chain", odb->path);
}
@@ -523,7 +523,7 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r,
struct stat st;
struct object_id *oids;
int i = 0, valid = 1, count;
- char *chain_name = get_chain_filename(odb);
+ char *chain_name = get_commit_graph_chain_filename(odb);
FILE *fp;
int stat_res;
@@ -1675,7 +1675,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
}
if (ctx->split) {
- char *lock_name = get_chain_filename(ctx->odb);
+ char *lock_name = get_commit_graph_chain_filename(ctx->odb);
hold_lock_file_for_update_mode(&lk, lock_name,
LOCK_DIE_ON_ERROR, 0444);
@@ -2045,7 +2045,7 @@ static void expire_commit_graphs(struct write_commit_graph_context *ctx)
if (ctx->split_opts && ctx->split_opts->expire_time)
expire_time = ctx->split_opts->expire_time;
if (!ctx->split) {
- char *chain_file_name = get_chain_filename(ctx->odb);
+ char *chain_file_name = get_commit_graph_chain_filename(ctx->odb);
unlink(chain_file_name);
free(chain_file_name);
ctx->num_commit_graphs_after = 0;
diff --git a/commit-graph.h b/commit-graph.h
index 09a97030dc..765221cdcc 100644
--- a/commit-graph.h
+++ b/commit-graph.h
@@ -25,6 +25,7 @@ struct raw_object_store;
struct string_list;
char *get_commit_graph_filename(struct object_directory *odb);
+char *get_commit_graph_chain_filename(struct object_directory *odb);
int open_commit_graph(const char *graph_file, int *fd, struct stat *st);
/*
diff --git a/git.c b/git.c
index 01c456edce..f1e8b56d99 100644
--- a/git.c
+++ b/git.c
@@ -534,6 +534,7 @@ static struct cmd_struct commands[] = {
{ "ls-tree", cmd_ls_tree, RUN_SETUP },
{ "mailinfo", cmd_mailinfo, RUN_SETUP_GENTLY | NO_PARSEOPT },
{ "mailsplit", cmd_mailsplit, NO_PARSEOPT },
+ { "maintenance", cmd_maintenance, RUN_SETUP_GENTLY | NO_PARSEOPT },
{ "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
{ "merge-base", cmd_merge_base, RUN_SETUP },
{ "merge-file", cmd_merge_file, RUN_SETUP_GENTLY },
diff --git a/object.h b/object.h
index 96a2105859..20b18805f0 100644
--- a/object.h
+++ b/object.h
@@ -73,6 +73,7 @@ struct object_array {
* sha1-name.c: 20
* list-objects-filter.c: 21
* builtin/fsck.c: 0--3
+ * builtin/gc.c: 0
* builtin/index-pack.c: 2021
* builtin/pack-objects.c: 20
* builtin/reflog.c: 10--12
diff --git a/run-command.c b/run-command.c
index cc9c3296ba..2ee59acdc8 100644
--- a/run-command.c
+++ b/run-command.c
@@ -1866,15 +1866,13 @@ int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,
return result;
}
-int run_auto_gc(int quiet)
+int run_auto_maintenance(int quiet)
{
- struct strvec argv_gc_auto = STRVEC_INIT;
- int status;
+ struct child_process maint = CHILD_PROCESS_INIT;
- strvec_pushl(&argv_gc_auto, "gc", "--auto", NULL);
- if (quiet)
- strvec_push(&argv_gc_auto, "--quiet");
- status = run_command_v_opt(argv_gc_auto.v, RUN_GIT_CMD);
- strvec_clear(&argv_gc_auto);
- return status;
+ maint.git_cmd = 1;
+ strvec_pushl(&maint.args, "maintenance", "run", "--auto", NULL);
+ strvec_push(&maint.args, quiet ? "--quiet" : "--no-quiet");
+
+ return run_command(&maint);
}
diff --git a/run-command.h b/run-command.h
index 8b9bfaef16..6472b38bde 100644
--- a/run-command.h
+++ b/run-command.h
@@ -221,7 +221,7 @@ int run_hook_ve(const char *const *env, const char *name, va_list args);
/*
* Trigger an auto-gc
*/
-int run_auto_gc(int quiet);
+int run_auto_maintenance(int quiet);
#define RUN_COMMAND_NO_STDIN 1
#define RUN_GIT_CMD 2 /*If this is to be git sub-command */
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 759aec9305..dbc724e4c0 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -936,7 +936,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '
git config fetch.unpackLimit 1 &&
git config gc.autoPackLimit 1 &&
git config gc.autoDetach false &&
- GIT_ASK_YESNO="$D/askyesno" git fetch >fetch.out 2>&1 &&
+ GIT_ASK_YESNO="$D/askyesno" git fetch --verbose >fetch.out 2>&1 &&
test_i18ngrep "Auto packing the repository" fetch.out &&
! grep "Should I try again" fetch.out
)
diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh
index de8e2f1531..bd202ec6f3 100755
--- a/t/t5514-fetch-multiple.sh
+++ b/t/t5514-fetch-multiple.sh
@@ -108,7 +108,7 @@ test_expect_success 'git fetch --multiple (two remotes)' '
GIT_TRACE=1 git fetch --multiple one two 2>trace &&
git branch -r > output &&
test_cmp ../expect output &&
- grep "built-in: git gc" trace >gc &&
+ grep "built-in: git maintenance" trace >gc &&
test_line_count = 1 gc
)
'
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
new file mode 100755
index 0000000000..53c883531e
--- /dev/null
+++ b/t/t7900-maintenance.sh
@@ -0,0 +1,65 @@
+#!/bin/sh
+
+test_description='git maintenance builtin'
+
+. ./test-lib.sh
+
+GIT_TEST_COMMIT_GRAPH=0
+
+test_expect_success 'help text' '
+ test_expect_code 129 git maintenance -h 2>err &&
+ test_i18ngrep "usage: git maintenance run" err &&
+ test_expect_code 128 git maintenance barf 2>err &&
+ test_i18ngrep "invalid subcommand: barf" err &&
+ test_expect_code 129 git maintenance 2>err &&
+ test_i18ngrep "usage: git maintenance" err
+'
+
+test_expect_success 'run [--auto|--quiet]' '
+ GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" \
+ git maintenance run 2>/dev/null &&
+ GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" \
+ git maintenance run --auto 2>/dev/null &&
+ GIT_TRACE2_EVENT="$(pwd)/run-no-quiet.txt" \
+ git maintenance run --no-quiet 2>/dev/null &&
+ test_subcommand git gc --quiet <run-no-auto.txt &&
+ test_subcommand ! git gc --auto --quiet <run-auto.txt &&
+ test_subcommand git gc --no-quiet <run-no-quiet.txt
+'
+
+test_expect_success 'maintenance.<task>.enabled' '
+ git config maintenance.gc.enabled false &&
+ git config maintenance.commit-graph.enabled true &&
+ GIT_TRACE2_EVENT="$(pwd)/run-config.txt" git maintenance run 2>err &&
+ test_subcommand ! git gc --quiet <run-config.txt &&
+ test_subcommand git commit-graph write --split --reachable --no-progress <run-config.txt
+'
+
+test_expect_success 'run --task=<task>' '
+ GIT_TRACE2_EVENT="$(pwd)/run-commit-graph.txt" \
+ git maintenance run --task=commit-graph 2>/dev/null &&
+ GIT_TRACE2_EVENT="$(pwd)/run-gc.txt" \
+ git maintenance run --task=gc 2>/dev/null &&
+ GIT_TRACE2_EVENT="$(pwd)/run-commit-graph.txt" \
+ git maintenance run --task=commit-graph 2>/dev/null &&
+ GIT_TRACE2_EVENT="$(pwd)/run-both.txt" \
+ git maintenance run --task=commit-graph --task=gc 2>/dev/null &&
+ test_subcommand ! git gc --quiet <run-commit-graph.txt &&
+ test_subcommand git gc --quiet <run-gc.txt &&
+ test_subcommand git gc --quiet <run-both.txt &&
+ test_subcommand git 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
+'
+
+test_expect_success 'run --task=bogus' '
+ test_must_fail git maintenance run --task=bogus 2>err &&
+ test_i18ngrep "is not a valid task" err
+'
+
+test_expect_success 'run --task duplicate' '
+ test_must_fail git maintenance run --task=gc --task=gc 2>err &&
+ test_i18ngrep "cannot be selected multiple times" err
+'
+
+test_done
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index f9904066fe..8d59b90348 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -1628,3 +1628,36 @@ test_path_is_hidden () {
case "$("$SYSTEMROOT"/system32/attrib "$1")" in *H*?:*) return 0;; esac
return 1
}
+
+# Check that the given command was invoked as part of the
+# trace2-format trace on stdin.
+#
+# test_subcommand [!] <command> <args>... < <trace>
+#
+# For example, to look for an invocation of "git upload-pack
+# /path/to/repo"
+#
+# GIT_TRACE2_EVENT=event.log git fetch ... &&
+# test_subcommand git upload-pack "$PATH" <event.log
+#
+# If the first parameter passed is !, this instead checks that
+# the given command was not called.
+#
+test_subcommand () {
+ local negate=
+ if test "$1" = "!"
+ then
+ negate=t
+ shift
+ fi
+
+ local expr=$(printf '"%s",' "$@")
+ expr="${expr%,}"
+
+ if test -n "$negate"
+ then
+ ! grep "\[$expr\]"
+ else
+ grep "\[$expr\]"
+ fi
+}