{"thread":{"id":"53997","subject":"[PATCH 06/11] maintenance: add --task option","startedAt":"2020-08-06T16:23:44Z","lastAt":"2020-09-21T19:30:02Z","messageCount":91,"participants":["Derrick Stolee via GitGitGadget","Martin Ågren","Derrick Stolee","Jonathan Nieder","Junio C Hamano","Jonathan Tan","Johannes Schindelin","Ævar Arnfjörð Bjarmason"],"isPatch":true,"patchVersion":1,"patchTotal":11},"messages":[{"id":"403034","messageId":"dddbcc4f3d28e2c00f153007b7db40733dbd572d.1596728921.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.git.1596728921.gitgitgadget@gmail.com","subject":"[PATCH 06/11] maintenance: add --task option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-06T15:48:36Z","receivedAt":"2020-08-06T16:23:44Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nA user may want to only run certain maintenance tasks in a certain\norder. Add the --task=<task> option, which allows a user to specify an\nordered list of tasks to run. These cannot be run multiple times,\nhowever.\n\nHere is where our array of maintenance_task pointers becomes critical.\nWe can sort the array of pointers based on the task order, but we do not\nwant to move the struct data itself in order to preserve the hashmap\nreferences. We use the hashmap to match the --task=<task> arguments into\nthe task struct data.\n\nKeep in mind that the 'enabled' member of the maintenance_task struct is\na placeholder for a future 'maintenance.<task>.enabled' config option.\nThus, we use the 'enabled' member to specify which tasks are run when\nthe user does not specify any --task=<task> arguments. The 'enabled'\nmember should be ignored if --task=<task> appears.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-maintenance.txt |  8 +++-\n builtin/gc.c                      | 61 +++++++++++++++++++++++++++++--\n t/t7900-maintenance.sh            | 27 ++++++++++++++\n 3 files changed, 92 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nindex 35b0be7d40..5911213c9c 100644\n--- a/Documentation/git-maintenance.txt\n+++ b/Documentation/git-maintenance.txt\n@@ -30,7 +30,9 @@ SUBCOMMANDS\n -----------\n \n run::\n-\tRun one or more maintenance tasks.\n+\tRun one or more maintenance tasks. If one or more `--task=<task>`\n+\toptions are specified, then those tasks are run in the provided\n+\torder. Otherwise, only the `gc` task is run.\n \n TASKS\n -----\n@@ -73,6 +75,10 @@ OPTIONS\n --quiet::\n \tDo not report progress or other information over `stderr`.\n \n+--task=<task>::\n+\tIf this option is specified one or more times, then only run the\n+\tspecified tasks in the specified order.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 3b7b914d60..3d50ab7ac9 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -791,7 +791,9 @@ typedef int maintenance_task_fn(struct maintenance_opts *opts);\n struct maintenance_task {\n \tconst char *name;\n \tmaintenance_task_fn *fn;\n-\tunsigned enabled:1;\n+\tunsigned enabled:1,\n+\t\t selected:1;\n+\tint selected_order;\n };\n \n enum maintenance_task_label {\n@@ -814,13 +816,32 @@ static struct maintenance_task tasks[] = {\n \t},\n };\n \n+static int compare_tasks_by_selection(const void *a_, const void *b_)\n+{\n+\tconst struct maintenance_task *a, *b;\n+\n+\ta = (const struct maintenance_task *)&a_;\n+\tb = (const struct maintenance_task *)&b_;\n+\n+\treturn b->selected_order - a->selected_order;\n+}\n+\n static int maintenance_run(struct maintenance_opts *opts)\n {\n-\tint i;\n+\tint i, found_selected = 0;\n \tint result = 0;\n \n+\tfor (i = 0; !found_selected && i < TASK__COUNT; i++)\n+\t\tfound_selected = tasks[i].selected;\n+\n+\tif (found_selected)\n+\t\tQSORT(tasks, TASK__COUNT, compare_tasks_by_selection);\n+\n \tfor (i = 0; i < TASK__COUNT; i++) {\n-\t\tif (!tasks[i].enabled)\n+\t\tif (found_selected && !tasks[i].selected)\n+\t\t\tcontinue;\n+\n+\t\tif (!found_selected && !tasks[i].enabled)\n \t\t\tcontinue;\n \n \t\tif (tasks[i].fn(opts)) {\n@@ -832,6 +853,37 @@ static int maintenance_run(struct maintenance_opts *opts)\n \treturn result;\n }\n \n+static int task_option_parse(const struct option *opt,\n+\t\t\t     const char *arg, int unset)\n+{\n+\tint i, num_selected = 0;\n+\tstruct maintenance_task *task = NULL;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tfor (i = 0; i < TASK__COUNT; i++) {\n+\t\tnum_selected += tasks[i].selected;\n+\t\tif (!strcasecmp(tasks[i].name, arg)) {\n+\t\t\ttask = &tasks[i];\n+\t\t}\n+\t}\n+\n+\tif (!task) {\n+\t\terror(_(\"'%s' is not a valid task\"), arg);\n+\t\treturn 1;\n+\t}\n+\n+\tif (task->selected) {\n+\t\terror(_(\"task '%s' cannot be selected multiple times\"), arg);\n+\t\treturn 1;\n+\t}\n+\n+\ttask->selected = 1;\n+\ttask->selected_order = num_selected + 1;\n+\n+\treturn 0;\n+}\n+\n int cmd_maintenance(int argc, const char **argv, const char *prefix)\n {\n \tstatic struct maintenance_opts opts;\n@@ -840,6 +892,9 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix)\n \t\t\t N_(\"run tasks based on the state of the repository\")),\n \t\tOPT_BOOL(0, \"quiet\", &opts.quiet,\n \t\t\t N_(\"do not report progress or other information over stderr\")),\n+\t\tOPT_CALLBACK_F(0, \"task\", NULL, N_(\"task\"),\n+\t\t\tN_(\"run a specific task\"),\n+\t\t\tPARSE_OPT_NONEG, task_option_parse),\n \t\tOPT_END()\n \t};\n \ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex 384294d111..bcc7131818 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -23,4 +23,31 @@ test_expect_success 'run [--auto|--quiet]' '\n \ttest_subcommand git gc --no-quiet <run-no-quiet.txt\n '\n \n+test_expect_success 'run --task=<task>' '\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-commit-graph.txt\" \\\n+\t\tgit maintenance run --task=commit-graph 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-gc.txt\" \\\n+\t\tgit maintenance run --task=gc 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-commit-graph.txt\" \\\n+\t\tgit maintenance run --task=commit-graph 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-both.txt\" \\\n+\t\tgit maintenance run --task=commit-graph --task=gc 2>/dev/null &&\n+\ttest_subcommand ! git gc --quiet <run-commit-graph.txt &&\n+\ttest_subcommand git gc --quiet <run-gc.txt &&\n+\ttest_subcommand git gc --quiet <run-both.txt &&\n+\ttest_subcommand git commit-graph write --split --reachable --no-progress <run-commit-graph.txt &&\n+\ttest_subcommand ! git commit-graph write --split --reachable --no-progress <run-gc.txt &&\n+\ttest_subcommand git commit-graph write --split --reachable --no-progress <run-both.txt\n+'\n+\n+test_expect_success 'run --task=bogus' '\n+\ttest_must_fail git maintenance run --task=bogus 2>err &&\n+\ttest_i18ngrep \"is not a valid task\" err\n+'\n+\n+test_expect_success 'run --task duplicate' '\n+\ttest_must_fail git maintenance run --task=gc --task=gc 2>err &&\n+\ttest_i18ngrep \"cannot be selected multiple times\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"403035","messageId":"69bfc6a4b2bb1a86788e69a2a3ac6134085aa798.1596728921.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.git.1596728921.gitgitgadget@gmail.com","subject":"[PATCH 08/11] maintenance: create maintenance.<task>.enabled config","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-06T15:48:38Z","receivedAt":"2020-08-06T16:27:51Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nCurrently, a normal run of \"git maintenance run\" will only run the 'gc'\ntask, as it is the only one enabled. This is mostly for backwards-\ncompatible reasons since \"git maintenance run --auto\" commands replaced\nprevious \"git gc --auto\" commands after some Git processes. Users could\nmanually run specific maintenance tasks by calling \"git maintenance run\n--task=<task>\" directly.\n\nAllow users to customize which steps are run automatically using config.\nThe 'maintenance.<task>.enabled' option then can turn on these other\ntasks (or turn off the 'gc' task).\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config.txt             |  2 ++\n Documentation/config/maintenance.txt |  4 ++++\n Documentation/git-maintenance.txt    |  8 +++++---\n builtin/gc.c                         | 19 +++++++++++++++++++\n t/t7900-maintenance.sh               |  8 ++++++++\n 5 files changed, 38 insertions(+), 3 deletions(-)\n create mode 100644 Documentation/config/maintenance.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex ef0768b91a..2783b825f9 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -396,6 +396,8 @@ include::config/mailinfo.txt[]\n \n include::config/mailmap.txt[]\n \n+include::config/maintenance.txt[]\n+\n include::config/man.txt[]\n \n include::config/merge.txt[]\ndiff --git a/Documentation/config/maintenance.txt b/Documentation/config/maintenance.txt\nnew file mode 100644\nindex 0000000000..370cbfb42f\n--- /dev/null\n+++ b/Documentation/config/maintenance.txt\n@@ -0,0 +1,4 @@\n+maintenance.<task>.enabled::\n+\tThis boolean config option controls whether the maintenance task\n+\twith name `<task>` is run when no `--task` option is specified.\n+\tBy default, only `maintenance.gc.enabled` is true.\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nindex 5911213c9c..e1a2a8902c 100644\n--- a/Documentation/git-maintenance.txt\n+++ b/Documentation/git-maintenance.txt\n@@ -30,9 +30,11 @@ SUBCOMMANDS\n -----------\n \n run::\n-\tRun one or more maintenance tasks. If one or more `--task=<task>`\n-\toptions are specified, then those tasks are run in the provided\n-\torder. Otherwise, only the `gc` task is run.\n+\tRun one or more maintenance tasks. If one or more `--task` options\n+\tare specified, then those tasks are run in that order. Otherwise,\n+\tthe tasks are determined by which `maintenance.<task>.enabled`\n+\tconfig options are true. By default, only `maintenance.gc.enabled`\n+\tis true.\n \n TASKS\n -----\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 1b7d502b87..dc1d260858 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -873,6 +873,24 @@ static int maintenance_run(struct maintenance_opts *opts)\n \treturn result;\n }\n \n+static void initialize_task_config(void)\n+{\n+\tint i;\n+\tstruct strbuf config_name = STRBUF_INIT;\n+\tfor (i = 0; i < TASK__COUNT; i++) {\n+\t\tint config_value;\n+\n+\t\tstrbuf_setlen(&config_name, 0);\n+\t\tstrbuf_addf(&config_name, \"maintenance.%s.enabled\",\n+\t\t\t    tasks[i].name);\n+\n+\t\tif (!git_config_get_bool(config_name.buf, &config_value))\n+\t\t\ttasks[i].enabled = config_value;\n+\t}\n+\n+\tstrbuf_release(&config_name);\n+}\n+\n static int task_option_parse(const struct option *opt,\n \t\t\t     const char *arg, int unset)\n {\n@@ -925,6 +943,7 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix)\n \t\t\t\t   builtin_maintenance_options);\n \n \topts.quiet = !isatty(2);\n+\tinitialize_task_config();\n \n \targc = parse_options(argc, argv, prefix,\n \t\t\t     builtin_maintenance_options,\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex bcc7131818..cdf0bf2e60 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -23,6 +23,14 @@ test_expect_success 'run [--auto|--quiet]' '\n \ttest_subcommand git gc --no-quiet <run-no-quiet.txt\n '\n \n+test_expect_success 'maintenance.<task>.enabled' '\n+\tgit config maintenance.gc.enabled false &&\n+\tgit config maintenance.commit-graph.enabled true &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-config.txt\" git maintenance run 2>err &&\n+\ttest_subcommand ! git gc --quiet <run-config.txt &&\n+\ttest_subcommand git commit-graph write --split --reachable --no-progress <run-config.txt\n+'\n+\n test_expect_success 'run --task=<task>' '\n \tGIT_TRACE2_EVENT=\"$(pwd)/run-commit-graph.txt\" \\\n \t\tgit maintenance run --task=commit-graph 2>/dev/null &&\n-- \ngitgitgadget\n\n"},{"id":"403040","messageId":"902b742032ae19087392538936cc81768a59e0e1.1596728921.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.git.1596728921.gitgitgadget@gmail.com","subject":"[PATCH 05/11] maintenance: add commit-graph task","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-06T15:48:35Z","receivedAt":"2020-08-06T16:48:04Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe first new task in the 'git maintenance' builtin is the\n'commit-graph' job. It is based on the sequence of events in the\n'commit-graph' job in Scalar [1]. This sequence is as follows:\n\n1. git commit-graph write --reachable --split\n2. git commit-graph verify --shallow\n3. If the verify succeeds, stop.\n4. Delete the commit-graph-chain file.\n5. git commit-graph write --reachable --split\n\nBy writing an incremental commit-graph file using the \"--split\"\noption we minimize the disruption from this operation. The default\nbehavior is to merge layers until the new \"top\" layer is less than\nhalf the size of the layer below. This provides quick writes most\nof the time, with the longer writes following a power law\ndistribution.\n\nMost importantly, concurrent Git processes only look at the\ncommit-graph-chain file for a very short amount of time, so they\nwill verly likely not be holding a handle to the file when we try\nto replace it. (This only matters on Windows.)\n\nIf a concurrent process reads the old commit-graph-chain file, but\nour job expires some of the .graph files before they can be read,\nthen those processes will see a warning message (but not fail).\nThis could be avoided by a future update to use the --expire-time\nargument when writing the commit-graph.\n\nBy using 'git commit-graph verify --shallow' we can ensure that\nthe file we just wrote is valid. This is an extra safety precaution\nthat is faster than our 'write' subcommand. In the rare situation\nthat the newest layer of the commit-graph is corrupt, we can \"fix\"\nthe corruption by deleting the commit-graph-chain file and rewrite\nthe full commit-graph as a new one-layer commit graph. This does\nnot completely prevent _that_ file from being corrupt, but it does\nrecompute the commit-graph by parsing commits from the object\ndatabase. In our use of this step in Scalar and VFS for Git, we\nhave only seen this issue arise because our microsoft/git fork\nreverted 43d3561 (\"commit-graph write: don't die if the existing\ngraph is corrupt\" 2019-03-25) for a while to keep commit-graph\nwrites very fast. We dropped the revert when updating to v2.23.0.\nThe verify still has potential for catching corrupt data across\nthe layer boundary: if the new file has commit X with parent Y\nin an old file but the commit ID for Y in the old file had a\nbitswap, then we will notice that in the 'verify' command.\n\n[1] https://github.com/microsoft/scalar/blob/master/Scalar.Common/Maintenance/CommitGraphStep.cs\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-maintenance.txt | 18 +++++++++\n builtin/gc.c                      | 63 +++++++++++++++++++++++++++++++\n commit-graph.c                    |  8 ++--\n commit-graph.h                    |  1 +\n t/t7900-maintenance.sh            |  2 +\n 5 files changed, 88 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nindex 089fa4cedc..35b0be7d40 100644\n--- a/Documentation/git-maintenance.txt\n+++ b/Documentation/git-maintenance.txt\n@@ -35,6 +35,24 @@ run::\n TASKS\n -----\n \n+commit-graph::\n+\tThe `commit-graph` job updates the `commit-graph` files incrementally,\n+\tthen verifies that the written data is correct. If the new layer has an\n+\tissue, then the chain file is removed and the `commit-graph` is\n+\trewritten from scratch.\n++\n+The verification only checks the top layer of the `commit-graph` chain.\n+If the incremental write merged the new commits with at least one\n+existing layer, then there is potential for on-disk corruption being\n+carried forward into the new file. This will be noticed and the new\n+commit-graph file will be clean as Git reparses the commit data from\n+the object database.\n++\n+The incremental write is safe to run alongside concurrent Git processes\n+since it will not expire `.graph` files that were in the previous\n+`commit-graph-chain` file. They will be deleted by a later run based on\n+the expiration delay.\n+\n gc::\n \tCleanup unnecessary files and optimize the local repository. \"GC\"\n \tstands for \"garbage collection,\" but this task performs many\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 150dce4301..3b7b914d60 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -710,6 +710,64 @@ struct maintenance_opts {\n \tint quiet;\n };\n \n+static int run_write_commit_graph(struct maintenance_opts *opts)\n+{\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\n+\tchild.git_cmd = 1;\n+\tstrvec_pushl(&child.args, \"commit-graph\", \"write\",\n+\t\t     \"--split\", \"--reachable\", NULL);\n+\n+\tif (opts->quiet)\n+\t\tstrvec_push(&child.args, \"--no-progress\");\n+\n+\treturn !!run_command(&child);\n+}\n+\n+static int run_verify_commit_graph(struct maintenance_opts *opts)\n+{\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\n+\tchild.git_cmd = 1;\n+\tstrvec_pushl(&child.args, \"commit-graph\", \"verify\",\n+\t\t     \"--shallow\", NULL);\n+\n+\tif (opts->quiet)\n+\t\tstrvec_push(&child.args, \"--no-progress\");\n+\n+\treturn !!run_command(&child);\n+}\n+\n+static int maintenance_task_commit_graph(struct maintenance_opts *opts)\n+{\n+\tstruct repository *r = the_repository;\n+\tchar *chain_path;\n+\n+\tclose_object_store(r->objects);\n+\tif (run_write_commit_graph(opts)) {\n+\t\terror(_(\"failed to write commit-graph\"));\n+\t\treturn 1;\n+\t}\n+\n+\tif (!run_verify_commit_graph(opts))\n+\t\treturn 0;\n+\n+\twarning(_(\"commit-graph verify caught error, rewriting\"));\n+\n+\tchain_path = get_commit_graph_chain_filename(r->objects->odb);\n+\tif (unlink(chain_path)) {\n+\t\tUNLEAK(chain_path);\n+\t\tdie(_(\"failed to remove commit-graph at %s\"), chain_path);\n+\t}\n+\tfree(chain_path);\n+\n+\tif (!run_write_commit_graph(opts))\n+\t\treturn 0;\n+\n+\terror(_(\"failed to rewrite commit-graph\"));\n+\treturn 1;\n+}\n+\n static int maintenance_task_gc(struct maintenance_opts *opts)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n@@ -738,6 +796,7 @@ struct maintenance_task {\n \n enum maintenance_task_label {\n \tTASK_GC,\n+\tTASK_COMMIT_GRAPH,\n \n \t/* Leave as final value */\n \tTASK__COUNT\n@@ -749,6 +808,10 @@ static struct maintenance_task tasks[] = {\n \t\tmaintenance_task_gc,\n \t\t1,\n \t},\n+\t[TASK_COMMIT_GRAPH] = {\n+\t\t\"commit-graph\",\n+\t\tmaintenance_task_commit_graph,\n+\t},\n };\n \n static int maintenance_run(struct maintenance_opts *opts)\ndiff --git a/commit-graph.c b/commit-graph.c\nindex 1af68c297d..9705d237e4 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -172,7 +172,7 @@ static char *get_split_graph_filename(struct object_directory *odb,\n \t\t       oid_hex);\n }\n \n-static char *get_chain_filename(struct object_directory *odb)\n+char *get_commit_graph_chain_filename(struct object_directory *odb)\n {\n \treturn xstrfmt(\"%s/info/commit-graphs/commit-graph-chain\", odb->path);\n }\n@@ -521,7 +521,7 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r,\n \tstruct stat st;\n \tstruct object_id *oids;\n \tint i = 0, valid = 1, count;\n-\tchar *chain_name = get_chain_filename(odb);\n+\tchar *chain_name = get_commit_graph_chain_filename(odb);\n \tFILE *fp;\n \tint stat_res;\n \n@@ -1619,7 +1619,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)\n \t}\n \n \tif (ctx->split) {\n-\t\tchar *lock_name = get_chain_filename(ctx->odb);\n+\t\tchar *lock_name = get_commit_graph_chain_filename(ctx->odb);\n \n \t\thold_lock_file_for_update_mode(&lk, lock_name,\n \t\t\t\t\t       LOCK_DIE_ON_ERROR, 0444);\n@@ -1996,7 +1996,7 @@ static void expire_commit_graphs(struct write_commit_graph_context *ctx)\n \tif (ctx->split_opts && ctx->split_opts->expire_time)\n \t\texpire_time = ctx->split_opts->expire_time;\n \tif (!ctx->split) {\n-\t\tchar *chain_file_name = get_chain_filename(ctx->odb);\n+\t\tchar *chain_file_name = get_commit_graph_chain_filename(ctx->odb);\n \t\tunlink(chain_file_name);\n \t\tfree(chain_file_name);\n \t\tctx->num_commit_graphs_after = 0;\ndiff --git a/commit-graph.h b/commit-graph.h\nindex 28f89cdf3e..3c202748c3 100644\n--- a/commit-graph.h\n+++ b/commit-graph.h\n@@ -25,6 +25,7 @@ struct commit;\n struct bloom_filter_settings;\n \n char *get_commit_graph_filename(struct object_directory *odb);\n+char *get_commit_graph_chain_filename(struct object_directory *odb);\n int open_commit_graph(const char *graph_file, int *fd, struct stat *st);\n \n /*\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex 7b63b4ec0c..384294d111 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -4,6 +4,8 @@ test_description='git maintenance builtin'\n \n . ./test-lib.sh\n \n+GIT_TEST_COMMIT_GRAPH=0\n+\n test_expect_success 'help text' '\n \ttest_expect_code 129 git maintenance -h 2>err &&\n \ttest_i18ngrep \"usage: git maintenance run\" err\n-- \ngitgitgadget\n\n"},{"id":"403044","messageId":"7efa23abc85bead9713c34d5f56b2f308dde7bf8.1596728921.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.git.1596728921.gitgitgadget@gmail.com","subject":"[PATCH 04/11] maintenance: initialize task array","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-06T15:48:34Z","receivedAt":"2020-08-06T16:52:02Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nIn anticipation of implementing multiple maintenance tasks inside the\n'maintenance' builtin, use a list of structs to describe the work to be\ndone.\n\nThe struct maintenance_task stores the name of the task (as given by a\nfuture command-line argument) along with a function pointer to its\nimplementation and a boolean for whether the step is enabled.\n\nA list these structs are initialized with the full list of implemented\ntasks along with a default order. For now, this list only contains the\n\"gc\" task. This task is also the only task enabled by default.\n\nThe run subcommand will return a nonzero exit code if any task fails.\nHowever, it will attempt all tasks in its loop before returning with the\nfailure. Also each failed task will send an error message.\n\nHelped-by: Taylor Blau <me@ttaylorr.com>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/gc.c | 38 +++++++++++++++++++++++++++++++++++++-\n 1 file changed, 37 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex a060ba8424..150dce4301 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -728,9 +728,45 @@ static int maintenance_task_gc(struct maintenance_opts *opts)\n \treturn run_command(&child);\n }\n \n+typedef int maintenance_task_fn(struct maintenance_opts *opts);\n+\n+struct maintenance_task {\n+\tconst char *name;\n+\tmaintenance_task_fn *fn;\n+\tunsigned enabled:1;\n+};\n+\n+enum maintenance_task_label {\n+\tTASK_GC,\n+\n+\t/* Leave as final value */\n+\tTASK__COUNT\n+};\n+\n+static struct maintenance_task tasks[] = {\n+\t[TASK_GC] = {\n+\t\t\"gc\",\n+\t\tmaintenance_task_gc,\n+\t\t1,\n+\t},\n+};\n+\n static int maintenance_run(struct maintenance_opts *opts)\n {\n-\treturn maintenance_task_gc(opts);\n+\tint i;\n+\tint result = 0;\n+\n+\tfor (i = 0; i < TASK__COUNT; i++) {\n+\t\tif (!tasks[i].enabled)\n+\t\t\tcontinue;\n+\n+\t\tif (tasks[i].fn(opts)) {\n+\t\t\terror(_(\"task '%s' failed\"), tasks[i].name);\n+\t\t\tresult = 1;\n+\t\t}\n+\t}\n+\n+\treturn result;\n }\n \n int cmd_maintenance(int argc, const char **argv, const char *prefix)\n-- \ngitgitgadget\n\n"},{"id":"403045","messageId":"e67e259aefddab76503be0963b5833ff2aa5cb43.1596728921.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.git.1596728921.gitgitgadget@gmail.com","subject":"[PATCH 10/11] maintenance: add auto condition for commit-graph task","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-06T15:48:40Z","receivedAt":"2020-08-06T16:53:35Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nInstead of writing a new commit-graph in every 'git maintenance run\n--auto' process (when maintenance.commit-graph.enalbed is configured to\nbe true), only write when there are \"enough\" commits not in a\ncommit-graph file.\n\nThis count is controlled by the maintenance.commit-graph.auto config\noption.\n\nTo compute the count, use a depth-first search starting at each ref, and\nleaving markers using the PARENT1 flag. If this count reaches the limit,\nthen terminate early and start the task. Otherwise, this operation will\npeel every ref and parse the commit it points to. If these are all in\nthe commit-graph, then this is typically a very fast operation. Users\nwith many refs might feel a slow-down, and hence could consider updating\ntheir limit to be very small. A negative value will force the step to\nrun every time.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/maintenance.txt | 10 ++++\n builtin/gc.c                         | 76 ++++++++++++++++++++++++++++\n object.h                             |  1 +\n 3 files changed, 87 insertions(+)\n\ndiff --git a/Documentation/config/maintenance.txt b/Documentation/config/maintenance.txt\nindex 370cbfb42f..9bd69b9df3 100644\n--- a/Documentation/config/maintenance.txt\n+++ b/Documentation/config/maintenance.txt\n@@ -2,3 +2,13 @@ maintenance.<task>.enabled::\n \tThis boolean config option controls whether the maintenance task\n \twith name `<task>` is run when no `--task` option is specified.\n \tBy default, only `maintenance.gc.enabled` is true.\n+\n+maintenance.commit-graph.auto::\n+\tThis integer config option controls how often the `commit-graph` task\n+\tshould be run as part of `git maintenance run --auto`. If zero, then\n+\tthe `commit-graph` task will not run with the `--auto` option. A\n+\tnegative value will force the task to run every time. Otherwise, a\n+\tpositive value implies the command should run when the number of\n+\treachable commits that are not in the commit-graph file is at least\n+\tthe value of `maintenance.commit-graph.auto`. The default value is\n+\t100.\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 2b3e45eb71..20d85f6f4e 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -28,6 +28,7 @@\n #include \"blob.h\"\n #include \"tree.h\"\n #include \"promisor-remote.h\"\n+#include \"refs.h\"\n \n #define FAILED_RUN \"failed to run %s\"\n \n@@ -710,6 +711,80 @@ struct maintenance_opts {\n \tint quiet;\n };\n \n+/* Remember to update object flag allocation in object.h */\n+#define PARENT1\t\t(1u<<16)\n+\n+static int num_commits_not_in_graph = 0;\n+static int limit_commits_not_in_graph = 100;\n+\n+static int dfs_on_ref(const char *refname,\n+\t\t      const struct object_id *oid, int flags,\n+\t\t      void *cb_data)\n+{\n+\tint result = 0;\n+\tstruct object_id peeled;\n+\tstruct commit_list *stack = NULL;\n+\tstruct commit *commit;\n+\n+\tif (!peel_ref(refname, &peeled))\n+\t\toid = &peeled;\n+\tif (oid_object_info(the_repository, oid, NULL) != OBJ_COMMIT)\n+\t\treturn 0;\n+\n+\tcommit = lookup_commit(the_repository, oid);\n+\tif (!commit)\n+\t\treturn 0;\n+\tif (parse_commit(commit))\n+\t\treturn 0;\n+\n+\tcommit_list_append(commit, &stack);\n+\n+\twhile (!result && stack) {\n+\t\tstruct commit_list *parent;\n+\n+\t\tcommit = pop_commit(&stack);\n+\n+\t\tfor (parent = commit->parents; parent; parent = parent->next) {\n+\t\t\tif (parse_commit(parent->item) ||\n+\t\t\t    commit_graph_position(parent->item) != COMMIT_NOT_FROM_GRAPH ||\n+\t\t\t    parent->item->object.flags & PARENT1)\n+\t\t\t\tcontinue;\n+\n+\t\t\tparent->item->object.flags |= PARENT1;\n+\t\t\tnum_commits_not_in_graph++;\n+\n+\t\t\tif (num_commits_not_in_graph >= limit_commits_not_in_graph) {\n+\t\t\t\tresult = 1;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tcommit_list_append(parent->item, &stack);\n+\t\t}\n+\t}\n+\n+\tfree_commit_list(stack);\n+\treturn result;\n+}\n+\n+static int should_write_commit_graph(void)\n+{\n+\tint result;\n+\n+\tgit_config_get_int(\"maintenance.commit-graph.auto\",\n+\t\t\t   &limit_commits_not_in_graph);\n+\n+\tif (!limit_commits_not_in_graph)\n+\t\treturn 0;\n+\tif (limit_commits_not_in_graph < 0)\n+\t\treturn 1;\n+\n+\tresult = for_each_ref(dfs_on_ref, NULL);\n+\n+\tclear_commit_marks_all(PARENT1);\n+\n+\treturn result;\n+}\n+\n static int run_write_commit_graph(struct maintenance_opts *opts)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n@@ -822,6 +897,7 @@ static struct maintenance_task tasks[] = {\n \t[TASK_COMMIT_GRAPH] = {\n \t\t\"commit-graph\",\n \t\tmaintenance_task_commit_graph,\n+\t\tshould_write_commit_graph,\n \t},\n };\n \ndiff --git a/object.h b/object.h\nindex 96a2105859..30d4e0f6a0 100644\n--- a/object.h\n+++ b/object.h\n@@ -74,6 +74,7 @@ struct object_array {\n  * list-objects-filter.c:                                      21\n  * builtin/fsck.c:           0--3\n  * builtin/index-pack.c:                                     2021\n+ * builtin/maintenance.c:                           16\n  * builtin/pack-objects.c:                                   20\n  * builtin/reflog.c:                   10--12\n  * builtin/show-branch.c:    0-------------------------------------------26\n-- \ngitgitgadget\n\n"},{"id":"403046","messageId":"a5d19148460decaf08e0e6293e996d42ff3f2d32.1596728921.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.git.1596728921.gitgitgadget@gmail.com","subject":"[PATCH 11/11] maintenance: add trace2 regions for task execution","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-06T15:48:41Z","receivedAt":"2020-08-06T16:54:36Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/gc.c | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 20d85f6f4e..8162bca974 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -953,10 +953,12 @@ static int maintenance_run(struct maintenance_opts *opts)\n \t\t     !tasks[i].auto_condition()))\n \t\t\tcontinue;\n \n+\t\ttrace2_region_enter(\"maintenance\", tasks[i].name, r);\n \t\tif (tasks[i].fn(opts)) {\n \t\t\terror(_(\"task '%s' failed\"), tasks[i].name);\n \t\t\tresult = 1;\n \t\t}\n+\t\ttrace2_region_leave(\"maintenance\", tasks[i].name, r);\n \t}\n \n \trollback_lock_file(&lk);\n-- \ngitgitgadget\n"},{"id":"403047","messageId":"d5faef26af8896dc7a82c2b809a3af2724da8afd.1596728921.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.git.1596728921.gitgitgadget@gmail.com","subject":"[PATCH 02/11] maintenance: add --quiet option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-06T15:48:32Z","receivedAt":"2020-08-06T16:54:44Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nMaintenance activities are commonly used as steps in larger scripts.\nProviding a '--quiet' option allows those scripts to be less noisy when\nrun on a terminal window. Turn this mode on by default when stderr is\nnot a terminal.\n\nPipe the option to the 'git gc' child process.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-maintenance.txt |  3 +++\n builtin/gc.c                      |  9 +++++++++\n t/t7900-maintenance.sh            | 15 ++++++++++-----\n 3 files changed, 22 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nindex 34cd2b4417..089fa4cedc 100644\n--- a/Documentation/git-maintenance.txt\n+++ b/Documentation/git-maintenance.txt\n@@ -52,6 +52,9 @@ OPTIONS\n \tin the `gc.auto` config setting, or when the number of pack-files\n \texceeds the `gc.autoPackLimit` config setting.\n \n+--quiet::\n+\tDo not report progress or other information over `stderr`.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex e4f0ce1c86..a060ba8424 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -707,6 +707,7 @@ static const char * const builtin_maintenance_usage[] = {\n \n struct maintenance_opts {\n \tint auto_flag;\n+\tint quiet;\n };\n \n static int maintenance_task_gc(struct maintenance_opts *opts)\n@@ -718,6 +719,10 @@ static int maintenance_task_gc(struct maintenance_opts *opts)\n \n \tif (opts->auto_flag)\n \t\tstrvec_push(&child.args, \"--auto\");\n+\tif (opts->quiet)\n+\t\tstrvec_push(&child.args, \"--quiet\");\n+\telse\n+\t\tstrvec_push(&child.args, \"--no-quiet\");\n \n \tclose_object_store(the_repository->objects);\n \treturn run_command(&child);\n@@ -734,6 +739,8 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix)\n \tstatic struct option builtin_maintenance_options[] = {\n \t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n \t\t\t N_(\"run tasks based on the state of the repository\")),\n+\t\tOPT_BOOL(0, \"quiet\", &opts.quiet,\n+\t\t\t N_(\"do not report progress or other information over stderr\")),\n \t\tOPT_END()\n \t};\n \n@@ -743,6 +750,8 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(builtin_maintenance_usage,\n \t\t\t\t   builtin_maintenance_options);\n \n+\topts.quiet = !isatty(2);\n+\n \targc = parse_options(argc, argv, prefix,\n \t\t\t     builtin_maintenance_options,\n \t\t\t     builtin_maintenance_usage,\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex c4b9b4a6fe..7b63b4ec0c 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -9,11 +9,16 @@ test_expect_success 'help text' '\n \ttest_i18ngrep \"usage: git maintenance run\" err\n '\n \n-test_expect_success 'run [--auto]' '\n-\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-auto.txt\" git maintenance run &&\n-\tGIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" git maintenance run --auto &&\n-\ttest_subcommand git gc <run-no-auto.txt &&\n-\ttest_subcommand git gc --auto <run-auto.txt\n+test_expect_success 'run [--auto|--quiet]' '\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-auto.txt\" \\\n+\t\tgit maintenance run 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" \\\n+\t\tgit maintenance run --auto 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-quiet.txt\" \\\n+\t\tgit maintenance run --no-quiet 2>/dev/null &&\n+\ttest_subcommand git gc --quiet <run-no-auto.txt &&\n+\ttest_subcommand git gc --auto --quiet <run-auto.txt &&\n+\ttest_subcommand git gc --no-quiet <run-no-quiet.txt\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"403048","messageId":"pull.695.git.1596728921.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":null,"subject":"[PATCH 00/11] Maintenance I: Command, gc and commit-graph tasks","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-06T15:48:30Z","receivedAt":"2020-08-06T16:55:37Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"This series is based on jk/strvec.\n\nThis patch series contains 11patches that were going to be part of v4 of\nds/maintenance [1], but the discussion has gotten really long. To help focus\nthe conversation, I'm splitting out the portions that create and test the\n'maintenance' builtin from the additional tasks (prefetch, loose-objects,\nincremental-repack) that can be brought in later.\n\n[1] \nhttps://lore.kernel.org/git/pull.671.git.1594131695.gitgitgadget@gmail.com/\n\nAs mentioned before, git gc already plays the role of maintaining Git\nrepositories. It has accumulated several smaller pieces in its long history,\nincluding:\n\n 1. Repacking all reachable objects into one pack-file (and deleting\n    unreachable objects).\n 2. Packing refs.\n 3. Expiring reflogs.\n 4. Clearing rerere logs.\n 5. Updating the commit-graph file.\n 6. Pruning worktrees.\n\nWhile expiring reflogs, clearing rererelogs, and deleting unreachable\nobjects are suitable under the guise of \"garbage collection\", packing refs\nand updating the commit-graph file are not as obviously fitting. Further,\nthese operations are \"all or nothing\" in that they rewrite almost all\nrepository data, which does not perform well at extremely large scales.\nThese operations can also be disruptive to foreground Git commands when git\ngc --auto triggers during routine use.\n\nThis series does not intend to change what git gc does, but instead create\nnew choices for automatic maintenance activities, of which git gc remains\nthe only one enabled by default.\n\nThe new maintenance tasks are:\n\n * 'commit-graph' : write and verify a single layer of an incremental\n   commit-graph.\n * 'loose-objects' : prune packed loose objects, then create a new pack from\n   a batch of loose objects.\n * 'pack-files' : expire redundant packs from the multi-pack-index, then\n   repack using the multi-pack-index's incremental repack strategy.\n * 'prefetch' : fetch from each remote, storing the refs in 'refs/prefetch/\n   /'.\n\nThe only included tasks are the 'gc' and 'commit-graph' tasks. The rest will\nfollow in a follow-up series. Including the 'commit-graph' task here allows\nus to build and test features like config settings and the --task= \ncommand-line argument.\n\nThese tasks are all disabled by default, but can be enabled with config\noptions or run explicitly using \"git maintenance run --task=\". There are\nadditional config options to allow customizing the conditions for which the\ntasks run during the '--auto' option.\n\n Because 'gc' is implemented as a maintenance task, the most dramatic change\nof this series is to convert the 'git gc --auto' calls into 'git maintenance\nrun --auto' calls at the end of some Git commands. By default, the only\nchange is that 'git gc --auto' will be run below an additional 'git\nmaintenance' process.\n\nThe 'git maintenance' builtin has a 'run' subcommand so it can be extended\nlater with subcommands that manage background maintenance, such as 'start'\nor 'stop'. These are not the subject of this series, as it is important to\nfocus on the maintenance activities themselves.\n\nUPDATES since v3 of [1]\n=======================\n\n * The biggest change here is the use of \"test_subcommand\", based on\n   Jonathan Nieder's approach. This requires having the exact command-line\n   figured out, which now requires spelling out all --no- [quiet%7Cprogress] \n   options. I also added a bunch of \"2>/dev/null\" checks because of the\n   isatty(2) calls. Without that, the behavior will change depending on\n   whether the test is run with -x/-v or without.\n   \n   \n * The option parsing has changed to use a local struct and pass that struct\n   to the helper methods. This is instead of having a global singleton.\n   \n   \n\nThanks, -Stolee\n\nHere is the range-diff from the v3 of [1].\n\n 1:  12fe73bb72 !  1:  2b9deb6d6a maintenance: create basic maintenance runner\n    @@ Commit message\n         'gc' builtin entirely at some point, leaving 'git gc' as an alias for\n         some specific arguments to 'git maintenance run'.\n\n    +    Create a new test_subcommand helper that allows us to test if a certain\n    +    subcommand was run. It requires storing the GIT_TRACE2_EVENT logs in a\n    +    file. A negation mode is available that will be used in later tests.\n    +\n    +    Helped-by: Jonathan Nieder <jrnieder@gmail.com>\n         Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n\n      ## .gitignore ##\n    @@ builtin/gc.c: int cmd_gc(int argc, const char **argv, const char *prefix)\n     +    NULL\n     +};\n     +\n    -+static struct maintenance_opts {\n    ++struct maintenance_opts {\n     +    int auto_flag;\n    -+} opts;\n    ++};\n     +\n    -+static int maintenance_task_gc(void)\n    ++static int maintenance_task_gc(struct maintenance_opts *opts)\n     +{\n     +    struct child_process child = CHILD_PROCESS_INIT;\n     +\n     +    child.git_cmd = 1;\n     +    strvec_push(&child.args, \"gc\");\n     +\n    -+    if (opts.auto_flag)\n    ++    if (opts->auto_flag)\n     +        strvec_push(&child.args, \"--auto\");\n     +\n     +    close_object_store(the_repository->objects);\n     +    return run_command(&child);\n     +}\n     +\n    -+static int maintenance_run(void)\n    ++static int maintenance_run(struct maintenance_opts *opts)\n     +{\n    -+    return maintenance_task_gc();\n    ++    return maintenance_task_gc(opts);\n     +}\n     +\n     +int cmd_maintenance(int argc, const char **argv, const char *prefix)\n     +{\n    ++    static struct maintenance_opts opts;\n     +    static struct option builtin_maintenance_options[] = {\n     +        OPT_BOOL(0, \"auto\", &opts.auto_flag,\n     +             N_(\"run tasks based on the state of the repository\")),\n    @@ builtin/gc.c: int cmd_gc(int argc, const char **argv, const char *prefix)\n     +\n     +    if (argc == 1) {\n     +        if (!strcmp(argv[0], \"run\"))\n    -+            return maintenance_run();\n    ++            return maintenance_run(&opts);\n     +    }\n     +\n     +    usage_with_options(builtin_maintenance_usage,\n    @@ t/t7900-maintenance.sh (new)\n     +test_expect_success 'run [--auto]' '\n     +    GIT_TRACE2_EVENT=\"$(pwd)/run-no-auto.txt\" git maintenance run &&\n     +    GIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" git maintenance run --auto &&\n    -+    grep \",\\\"gc\\\"]\" run-no-auto.txt  &&\n    -+    grep \",\\\"gc\\\",\\\"--auto\\\"]\" run-auto.txt\n    ++    test_subcommand git gc <run-no-auto.txt &&\n    ++    test_subcommand git gc --auto <run-auto.txt\n     +'\n     +\n     +test_done\n    +\n    + ## t/test-lib-functions.sh ##\n    +@@ t/test-lib-functions.sh: test_path_is_hidden () {\n    +     case \"$(\"$SYSTEMROOT\"/system32/attrib \"$1\")\" in *H*?:*) return 0;; esac\n    +     return 1\n    + }\n    ++\n    ++# Check that the given command was invoked as part of the\n    ++# trace2-format trace on stdin.\n    ++#\n    ++#    test_subcommand [!] <command> <args>... < <trace>\n    ++#\n    ++# For example, to look for an invocation of \"git upload-pack\n    ++# /path/to/repo\"\n    ++#\n    ++#    GIT_TRACE2_EVENT=event.log git fetch ... &&\n    ++#    test_subcommand git upload-pack \"$PATH\" <event.log\n    ++#\n    ++# If the first parameter passed is !, this instead checks that\n    ++# the given command was not called.\n    ++#\n    ++test_subcommand () {\n    ++    local negate=\n    ++    if test \"$1\" = \"!\"\n    ++    then\n    ++        negate=t\n    ++        shift\n    ++    fi\n    ++\n    ++    local expr=$(printf '\"%s\",' \"$@\")\n    ++    expr=\"${expr%,}\"\n    ++\n    ++    if test -n \"$negate\"\n    ++    then\n    ++        ! grep \"\\[$expr\\]\"\n    ++    else\n    ++        grep \"\\[$expr\\]\"\n    ++    fi\n    ++}\n 2:  6e533e43d7 !  2:  d5faef26af maintenance: add --quiet option\n    @@ Documentation/git-maintenance.txt: OPTIONS\n      ## builtin/gc.c ##\n     @@ builtin/gc.c: static const char * const builtin_maintenance_usage[] = {\n\n    - static struct maintenance_opts {\n    + struct maintenance_opts {\n          int auto_flag;\n     +    int quiet;\n    - } opts;\n    + };\n\n    - static int maintenance_task_gc(void)\n    -@@ builtin/gc.c: static int maintenance_task_gc(void)\n    + static int maintenance_task_gc(struct maintenance_opts *opts)\n    +@@ builtin/gc.c: static int maintenance_task_gc(struct maintenance_opts *opts)\n\n    -     if (opts.auto_flag)\n    +     if (opts->auto_flag)\n              strvec_push(&child.args, \"--auto\");\n    -+    if (opts.quiet)\n    ++    if (opts->quiet)\n     +        strvec_push(&child.args, \"--quiet\");\n    ++    else\n    ++        strvec_push(&child.args, \"--no-quiet\");\n\n          close_object_store(the_repository->objects);\n          return run_command(&child);\n    @@ t/t7900-maintenance.sh: test_expect_success 'help text' '\n\n     -test_expect_success 'run [--auto]' '\n     -    GIT_TRACE2_EVENT=\"$(pwd)/run-no-auto.txt\" git maintenance run &&\n    +-    GIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" git maintenance run --auto &&\n    +-    test_subcommand git gc <run-no-auto.txt &&\n    +-    test_subcommand git gc --auto <run-auto.txt\n     +test_expect_success 'run [--auto|--quiet]' '\n    -+    GIT_TRACE2_EVENT=\"$(pwd)/run-no-auto.txt\" git maintenance run --no-quiet &&\n    -     GIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" git maintenance run --auto &&\n    -+    GIT_TRACE2_EVENT=\"$(pwd)/run-quiet.txt\" git maintenance run --quiet &&\n    -     grep \",\\\"gc\\\"]\" run-no-auto.txt  &&\n    --    grep \",\\\"gc\\\",\\\"--auto\\\"]\" run-auto.txt\n    -+    grep \",\\\"gc\\\",\\\"--auto\\\"\" run-auto.txt &&\n    -+    grep \",\\\"gc\\\",\\\"--quiet\\\"\" run-quiet.txt\n    ++    GIT_TRACE2_EVENT=\"$(pwd)/run-no-auto.txt\" \\\n    ++        git maintenance run 2>/dev/null &&\n    ++    GIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" \\\n    ++        git maintenance run --auto 2>/dev/null &&\n    ++    GIT_TRACE2_EVENT=\"$(pwd)/run-no-quiet.txt\" \\\n    ++        git maintenance run --no-quiet 2>/dev/null &&\n    ++    test_subcommand git gc --quiet <run-no-auto.txt &&\n    ++    test_subcommand git gc --auto --quiet <run-auto.txt &&\n    ++    test_subcommand git gc --no-quiet <run-no-quiet.txt\n      '\n\n      test_done\n 3:  c4674fc211 !  3:  233811310b maintenance: replace run_auto_gc()\n    @@ run-command.c: int run_processes_parallel_tr2(int n, get_next_task_fn get_next_t\n     -    strvec_pushl(&argv_gc_auto, \"gc\", \"--auto\", NULL);\n     -    if (quiet)\n     -        strvec_push(&argv_gc_auto, \"--quiet\");\n    --    status = run_command_v_opt(argv_gc_auto.items, RUN_GIT_CMD);\n    +-    status = run_command_v_opt(argv_gc_auto.v, RUN_GIT_CMD);\n     -    strvec_clear(&argv_gc_auto);\n     -    return status;\n     +    maint.git_cmd = 1;\n 4:  b9332c1318 !  4:  7efa23abc8 maintenance: initialize task array\n    @@ Commit message\n         Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n\n      ## builtin/gc.c ##\n    -@@ builtin/gc.c: static int maintenance_task_gc(void)\n    +@@ builtin/gc.c: static int maintenance_task_gc(struct maintenance_opts *opts)\n          return run_command(&child);\n      }\n\n    -+typedef int maintenance_task_fn(void);\n    ++typedef int maintenance_task_fn(struct maintenance_opts *opts);\n     +\n     +struct maintenance_task {\n     +    const char *name;\n    @@ builtin/gc.c: static int maintenance_task_gc(void)\n     +    },\n     +};\n     +\n    - static int maintenance_run(void)\n    + static int maintenance_run(struct maintenance_opts *opts)\n      {\n    --    return maintenance_task_gc();\n    +-    return maintenance_task_gc(opts);\n     +    int i;\n     +    int result = 0;\n     +\n    @@ builtin/gc.c: static int maintenance_task_gc(void)\n     +        if (!tasks[i].enabled)\n     +            continue;\n     +\n    -+        if (tasks[i].fn()) {\n    ++        if (tasks[i].fn(opts)) {\n     +            error(_(\"task '%s' failed\"), tasks[i].name);\n     +            result = 1;\n     +        }\n 5:  a4d9836bed !  5:  902b742032 maintenance: add commit-graph task\n    @@ Documentation/git-maintenance.txt: run::\n          stands for \"garbage collection,\" but this task performs many\n\n      ## builtin/gc.c ##\n    -@@ builtin/gc.c: static struct maintenance_opts {\n    +@@ builtin/gc.c: struct maintenance_opts {\n          int quiet;\n    - } opts;\n    + };\n\n    -+static int run_write_commit_graph(void)\n    ++static int run_write_commit_graph(struct maintenance_opts *opts)\n     +{\n     +    struct child_process child = CHILD_PROCESS_INIT;\n     +\n    @@ builtin/gc.c: static struct maintenance_opts {\n     +    strvec_pushl(&child.args, \"commit-graph\", \"write\",\n     +             \"--split\", \"--reachable\", NULL);\n     +\n    -+    if (opts.quiet)\n    ++    if (opts->quiet)\n     +        strvec_push(&child.args, \"--no-progress\");\n     +\n     +    return !!run_command(&child);\n     +}\n     +\n    -+static int run_verify_commit_graph(void)\n    ++static int run_verify_commit_graph(struct maintenance_opts *opts)\n     +{\n     +    struct child_process child = CHILD_PROCESS_INIT;\n     +\n    @@ builtin/gc.c: static struct maintenance_opts {\n     +    strvec_pushl(&child.args, \"commit-graph\", \"verify\",\n     +             \"--shallow\", NULL);\n     +\n    -+    if (opts.quiet)\n    ++    if (opts->quiet)\n     +        strvec_push(&child.args, \"--no-progress\");\n     +\n     +    return !!run_command(&child);\n     +}\n     +\n    -+static int maintenance_task_commit_graph(void)\n    ++static int maintenance_task_commit_graph(struct maintenance_opts *opts)\n     +{\n     +    struct repository *r = the_repository;\n     +    char *chain_path;\n     +\n     +    close_object_store(r->objects);\n    -+    if (run_write_commit_graph()) {\n    ++    if (run_write_commit_graph(opts)) {\n     +        error(_(\"failed to write commit-graph\"));\n     +        return 1;\n     +    }\n     +\n    -+    if (!run_verify_commit_graph())\n    ++    if (!run_verify_commit_graph(opts))\n     +        return 0;\n     +\n     +    warning(_(\"commit-graph verify caught error, rewriting\"));\n    @@ builtin/gc.c: static struct maintenance_opts {\n     +    }\n     +    free(chain_path);\n     +\n    -+    if (!run_write_commit_graph())\n    ++    if (!run_write_commit_graph(opts))\n     +        return 0;\n     +\n     +    error(_(\"failed to rewrite commit-graph\"));\n     +    return 1;\n     +}\n     +\n    - static int maintenance_task_gc(void)\n    + static int maintenance_task_gc(struct maintenance_opts *opts)\n      {\n          struct child_process child = CHILD_PROCESS_INIT;\n     @@ builtin/gc.c: struct maintenance_task {\n    @@ builtin/gc.c: static struct maintenance_task tasks[] = {\n     +    },\n      };\n\n    - static int maintenance_run(void)\n    + static int maintenance_run(struct maintenance_opts *opts)\n\n      ## commit-graph.c ##\n     @@ commit-graph.c: static char *get_split_graph_filename(struct object_directory *odb,\n 6:  dafb0d9bbc !  6:  dddbcc4f3d maintenance: add --task option\n    @@ Commit message\n         Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n\n      ## Documentation/git-maintenance.txt ##\n    +@@ Documentation/git-maintenance.txt: SUBCOMMANDS\n    + -----------\n    + \n    + run::\n    +-    Run one or more maintenance tasks.\n    ++    Run one or more maintenance tasks. If one or more `--task=<task>`\n    ++    options are specified, then those tasks are run in the provided\n    ++    order. Otherwise, only the `gc` task is run.\n    + \n    + TASKS\n    + -----\n     @@ Documentation/git-maintenance.txt: OPTIONS\n      --quiet::\n          Do not report progress or other information over `stderr`.\n    @@ Documentation/git-maintenance.txt: OPTIONS\n      Part of the linkgit:git[1] suite\n\n      ## builtin/gc.c ##\n    -@@ builtin/gc.c: static const char * const builtin_maintenance_usage[] = {\n    - static struct maintenance_opts {\n    -     int auto_flag;\n    -     int quiet;\n    -+    int tasks_selected;\n    - } opts;\n    - \n    - static int run_write_commit_graph(void)\n    -@@ builtin/gc.c: typedef int maintenance_task_fn(void);\n    +@@ builtin/gc.c: typedef int maintenance_task_fn(struct maintenance_opts *opts);\n      struct maintenance_task {\n          const char *name;\n          maintenance_task_fn *fn;\n    @@ builtin/gc.c: static struct maintenance_task tasks[] = {\n     +    return b->selected_order - a->selected_order;\n     +}\n     +\n    - static int maintenance_run(void)\n    + static int maintenance_run(struct maintenance_opts *opts)\n      {\n    -     int i;\n    +-    int i;\n    ++    int i, found_selected = 0;\n          int result = 0;\n\n    -+    if (opts.tasks_selected)\n    ++    for (i = 0; !found_selected && i < TASK__COUNT; i++)\n    ++        found_selected = tasks[i].selected;\n    ++\n    ++    if (found_selected)\n     +        QSORT(tasks, TASK__COUNT, compare_tasks_by_selection);\n     +\n          for (i = 0; i < TASK__COUNT; i++) {\n     -        if (!tasks[i].enabled)\n    -+        if (opts.tasks_selected && !tasks[i].selected)\n    ++        if (found_selected && !tasks[i].selected)\n     +            continue;\n     +\n    -+        if (!opts.tasks_selected && !tasks[i].enabled)\n    ++        if (!found_selected && !tasks[i].enabled)\n                  continue;\n\n    -         if (tasks[i].fn()) {\n    -@@ builtin/gc.c: static int maintenance_run(void)\n    +         if (tasks[i].fn(opts)) {\n    +@@ builtin/gc.c: static int maintenance_run(struct maintenance_opts *opts)\n          return result;\n      }\n\n     +static int task_option_parse(const struct option *opt,\n     +                 const char *arg, int unset)\n     +{\n    -+    int i;\n    ++    int i, num_selected = 0;\n     +    struct maintenance_task *task = NULL;\n     +\n     +    BUG_ON_OPT_NEG(unset);\n     +\n    -+    opts.tasks_selected++;\n    -+\n     +    for (i = 0; i < TASK__COUNT; i++) {\n    ++        num_selected += tasks[i].selected;\n     +        if (!strcasecmp(tasks[i].name, arg)) {\n     +            task = &tasks[i];\n    -+            break;\n     +        }\n     +    }\n     +\n    @@ builtin/gc.c: static int maintenance_run(void)\n     +    }\n     +\n     +    task->selected = 1;\n    -+    task->selected_order = opts.tasks_selected;\n    ++    task->selected_order = num_selected + 1;\n     +\n     +    return 0;\n     +}\n     +\n      int cmd_maintenance(int argc, const char **argv, const char *prefix)\n      {\n    -     static struct option builtin_maintenance_options[] = {\n    +     static struct maintenance_opts opts;\n     @@ builtin/gc.c: int cmd_maintenance(int argc, const char **argv, const char *prefix)\n                   N_(\"run tasks based on the state of the repository\")),\n              OPT_BOOL(0, \"quiet\", &opts.quiet,\n    @@ builtin/gc.c: int cmd_maintenance(int argc, const char **argv, const char *prefi\n\n      ## t/t7900-maintenance.sh ##\n     @@ t/t7900-maintenance.sh: test_expect_success 'run [--auto|--quiet]' '\n    -     grep \",\\\"gc\\\",\\\"--quiet\\\"\" run-quiet.txt\n    +     test_subcommand git gc --no-quiet <run-no-quiet.txt\n      '\n\n     +test_expect_success 'run --task=<task>' '\n    -+    GIT_TRACE2_EVENT=\"$(pwd)/run-commit-graph.txt\" git maintenance run --task=commit-graph &&\n    -+    GIT_TRACE2_EVENT=\"$(pwd)/run-gc.txt\" git maintenance run --task=gc &&\n    -+    GIT_TRACE2_EVENT=\"$(pwd)/run-commit-graph.txt\" git maintenance run --task=commit-graph &&\n    -+    GIT_TRACE2_EVENT=\"$(pwd)/run-both.txt\" git maintenance run --task=commit-graph --task=gc &&\n    -+    ! grep \",\\\"gc\\\"\" run-commit-graph.txt  &&\n    -+    grep \",\\\"gc\\\"\" run-gc.txt  &&\n    -+    grep \",\\\"gc\\\"\" run-both.txt  &&\n    -+    grep \",\\\"commit-graph\\\",\\\"write\\\"\" run-commit-graph.txt  &&\n    -+    ! grep \",\\\"commit-graph\\\",\\\"write\\\"\" run-gc.txt  &&\n    -+    grep \",\\\"commit-graph\\\",\\\"write\\\"\" run-both.txt\n    ++    GIT_TRACE2_EVENT=\"$(pwd)/run-commit-graph.txt\" \\\n    ++        git maintenance run --task=commit-graph 2>/dev/null &&\n    ++    GIT_TRACE2_EVENT=\"$(pwd)/run-gc.txt\" \\\n    ++        git maintenance run --task=gc 2>/dev/null &&\n    ++    GIT_TRACE2_EVENT=\"$(pwd)/run-commit-graph.txt\" \\\n    ++        git maintenance run --task=commit-graph 2>/dev/null &&\n    ++    GIT_TRACE2_EVENT=\"$(pwd)/run-both.txt\" \\\n    ++        git maintenance run --task=commit-graph --task=gc 2>/dev/null &&\n    ++    test_subcommand ! git gc --quiet <run-commit-graph.txt &&\n    ++    test_subcommand git gc --quiet <run-gc.txt &&\n    ++    test_subcommand git gc --quiet <run-both.txt &&\n    ++    test_subcommand git commit-graph write --split --reachable --no-progress <run-commit-graph.txt &&\n    ++    test_subcommand ! git commit-graph write --split --reachable --no-progress <run-gc.txt &&\n    ++    test_subcommand git commit-graph write --split --reachable --no-progress <run-both.txt\n     +'\n     +\n     +test_expect_success 'run --task=bogus' '\n 7:  1b00524da3 !  7:  79af39be13 maintenance: take a lock on the objects directory\n    @@ Commit message\n         Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n\n      ## builtin/gc.c ##\n    -@@ builtin/gc.c: static int maintenance_run(void)\n    +@@ builtin/gc.c: static int maintenance_run(struct maintenance_opts *opts)\n      {\n    -     int i;\n    +     int i, found_selected = 0;\n          int result = 0;\n     +    struct lock_file lk;\n     +    struct repository *r = the_repository;\n    @@ builtin/gc.c: static int maintenance_run(void)\n     +         * recursive process stack. Do not report an error in\n     +         * that case.\n     +         */\n    -+        if (!opts.auto_flag && !opts.quiet)\n    ++        if (!opts->auto_flag && !opts->quiet)\n     +            error(_(\"lock file '%s' exists, skipping maintenance\"),\n     +                  lock_path);\n     +        free(lock_path);\n    @@ builtin/gc.c: static int maintenance_run(void)\n     +    }\n     +    free(lock_path);\n\n    -     if (opts.tasks_selected)\n    -         QSORT(tasks, TASK__COUNT, compare_tasks_by_selection);\n    -@@ builtin/gc.c: static int maintenance_run(void)\n    +     for (i = 0; !found_selected && i < TASK__COUNT; i++)\n    +         found_selected = tasks[i].selected;\n    +@@ builtin/gc.c: static int maintenance_run(struct maintenance_opts *opts)\n              }\n          }\n\n 8:  0e94e04dcd <  -:  ---------- fetch: optionally allow disabling FETCH_HEAD update\n 9:  9e38ade15c <  -:  ---------- maintenance: add prefetch task\n10:  0128fdfd1a <  -:  ---------- maintenance: add loose-objects task\n11:  c2baf6e119 <  -:  ---------- midx: enable core.multiPackIndex by default\n12:  00f47c4848 <  -:  ---------- maintenance: add incremental-repack task\n13:  ef2a231956 <  -:  ---------- maintenance: auto-size incremental-repack batch\n14:  99840c4b8f !  8:  69bfc6a4b2 maintenance: create maintenance.<task>.enabled config\n    @@ Documentation/git-maintenance.txt: SUBCOMMANDS\n      -----------\n\n      run::\n    --    Run one or more maintenance tasks.\n    +-    Run one or more maintenance tasks. If one or more `--task=<task>`\n    +-    options are specified, then those tasks are run in the provided\n    +-    order. Otherwise, only the `gc` task is run.\n     +    Run one or more maintenance tasks. If one or more `--task` options\n     +    are specified, then those tasks are run in that order. Otherwise,\n     +    the tasks are determined by which `maintenance.<task>.enabled`\n    @@ Documentation/git-maintenance.txt: SUBCOMMANDS\n      -----\n\n      ## builtin/gc.c ##\n    -@@ builtin/gc.c: static int maintenance_run(void)\n    +@@ builtin/gc.c: static int maintenance_run(struct maintenance_opts *opts)\n          return result;\n      }\n\n    @@ builtin/gc.c: int cmd_maintenance(int argc, const char **argv, const char *prefi\n\n      ## t/t7900-maintenance.sh ##\n     @@ t/t7900-maintenance.sh: test_expect_success 'run [--auto|--quiet]' '\n    -     grep \",\\\"gc\\\",\\\"--quiet\\\"\" run-quiet.txt\n    +     test_subcommand git gc --no-quiet <run-no-quiet.txt\n      '\n\n     +test_expect_success 'maintenance.<task>.enabled' '\n     +    git config maintenance.gc.enabled false &&\n     +    git config maintenance.commit-graph.enabled true &&\n    -+    git config maintenance.loose-objects.enabled true &&\n    -+    GIT_TRACE2_EVENT=\"$(pwd)/run-config.txt\" git maintenance run &&\n    -+    ! grep \",\\\"fetch\\\"\" run-config.txt &&\n    -+    ! grep \",\\\"gc\\\"\" run-config.txt &&\n    -+    ! grep \",\\\"multi-pack-index\\\"\" run-config.txt &&\n    -+    grep \",\\\"commit-graph\\\"\" run-config.txt &&\n    -+    grep \",\\\"prune-packed\\\"\" run-config.txt\n    ++    GIT_TRACE2_EVENT=\"$(pwd)/run-config.txt\" git maintenance run 2>err &&\n    ++    test_subcommand ! git gc --quiet <run-config.txt &&\n    ++    test_subcommand git commit-graph write --split --reachable --no-progress <run-config.txt\n     +'\n     +\n      test_expect_success 'run --task=<task>' '\n    -     GIT_TRACE2_EVENT=\"$(pwd)/run-commit-graph.txt\" git maintenance run --task=commit-graph &&\n    -     GIT_TRACE2_EVENT=\"$(pwd)/run-gc.txt\" git maintenance run --task=gc &&\n    +     GIT_TRACE2_EVENT=\"$(pwd)/run-commit-graph.txt\" \\\n    +         git maintenance run --task=commit-graph 2>/dev/null &&\n15:  a087c63572 !  9:  df21bbb000 maintenance: use pointers to check --auto\n    @@ Commit message\n         Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n\n      ## builtin/gc.c ##\n    -@@ builtin/gc.c: static int maintenance_task_incremental_repack(void)\n    +@@ builtin/gc.c: static int maintenance_task_gc(struct maintenance_opts *opts)\n\n    - typedef int maintenance_task_fn(void);\n    + typedef int maintenance_task_fn(struct maintenance_opts *opts);\n\n     +/*\n     + * An auto condition function returns 1 if the task should run\n    @@ builtin/gc.c: static struct maintenance_task tasks[] = {\n              1,\n          },\n          [TASK_COMMIT_GRAPH] = {\n    -@@ builtin/gc.c: static int maintenance_run(void)\n    -         if (!opts.tasks_selected && !tasks[i].enabled)\n    +@@ builtin/gc.c: static int maintenance_run(struct maintenance_opts *opts)\n    +         if (!found_selected && !tasks[i].enabled)\n                  continue;\n\n    -+        if (opts.auto_flag &&\n    ++        if (opts->auto_flag &&\n     +            (!tasks[i].auto_condition ||\n     +             !tasks[i].auto_condition()))\n     +            continue;\n     +\n    -         if (tasks[i].fn()) {\n    +         if (tasks[i].fn(opts)) {\n                  error(_(\"task '%s' failed\"), tasks[i].name);\n                  result = 1;\n     @@ builtin/gc.c: static void initialize_task_config(void)\n    @@ t/t5514-fetch-multiple.sh: test_expect_success 'git fetch --multiple (two remote\n\n      ## t/t7900-maintenance.sh ##\n     @@ t/t7900-maintenance.sh: test_expect_success 'run [--auto|--quiet]' '\n    -     GIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" git maintenance run --auto &&\n    -     GIT_TRACE2_EVENT=\"$(pwd)/run-quiet.txt\" git maintenance run --quiet &&\n    -     grep \",\\\"gc\\\"]\" run-no-auto.txt  &&\n    --    grep \",\\\"gc\\\",\\\"--auto\\\"\" run-auto.txt &&\n    -+    ! grep \",\\\"gc\\\",\\\"--auto\\\"\" run-auto.txt &&\n    -     grep \",\\\"gc\\\",\\\"--quiet\\\"\" run-quiet.txt\n    +     GIT_TRACE2_EVENT=\"$(pwd)/run-no-quiet.txt\" \\\n    +         git maintenance run --no-quiet 2>/dev/null &&\n    +     test_subcommand git gc --quiet <run-no-auto.txt &&\n    +-    test_subcommand git gc --auto --quiet <run-auto.txt &&\n    ++    test_subcommand ! git gc --auto --quiet <run-auto.txt &&\n    +     test_subcommand git gc --no-quiet <run-no-quiet.txt\n      '\n\n16:  ef3a854508 ! 10:  e67e259aef maintenance: add auto condition for commit-graph task\n    @@ Documentation/config/maintenance.txt: maintenance.<task>.enabled::\n\n      ## builtin/gc.c ##\n     @@\n    + #include \"blob.h\"\n    + #include \"tree.h\"\n      #include \"promisor-remote.h\"\n    - #include \"remote.h\"\n    - #include \"midx.h\"\n     +#include \"refs.h\"\n\n      #define FAILED_RUN \"failed to run %s\"\n\n    -@@ builtin/gc.c: static struct maintenance_opts {\n    -     int tasks_selected;\n    - } opts;\n    +@@ builtin/gc.c: struct maintenance_opts {\n    +     int quiet;\n    + };\n\n     +/* Remember to update object flag allocation in object.h */\n     +#define PARENT1        (1u<<16)\n    @@ builtin/gc.c: static struct maintenance_opts {\n     +    return result;\n     +}\n     +\n    - static int run_write_commit_graph(void)\n    + static int run_write_commit_graph(struct maintenance_opts *opts)\n      {\n          struct child_process child = CHILD_PROCESS_INIT;\n     @@ builtin/gc.c: static struct maintenance_task tasks[] = {\n17:  6ac3a58f2f <  -:  ---------- maintenance: create auto condition for loose-objects\n18:  801b262d1c <  -:  ---------- maintenance: add incremental-repack auto condition\n19:  9b4cef7635 <  -:  ---------- midx: use start_delayed_progress()\n20:  39eb83ad1e ! 11:  a5d1914846 maintenance: add trace2 regions for task execution\n    @@ Commit message\n         Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n\n      ## builtin/gc.c ##\n    -@@ builtin/gc.c: static int maintenance_run(void)\n    +@@ builtin/gc.c: static int maintenance_run(struct maintenance_opts *opts)\n                   !tasks[i].auto_condition()))\n                  continue;\n\n     +        trace2_region_enter(\"maintenance\", tasks[i].name, r);\n    -         if (tasks[i].fn()) {\n    +         if (tasks[i].fn(opts)) {\n                  error(_(\"task '%s' failed\"), tasks[i].name);\n                  result = 1;\n              }\n\nDerrick Stolee (11):\n  maintenance: create basic maintenance runner\n  maintenance: add --quiet option\n  maintenance: replace run_auto_gc()\n  maintenance: initialize task array\n  maintenance: add commit-graph task\n  maintenance: add --task option\n  maintenance: take a lock on the objects directory\n  maintenance: create maintenance.<task>.enabled config\n  maintenance: use pointers to check --auto\n  maintenance: add auto condition for commit-graph task\n  maintenance: add trace2 regions for task execution\n\n .gitignore                           |   1 +\n Documentation/config.txt             |   2 +\n Documentation/config/maintenance.txt |  14 ++\n Documentation/fetch-options.txt      |   6 +-\n Documentation/git-clone.txt          |   6 +-\n Documentation/git-maintenance.txt    |  86 +++++++\n builtin.h                            |   1 +\n builtin/am.c                         |   2 +-\n builtin/commit.c                     |   2 +-\n builtin/fetch.c                      |   6 +-\n builtin/gc.c                         | 353 +++++++++++++++++++++++++++\n builtin/merge.c                      |   2 +-\n builtin/rebase.c                     |   4 +-\n commit-graph.c                       |   8 +-\n commit-graph.h                       |   1 +\n git.c                                |   1 +\n object.h                             |   1 +\n run-command.c                        |  16 +-\n run-command.h                        |   2 +-\n t/t5510-fetch.sh                     |   2 +-\n t/t5514-fetch-multiple.sh            |   2 +-\n t/t7900-maintenance.sh               |  61 +++++\n t/test-lib-functions.sh              |  33 +++\n 23 files changed, 584 insertions(+), 28 deletions(-)\n create mode 100644 Documentation/config/maintenance.txt\n create mode 100644 Documentation/git-maintenance.txt\n create mode 100755 t/t7900-maintenance.sh\n\n\nbase-commit: d70a9eb611a9d242c1d26847d223b8677609305b\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-695%2Fderrickstolee%2Fmaintenance%2Fbuiltin-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-695/derrickstolee/maintenance/builtin-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/695\n-- \ngitgitgadget\n"},{"id":"403079","messageId":"2b9deb6d6a23e53bec75e109f2e3ef9217420425.1596728921.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.git.1596728921.gitgitgadget@gmail.com","subject":"[PATCH 01/11] maintenance: create basic maintenance runner","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-06T15:48:31Z","receivedAt":"2020-08-06T17:56:06Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe 'gc' builtin is our current entrypoint for automatically maintaining\na repository. This one tool does many operations, such as repacking the\nrepository, packing refs, and rewriting the commit-graph file. The name\nimplies it performs \"garbage collection\" which means several different\nthings, and some users may not want to use this operation that rewrites\nthe entire object database.\n\nCreate a new 'maintenance' builtin that will become a more general-\npurpose command. To start, it will only support the 'run' subcommand,\nbut will later expand to add subcommands for scheduling maintenance in\nthe background.\n\nFor now, the 'maintenance' builtin is a thin shim over the 'gc' builtin.\nIn fact, the only option is the '--auto' toggle, which is handed\ndirectly to the 'gc' builtin. The current change is isolated to this\nsimple operation to prevent more interesting logic from being lost in\nall of the boilerplate of adding a new builtin.\n\nUse existing builtin/gc.c file because we want to share code between the\ntwo builtins. It is possible that we will have 'maintenance' replace the\n'gc' builtin entirely at some point, leaving 'git gc' as an alias for\nsome specific arguments to 'git maintenance run'.\n\nCreate a new test_subcommand helper that allows us to test if a certain\nsubcommand was run. It requires storing the GIT_TRACE2_EVENT logs in a\nfile. A negation mode is available that will be used in later tests.\n\nHelped-by: Jonathan Nieder <jrnieder@gmail.com>\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n .gitignore                        |  1 +\n Documentation/git-maintenance.txt | 57 +++++++++++++++++++++++++++++++\n builtin.h                         |  1 +\n builtin/gc.c                      | 57 +++++++++++++++++++++++++++++++\n git.c                             |  1 +\n t/t7900-maintenance.sh            | 19 +++++++++++\n t/test-lib-functions.sh           | 33 ++++++++++++++++++\n 7 files changed, 169 insertions(+)\n create mode 100644 Documentation/git-maintenance.txt\n create mode 100755 t/t7900-maintenance.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex ee509a2ad2..a5808fa30d 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -90,6 +90,7 @@\n /git-ls-tree\n /git-mailinfo\n /git-mailsplit\n+/git-maintenance\n /git-merge\n /git-merge-base\n /git-merge-index\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nnew file mode 100644\nindex 0000000000..34cd2b4417\n--- /dev/null\n+++ b/Documentation/git-maintenance.txt\n@@ -0,0 +1,57 @@\n+git-maintenance(1)\n+==================\n+\n+NAME\n+----\n+git-maintenance - Run tasks to optimize Git repository data\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git maintenance' run [<options>]\n+\n+\n+DESCRIPTION\n+-----------\n+Run tasks to optimize Git repository data, speeding up other Git commands\n+and reducing storage requirements for the repository.\n++\n+Git commands that add repository data, such as `git add` or `git fetch`,\n+are optimized for a responsive user experience. These commands do not take\n+time to optimize the Git data, since such optimizations scale with the full\n+size of the repository while these user commands each perform a relatively\n+small action.\n++\n+The `git maintenance` command provides flexibility for how to optimize the\n+Git repository.\n+\n+SUBCOMMANDS\n+-----------\n+\n+run::\n+\tRun one or more maintenance tasks.\n+\n+TASKS\n+-----\n+\n+gc::\n+\tCleanup unnecessary files and optimize the local repository. \"GC\"\n+\tstands for \"garbage collection,\" but this task performs many\n+\tsmaller tasks. This task can be rather expensive for large\n+\trepositories, as it repacks all Git objects into a single pack-file.\n+\tIt can also be disruptive in some situations, as it deletes stale\n+\tdata.\n+\n+OPTIONS\n+-------\n+--auto::\n+\tWhen combined with the `run` subcommand, run maintenance tasks\n+\tonly if certain thresholds are met. For example, the `gc` task\n+\truns when the number of loose objects exceeds the number stored\n+\tin the `gc.auto` config setting, or when the number of pack-files\n+\texceeds the `gc.autoPackLimit` config setting.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/builtin.h b/builtin.h\nindex a5ae15bfe5..17c1c0ce49 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -167,6 +167,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix);\n int cmd_ls_remote(int argc, const char **argv, const char *prefix);\n int cmd_mailinfo(int argc, const char **argv, const char *prefix);\n int cmd_mailsplit(int argc, const char **argv, const char *prefix);\n+int cmd_maintenance(int argc, const char **argv, const char *prefix);\n int cmd_merge(int argc, const char **argv, const char *prefix);\n int cmd_merge_base(int argc, const char **argv, const char *prefix);\n int cmd_merge_index(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex aafa0946f5..e4f0ce1c86 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -699,3 +699,60 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n \n \treturn 0;\n }\n+\n+static const char * const builtin_maintenance_usage[] = {\n+\tN_(\"git maintenance run [<options>]\"),\n+\tNULL\n+};\n+\n+struct maintenance_opts {\n+\tint auto_flag;\n+};\n+\n+static int maintenance_task_gc(struct maintenance_opts *opts)\n+{\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\n+\tchild.git_cmd = 1;\n+\tstrvec_push(&child.args, \"gc\");\n+\n+\tif (opts->auto_flag)\n+\t\tstrvec_push(&child.args, \"--auto\");\n+\n+\tclose_object_store(the_repository->objects);\n+\treturn run_command(&child);\n+}\n+\n+static int maintenance_run(struct maintenance_opts *opts)\n+{\n+\treturn maintenance_task_gc(opts);\n+}\n+\n+int cmd_maintenance(int argc, const char **argv, const char *prefix)\n+{\n+\tstatic struct maintenance_opts opts;\n+\tstatic struct option builtin_maintenance_options[] = {\n+\t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n+\t\t\t N_(\"run tasks based on the state of the repository\")),\n+\t\tOPT_END()\n+\t};\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n+\t\tusage_with_options(builtin_maintenance_usage,\n+\t\t\t\t   builtin_maintenance_options);\n+\n+\targc = parse_options(argc, argv, prefix,\n+\t\t\t     builtin_maintenance_options,\n+\t\t\t     builtin_maintenance_usage,\n+\t\t\t     PARSE_OPT_KEEP_UNKNOWN);\n+\n+\tif (argc == 1) {\n+\t\tif (!strcmp(argv[0], \"run\"))\n+\t\t\treturn maintenance_run(&opts);\n+\t}\n+\n+\tusage_with_options(builtin_maintenance_usage,\n+\t\t\t   builtin_maintenance_options);\n+}\ndiff --git a/git.c b/git.c\nindex 8bd1d7551d..24f250d29a 100644\n--- a/git.c\n+++ b/git.c\n@@ -529,6 +529,7 @@ static struct cmd_struct commands[] = {\n \t{ \"ls-tree\", cmd_ls_tree, RUN_SETUP },\n \t{ \"mailinfo\", cmd_mailinfo, RUN_SETUP_GENTLY | NO_PARSEOPT },\n \t{ \"mailsplit\", cmd_mailsplit, NO_PARSEOPT },\n+\t{ \"maintenance\", cmd_maintenance, RUN_SETUP_GENTLY | NO_PARSEOPT },\n \t{ \"merge\", cmd_merge, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"merge-base\", cmd_merge_base, RUN_SETUP },\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nnew file mode 100755\nindex 0000000000..c4b9b4a6fe\n--- /dev/null\n+++ b/t/t7900-maintenance.sh\n@@ -0,0 +1,19 @@\n+#!/bin/sh\n+\n+test_description='git maintenance builtin'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'help text' '\n+\ttest_expect_code 129 git maintenance -h 2>err &&\n+\ttest_i18ngrep \"usage: git maintenance run\" err\n+'\n+\n+test_expect_success 'run [--auto]' '\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-auto.txt\" git maintenance run &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" git maintenance run --auto &&\n+\ttest_subcommand git gc <run-no-auto.txt &&\n+\ttest_subcommand git gc --auto <run-auto.txt\n+'\n+\n+test_done\ndiff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh\nindex 3103be8a32..0adf2b85f8 100644\n--- a/t/test-lib-functions.sh\n+++ b/t/test-lib-functions.sh\n@@ -1561,3 +1561,36 @@ test_path_is_hidden () {\n \tcase \"$(\"$SYSTEMROOT\"/system32/attrib \"$1\")\" in *H*?:*) return 0;; esac\n \treturn 1\n }\n+\n+# Check that the given command was invoked as part of the\n+# trace2-format trace on stdin.\n+#\n+#\ttest_subcommand [!] <command> <args>... < <trace>\n+#\n+# For example, to look for an invocation of \"git upload-pack\n+# /path/to/repo\"\n+#\n+#\tGIT_TRACE2_EVENT=event.log git fetch ... &&\n+#\ttest_subcommand git upload-pack \"$PATH\" <event.log\n+#\n+# If the first parameter passed is !, this instead checks that\n+# the given command was not called.\n+#\n+test_subcommand () {\n+\tlocal negate=\n+\tif test \"$1\" = \"!\"\n+\tthen\n+\t\tnegate=t\n+\t\tshift\n+\tfi\n+\n+\tlocal expr=$(printf '\"%s\",' \"$@\")\n+\texpr=\"${expr%,}\"\n+\n+\tif test -n \"$negate\"\n+\tthen\n+\t\t! grep \"\\[$expr\\]\"\n+\telse\n+\t\tgrep \"\\[$expr\\]\"\n+\tfi\n+}\n-- \ngitgitgadget\n\n"},{"id":"403080","messageId":"233811310b6536f4287d0ebb0ab869a526dc04c6.1596728921.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.git.1596728921.gitgitgadget@gmail.com","subject":"[PATCH 03/11] maintenance: replace run_auto_gc()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-06T15:48:33Z","receivedAt":"2020-08-06T17:56:32Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe run_auto_gc() method is used in several places to trigger a check\nfor repo maintenance after some Git commands, such as 'git commit' or\n'git fetch'.\n\nTo allow for extra customization of this maintenance activity, replace\nthe 'git gc --auto [--quiet]' call with one to 'git maintenance run\n--auto [--quiet]'. As we extend the maintenance builtin with other\nsteps, users will be able to select different maintenance activities.\n\nRename run_auto_gc() to run_auto_maintenance() to be clearer what is\nhappening on this call, and to expose all callers in the current diff.\nRewrite the method to use a struct child_process to simplify the calls\nslightly.\n\nSince 'git fetch' already allows disabling the 'git gc --auto'\nsubprocess, add an equivalent option with a different name to be more\ndescriptive of the new behavior: '--[no-]maintenance'. Update the\ndocumentation to include these options at the same time.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/fetch-options.txt |  6 ++++--\n Documentation/git-clone.txt     |  6 +++---\n builtin/am.c                    |  2 +-\n builtin/commit.c                |  2 +-\n builtin/fetch.c                 |  6 ++++--\n builtin/merge.c                 |  2 +-\n builtin/rebase.c                |  4 ++--\n run-command.c                   | 16 +++++++---------\n run-command.h                   |  2 +-\n t/t5510-fetch.sh                |  2 +-\n 10 files changed, 25 insertions(+), 23 deletions(-)\n\ndiff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt\nindex 6e2a160a47..495bc8ab5a 100644\n--- a/Documentation/fetch-options.txt\n+++ b/Documentation/fetch-options.txt\n@@ -86,9 +86,11 @@ ifndef::git-pull[]\n \tAllow several <repository> and <group> arguments to be\n \tspecified. No <refspec>s may be specified.\n \n+--[no-]auto-maintenance::\n --[no-]auto-gc::\n-\tRun `git gc --auto` at the end to perform garbage collection\n-\tif needed. This is enabled by default.\n+\tRun `git maintenance run --auto` at the end to perform automatic\n+\trepository maintenance if needed. (`--[no-]auto-gc` is a synonym.)\n+\tThis is enabled by default.\n \n --[no-]write-commit-graph::\n \tWrite a commit-graph after fetching. This overrides the config\ndiff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt\nindex c898310099..097e6a86c5 100644\n--- a/Documentation/git-clone.txt\n+++ b/Documentation/git-clone.txt\n@@ -78,9 +78,9 @@ repository using this option and then delete branches (or use any\n other Git command that makes any existing commit unreferenced) in the\n source repository, some objects may become unreferenced (or dangling).\n These objects may be removed by normal Git operations (such as `git commit`)\n-which automatically call `git gc --auto`. (See linkgit:git-gc[1].)\n-If these objects are removed and were referenced by the cloned repository,\n-then the cloned repository will become corrupt.\n+which automatically call `git maintenance run --auto`. (See\n+linkgit:git-maintenance[1].) If these objects are removed and were referenced\n+by the cloned repository, then the cloned repository will become corrupt.\n +\n Note that running `git repack` without the `--local` option in a repository\n cloned with `--shared` will copy objects from the source repository into a pack\ndiff --git a/builtin/am.c b/builtin/am.c\nindex dd4e6c2d9b..68e9d17379 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1795,7 +1795,7 @@ static void am_run(struct am_state *state, int resume)\n \tif (!state->rebasing) {\n \t\tam_destroy(state);\n \t\tclose_object_store(the_repository->objects);\n-\t\trun_auto_gc(state->quiet);\n+\t\trun_auto_maintenance(state->quiet);\n \t}\n }\n \ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 69ac78d5e5..f9b0a0c05d 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1702,7 +1702,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \tgit_test_write_commit_graph_or_die();\n \n \trepo_rerere(the_repository, 0);\n-\trun_auto_gc(quiet);\n+\trun_auto_maintenance(quiet);\n \trun_commit_hook(use_editor, get_index_file(), \"post-commit\", NULL);\n \tif (amend && !no_post_rewrite) {\n \t\tcommit_post_rewrite(the_repository, current_head, &oid);\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex c49f0e9752..c8b9366d3c 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -196,8 +196,10 @@ static struct option builtin_fetch_options[] = {\n \tOPT_STRING_LIST(0, \"negotiation-tip\", &negotiation_tip, N_(\"revision\"),\n \t\t\tN_(\"report that we have only objects reachable from this object\")),\n \tOPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),\n+\tOPT_BOOL(0, \"auto-maintenance\", &enable_auto_gc,\n+\t\t N_(\"run 'maintenance --auto' after fetching\")),\n \tOPT_BOOL(0, \"auto-gc\", &enable_auto_gc,\n-\t\t N_(\"run 'gc --auto' after fetching\")),\n+\t\t N_(\"run 'maintenance --auto' after fetching\")),\n \tOPT_BOOL(0, \"show-forced-updates\", &fetch_show_forced_updates,\n \t\t N_(\"check for forced-updates on all updated branches\")),\n \tOPT_BOOL(0, \"write-commit-graph\", &fetch_write_commit_graph,\n@@ -1882,7 +1884,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)\n \tclose_object_store(the_repository->objects);\n \n \tif (enable_auto_gc)\n-\t\trun_auto_gc(verbosity < 0);\n+\t\trun_auto_maintenance(verbosity < 0);\n \n \treturn result;\n }\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 7da707bf55..c068e73037 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -457,7 +457,7 @@ static void finish(struct commit *head_commit,\n \t\t\t * user should see them.\n \t\t\t */\n \t\t\tclose_object_store(the_repository->objects);\n-\t\t\trun_auto_gc(verbosity < 0);\n+\t\t\trun_auto_maintenance(verbosity < 0);\n \t\t}\n \t}\n \tif (new_head && show_diffstat) {\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex dadb52fa92..9ffba9009d 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -728,10 +728,10 @@ static int finish_rebase(struct rebase_options *opts)\n \tapply_autostash(state_dir_path(\"autostash\", opts));\n \tclose_object_store(the_repository->objects);\n \t/*\n-\t * We ignore errors in 'gc --auto', since the\n+\t * We ignore errors in 'git maintenance run --auto', since the\n \t * user should see them.\n \t */\n-\trun_auto_gc(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE)));\n+\trun_auto_maintenance(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE)));\n \tif (opts->type == REBASE_MERGE) {\n \t\tstruct replay_opts replay = REPLAY_OPTS_INIT;\n \ndiff --git a/run-command.c b/run-command.c\nindex cc9c3296ba..2ee59acdc8 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -1866,15 +1866,13 @@ int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,\n \treturn result;\n }\n \n-int run_auto_gc(int quiet)\n+int run_auto_maintenance(int quiet)\n {\n-\tstruct strvec argv_gc_auto = STRVEC_INIT;\n-\tint status;\n+\tstruct child_process maint = CHILD_PROCESS_INIT;\n \n-\tstrvec_pushl(&argv_gc_auto, \"gc\", \"--auto\", NULL);\n-\tif (quiet)\n-\t\tstrvec_push(&argv_gc_auto, \"--quiet\");\n-\tstatus = run_command_v_opt(argv_gc_auto.v, RUN_GIT_CMD);\n-\tstrvec_clear(&argv_gc_auto);\n-\treturn status;\n+\tmaint.git_cmd = 1;\n+\tstrvec_pushl(&maint.args, \"maintenance\", \"run\", \"--auto\", NULL);\n+\tstrvec_push(&maint.args, quiet ? \"--quiet\" : \"--no-quiet\");\n+\n+\treturn run_command(&maint);\n }\ndiff --git a/run-command.h b/run-command.h\nindex 8b9bfaef16..6472b38bde 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -221,7 +221,7 @@ int run_hook_ve(const char *const *env, const char *name, va_list args);\n /*\n  * Trigger an auto-gc\n  */\n-int run_auto_gc(int quiet);\n+int run_auto_maintenance(int quiet);\n \n #define RUN_COMMAND_NO_STDIN 1\n #define RUN_GIT_CMD\t     2\t/*If this is to be git sub-command */\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex a66dbe0bde..9850ecde5d 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -919,7 +919,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '\n \t\tgit config fetch.unpackLimit 1 &&\n \t\tgit config gc.autoPackLimit 1 &&\n \t\tgit config gc.autoDetach false &&\n-\t\tGIT_ASK_YESNO=\"$D/askyesno\" git fetch >fetch.out 2>&1 &&\n+\t\tGIT_ASK_YESNO=\"$D/askyesno\" git fetch --verbose >fetch.out 2>&1 &&\n \t\ttest_i18ngrep \"Auto packing the repository\" fetch.out &&\n \t\t! grep \"Should I try again\" fetch.out\n \t)\n-- \ngitgitgadget\n\n"},{"id":"403081","messageId":"79af39be13701267a094362032cbdfa13aa6a6f1.1596728921.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.git.1596728921.gitgitgadget@gmail.com","subject":"[PATCH 07/11] maintenance: take a lock on the objects directory","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-06T15:48:37Z","receivedAt":"2020-08-06T17:56:38Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nPerforming maintenance on a Git repository involves writing data to the\n.git directory, which is not safe to do with multiple writers attempting\nthe same operation. Ensure that only one 'git maintenance' process is\nrunning at a time by holding a file-based lock. Simply the presence of\nthe .git/maintenance.lock file will prevent future maintenance. This\nlock is never committed, since it does not represent meaningful data.\nInstead, it is only a placeholder.\n\nIf the lock file already exists, then fail silently. This will become\nvery important later when we implement the 'fetch' task, as this is our\nstop-gap from creating a recursive process loop between 'git fetch' and\n'git maintenance run'.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/gc.c | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 3d50ab7ac9..1b7d502b87 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -830,6 +830,25 @@ static int maintenance_run(struct maintenance_opts *opts)\n {\n \tint i, found_selected = 0;\n \tint result = 0;\n+\tstruct lock_file lk;\n+\tstruct repository *r = the_repository;\n+\tchar *lock_path = xstrfmt(\"%s/maintenance\", r->objects->odb->path);\n+\n+\tif (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {\n+\t\t/*\n+\t\t * Another maintenance command is running.\n+\t\t *\n+\t\t * If --auto was provided, then it is likely due to a\n+\t\t * recursive process stack. Do not report an error in\n+\t\t * that case.\n+\t\t */\n+\t\tif (!opts->auto_flag && !opts->quiet)\n+\t\t\terror(_(\"lock file '%s' exists, skipping maintenance\"),\n+\t\t\t      lock_path);\n+\t\tfree(lock_path);\n+\t\treturn 0;\n+\t}\n+\tfree(lock_path);\n \n \tfor (i = 0; !found_selected && i < TASK__COUNT; i++)\n \t\tfound_selected = tasks[i].selected;\n@@ -850,6 +869,7 @@ static int maintenance_run(struct maintenance_opts *opts)\n \t\t}\n \t}\n \n+\trollback_lock_file(&lk);\n \treturn result;\n }\n \n-- \ngitgitgadget\n\n"},{"id":"403082","messageId":"df21bbb0005c0fa39917e326213423a9399308ab.1596728921.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.git.1596728921.gitgitgadget@gmail.com","subject":"[PATCH 09/11] maintenance: use pointers to check --auto","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-06T15:48:39Z","receivedAt":"2020-08-06T17:56:48Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe 'git maintenance run' command has an '--auto' option. This is used\nby other Git commands such as 'git commit' or 'git fetch' to check if\nmaintenance should be run after adding data to the repository.\n\nPreviously, this --auto option was only used to add the argument to the\n'git gc' command as part of the 'gc' task. We will be expanding the\nother tasks to perform a check to see if they should do work as part of\nthe --auto flag, when they are enabled by config.\n\nFirst, update the 'gc' task to perform the auto check inside the\nmaintenance process. This prevents running an extra 'git gc --auto'\ncommand when not needed. It also shows a model for other tasks.\n\nSecond, use the 'auto_condition' function pointer as a signal for\nwhether we enable the maintenance task under '--auto'. For instance, we\ndo not want to enable the 'fetch' task in '--auto' mode, so that\nfunction pointer will remain NULL.\n\nNow that we are not automatically calling 'git gc', a test in\nt5514-fetch-multiple.sh must be changed to watch for 'git maintenance'\ninstead.\n\nWe continue to pass the '--auto' option to the 'git gc' command when\nnecessary, because of the gc.autoDetach config option changes behavior.\nLikely, we will want to absorb the daemonizing behavior implied by\ngc.autoDetach as a maintenance.autoDetach config option.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/gc.c              | 16 ++++++++++++++++\n t/t5514-fetch-multiple.sh |  2 +-\n t/t7900-maintenance.sh    |  2 +-\n 3 files changed, 18 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex dc1d260858..2b3e45eb71 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -788,9 +788,17 @@ static int maintenance_task_gc(struct maintenance_opts *opts)\n \n typedef int maintenance_task_fn(struct maintenance_opts *opts);\n \n+/*\n+ * An auto condition function returns 1 if the task should run\n+ * and 0 if the task should NOT run. See needs_to_gc() for an\n+ * example.\n+ */\n+typedef int maintenance_auto_fn(void);\n+\n struct maintenance_task {\n \tconst char *name;\n \tmaintenance_task_fn *fn;\n+\tmaintenance_auto_fn *auto_condition;\n \tunsigned enabled:1,\n \t\t selected:1;\n \tint selected_order;\n@@ -808,6 +816,7 @@ static struct maintenance_task tasks[] = {\n \t[TASK_GC] = {\n \t\t\"gc\",\n \t\tmaintenance_task_gc,\n+\t\tneed_to_gc,\n \t\t1,\n \t},\n \t[TASK_COMMIT_GRAPH] = {\n@@ -863,6 +872,11 @@ static int maintenance_run(struct maintenance_opts *opts)\n \t\tif (!found_selected && !tasks[i].enabled)\n \t\t\tcontinue;\n \n+\t\tif (opts->auto_flag &&\n+\t\t    (!tasks[i].auto_condition ||\n+\t\t     !tasks[i].auto_condition()))\n+\t\t\tcontinue;\n+\n \t\tif (tasks[i].fn(opts)) {\n \t\t\terror(_(\"task '%s' failed\"), tasks[i].name);\n \t\t\tresult = 1;\n@@ -877,6 +891,8 @@ static void initialize_task_config(void)\n {\n \tint i;\n \tstruct strbuf config_name = STRBUF_INIT;\n+\tgc_config();\n+\n \tfor (i = 0; i < TASK__COUNT; i++) {\n \t\tint config_value;\n \ndiff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh\nindex de8e2f1531..bd202ec6f3 100755\n--- a/t/t5514-fetch-multiple.sh\n+++ b/t/t5514-fetch-multiple.sh\n@@ -108,7 +108,7 @@ test_expect_success 'git fetch --multiple (two remotes)' '\n \t GIT_TRACE=1 git fetch --multiple one two 2>trace &&\n \t git branch -r > output &&\n \t test_cmp ../expect output &&\n-\t grep \"built-in: git gc\" trace >gc &&\n+\t grep \"built-in: git maintenance\" trace >gc &&\n \t test_line_count = 1 gc\n \t)\n '\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex cdf0bf2e60..406dc7c303 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -19,7 +19,7 @@ test_expect_success 'run [--auto|--quiet]' '\n \tGIT_TRACE2_EVENT=\"$(pwd)/run-no-quiet.txt\" \\\n \t\tgit maintenance run --no-quiet 2>/dev/null &&\n \ttest_subcommand git gc --quiet <run-no-auto.txt &&\n-\ttest_subcommand git gc --auto --quiet <run-auto.txt &&\n+\ttest_subcommand ! git gc --auto --quiet <run-auto.txt &&\n \ttest_subcommand git gc --no-quiet <run-no-quiet.txt\n '\n \n-- \ngitgitgadget\n\n"},{"id":"403172","messageId":"CAN0heSqZ=nQzHiuLVCYcNfm34DO=4aSpsNPL4scyTV=vK1ssOQ@mail.gmail.com","threadId":"53997","inReplyTo":"2b9deb6d6a23e53bec75e109f2e3ef9217420425.1596728921.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 01/11] maintenance: create basic maintenance runner","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2020-08-07T22:16:48Z","receivedAt":"2020-08-07T22:17:03Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"On Thu, 6 Aug 2020 at 19:57, Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> +DESCRIPTION\n> +-----------\n> +Run tasks to optimize Git repository data, speeding up other Git commands\n> +and reducing storage requirements for the repository.\n> ++\n\nThis \"+\" and the one below render literally so you would want to drop\nthem. (You're not in any kind of \"list\" here, so no need for a \"list\ncontinuation\".)\n\n> +Git commands that add repository data, such as `git add` or `git fetch`,\n> +are optimized for a responsive user experience. These commands do not take\n> +time to optimize the Git data, since such optimizations scale with the full\n> +size of the repository while these user commands each perform a relatively\n> +small action.\n> ++\n> +The `git maintenance` command provides flexibility for how to optimize the\n> +Git repository.\n\nMartin\n"},{"id":"403175","messageId":"CAN0heSrvJ4nZnMH=CPyNrZTkEXsZm2htT5kzN+RfzPXgXW=oSA@mail.gmail.com","threadId":"53997","inReplyTo":"902b742032ae19087392538936cc81768a59e0e1.1596728921.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 05/11] maintenance: add commit-graph task","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2020-08-07T22:29:27Z","receivedAt":"2020-08-07T22:29:43Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"On Thu, 6 Aug 2020 at 18:50, Derrick Stolee via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> diff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\n> index 089fa4cedc..35b0be7d40 100644\n> --- a/Documentation/git-maintenance.txt\n> +++ b/Documentation/git-maintenance.txt\n> @@ -35,6 +35,24 @@ run::\n>  TASKS\n>  -----\n>\n> +commit-graph::\n> +       The `commit-graph` job updates the `commit-graph` files incrementally,\n> +       then verifies that the written data is correct. If the new layer has an\n> +       issue, then the chain file is removed and the `commit-graph` is\n> +       rewritten from scratch.\n> ++\n> +The verification only checks the top layer of the `commit-graph` chain.\n> +If the incremental write merged the new commits with at least one\n> +existing layer, then there is potential for on-disk corruption being\n> +carried forward into the new file. This will be noticed and the new\n> +commit-graph file will be clean as Git reparses the commit data from\n> +the object database.\n\nThis reads somewhat awkwardly. I think what you mean is something like\n\"is there a risk for on-disk corruption? yes, but no: we're clever\nenough to detect it and avoid it\". So from a user's point of view, I\nthink this is too detailed.\n\nHow about\n\n The verification checks the top layer of the resulting `commit-graph` chain.\n This ensures that the maintenance task leaves the top layer in a shape\n that matches the object database, even if it was ostensibly constructed\n by \"merging in\" existing, incorrect layers.\n\n? I don't quite like it -- it's a bit too technical -- but it describes\nthe end result which the user should care about -- on-disk data is\nconsistent and correct -- rather than how we got there.\n\nPerhaps:\n\n It is ensured that the resulting \"top layer\" is correct. This should\n help avoid most on-disk corruption of the commit-graph and ensure\n that the commit-graph matches the object database.\n\nDon't know whether that's entirely true though...\n\nFood for thought, perhaps.\n\nThere's something probabilistic about this whole thing: If a low layer\nis corrupt, you might \"eventually\" get to replace it. I suppose it could\nmake sense to go \"verify the whole thing, drop however many top layers\nwe need to drop to get only correct layers, then generate a new layer\n(and merge and whatnot) on top of that\". But the proposed commit message\nmakes it fairly clear that this would have other drawbacks and that we\ndon't really expect corrupt layers in the first place.\n\nMartin\n"},{"id":"403447","messageId":"0380dcbf-82a0-68d0-b421-9f5c5a640ab5@gmail.com","threadId":"53997","inReplyTo":"CAN0heSrvJ4nZnMH=CPyNrZTkEXsZm2htT5kzN+RfzPXgXW=oSA@mail.gmail.com","subject":"Re: [PATCH 05/11] maintenance: add commit-graph task","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2020-08-12T13:30:30Z","receivedAt":"2020-08-12T13:30:54Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 8/7/2020 6:29 PM, Martin Ågren wrote:\n> On Thu, 6 Aug 2020 at 18:50, Derrick Stolee via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>> diff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\n>> index 089fa4cedc..35b0be7d40 100644\n>> --- a/Documentation/git-maintenance.txt\n>> +++ b/Documentation/git-maintenance.txt\n>> @@ -35,6 +35,24 @@ run::\n>>  TASKS\n>>  -----\n>>\n>> +commit-graph::\n>> +       The `commit-graph` job updates the `commit-graph` files incrementally,\n>> +       then verifies that the written data is correct. If the new layer has an\n>> +       issue, then the chain file is removed and the `commit-graph` is\n>> +       rewritten from scratch.\n>> ++\n>> +The verification only checks the top layer of the `commit-graph` chain.\n>> +If the incremental write merged the new commits with at least one\n>> +existing layer, then there is potential for on-disk corruption being\n>> +carried forward into the new file. This will be noticed and the new\n>> +commit-graph file will be clean as Git reparses the commit data from\n>> +the object database.\n> \n> This reads somewhat awkwardly. I think what you mean is something like\n> \"is there a risk for on-disk corruption? yes, but no: we're clever\n> enough to detect it and avoid it\". So from a user's point of view, I\n> think this is too detailed.\n> \n> How about\n> \n>  The verification checks the top layer of the resulting `commit-graph` chain.\n>  This ensures that the maintenance task leaves the top layer in a shape\n>  that matches the object database, even if it was ostensibly constructed\n>  by \"merging in\" existing, incorrect layers.\n> \n> ? I don't quite like it -- it's a bit too technical -- but it describes\n> the end result which the user should care about -- on-disk data is\n> consistent and correct -- rather than how we got there.\n> \n> Perhaps:\n> \n>  It is ensured that the resulting \"top layer\" is correct. This should\n>  help avoid most on-disk corruption of the commit-graph and ensure\n>  that the commit-graph matches the object database.\n> \n> Don't know whether that's entirely true though...\n\nThis is my understanding. We focus on the data we just wrote to see\nif anything went wrong at a lower level (i.e. filesystem or RAM\ncorruption during the write process) instead of the data \"at rest\".\n\n> Food for thought, perhaps.\n> \n> There's something probabilistic about this whole thing: If a low layer\n> is corrupt, you might \"eventually\" get to replace it. I suppose it could\n> make sense to go \"verify the whole thing, drop however many top layers\n> we need to drop to get only correct layers, then generate a new layer\n> (and merge and whatnot) on top of that\". But the proposed commit message\n> makes it fairly clear that this would have other drawbacks and that we\n> don't really expect corrupt layers in the first place.\n\nRight. And we want to keep the amount of work to be very small in most\ncases, amortizing the cost of the big merge operations across many much\nsmaller runs. Perhaps a later change could introduce an option to drop\nthe '--shallow' option and check the entire chain, for users willing to\npay that price.\n\nBack to the point of your comments: I'm not sure this second paragraph\nis required at all in the documentation. The first paragraph already\nsays:\n\n\t...then verifies that the written data is correct.\n\nThis \"written data\" _is_ the top layer of the chain. There is probably\nno reason to dig deeper into _why_ we do this in this user-facing\ndocumentation.\n\nSo, I propose just deleting this paragraph. What do you think?\n\nThanks for keeping a close eye on documentation changes! I updated\nmy local branch to include the '+' problem from your other reply.\n\nThanks,\n-Stolee\n\n"},{"id":"403509","messageId":"20200812210326.GA104953@google.com","threadId":"53997","inReplyTo":"2b9deb6d6a23e53bec75e109f2e3ef9217420425.1596728921.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 01/11] maintenance: create basic maintenance runner","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2020-08-12T21:03:26Z","receivedAt":"2020-08-12T21:03:33Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Derrick Stolee wrote:\n\n> Helped-by: Jonathan Nieder <jrnieder@gmail.com>\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  .gitignore                        |  1 +\n>  Documentation/git-maintenance.txt | 57 +++++++++++++++++++++++++++++++\n>  builtin.h                         |  1 +\n>  builtin/gc.c                      | 57 +++++++++++++++++++++++++++++++\n>  git.c                             |  1 +\n>  t/t7900-maintenance.sh            | 19 +++++++++++\n>  t/test-lib-functions.sh           | 33 ++++++++++++++++++\n>  7 files changed, 169 insertions(+)\n>  create mode 100644 Documentation/git-maintenance.txt\n>  create mode 100755 t/t7900-maintenance.sh\n\nLooks reasonable.\n\n[...]\n> --- /dev/null\n> +++ b/Documentation/git-maintenance.txt\n> @@ -0,0 +1,57 @@\n> +git-maintenance(1)\n> +==================\n> +\n> +NAME\n> +----\n> +git-maintenance - Run tasks to optimize Git repository data\n> +\n> +\n> +SYNOPSIS\n> +--------\n> +[verse]\n> +'git maintenance' run [<options>]\n> +\n> +\n> +DESCRIPTION\n> +-----------\n> +Run tasks to optimize Git repository data, speeding up other Git commands\n> +and reducing storage requirements for the repository.\n> ++\n> +Git commands that add repository data, such as `git add` or `git fetch`,\n> +are optimized for a responsive user experience. These commands do not take\n> +time to optimize the Git data, since such optimizations scale with the full\n> +size of the repository while these user commands each perform a relatively\n> +small action.\n> ++\n> +The `git maintenance` command provides flexibility for how to optimize the\n> +Git repository.\n> +\n> +SUBCOMMANDS\n> +-----------\n> +\n> +run::\n> +\tRun one or more maintenance tasks.\n\nThis is still confusing --- shouldn't it say something like \"Run the\n`gc` maintenance task (see below)\"?\n\n[...]\n> +TASKS\n> +-----\n> +\n> +gc::\n> +\tCleanup unnecessary files and optimize the local repository. \"GC\"\n\nnit: cleanup is the noun, \"clean up\" is the verb\n\n> +\tstands for \"garbage collection,\" but this task performs many\n> +\tsmaller tasks. This task can be rather expensive for large\n\nnit: the word \"rather\" is not doing much work here, so we could leave\nit out\n\n> +\trepositories, as it repacks all Git objects into a single pack-file.\n> +\tIt can also be disruptive in some situations, as it deletes stale\n> +\tdata.\n\nWhat stale data is this referring to?  Where can I read more about\nwhat disruption it causes (or in other words, as a user, how would I\ndecide when *not* to run this command)?\n\n[...]\n> +OPTIONS\n> +-------\n> +--auto::\n> +\tWhen combined with the `run` subcommand, run maintenance tasks\n> +\tonly if certain thresholds are met. For example, the `gc` task\n> +\truns when the number of loose objects exceeds the number stored\n> +\tin the `gc.auto` config setting, or when the number of pack-files\n> +\texceeds the `gc.autoPackLimit` config setting.\n\nHm, today I learned.  I had thought that --auto doesn't only affect\nthresholds but also would affect how aggressive the gc is when\ntriggered, but the git-gc(1) manpage agrees with what's said above.\n\nIs there a way we can share information between the two to avoid one\nfalling out of date?  For example, should git-gc.txt point to this\npage for the authoritative description?\n\n[...]\n> --- a/builtin/gc.c\n> +++ b/builtin/gc.c\n> @@ -699,3 +699,60 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n>  \n>  \treturn 0;\n>  }\n> +\n> +static const char * const builtin_maintenance_usage[] = {\n> +\tN_(\"git maintenance run [<options>]\"),\n\nnot about this patch: I wish we could automatically generate a usage\nstring in this simple kind of case, to decrease the burden on\ntranslators.\n\n[...]\n> +static int maintenance_task_gc(struct maintenance_opts *opts)\n> +{\n> +\tstruct child_process child = CHILD_PROCESS_INIT;\n> +\n> +\tchild.git_cmd = 1;\n> +\tstrvec_push(&child.args, \"gc\");\n> +\n> +\tif (opts->auto_flag)\n> +\t\tstrvec_push(&child.args, \"--auto\");\n> +\n> +\tclose_object_store(the_repository->objects);\n\nInteresting --- what does this function call do?\n\n> +\treturn run_command(&child);\n> +}\n> +\n> +static int maintenance_run(struct maintenance_opts *opts)\n> +{\n> +\treturn maintenance_task_gc(opts);\n> +}\n> +\n> +int cmd_maintenance(int argc, const char **argv, const char *prefix)\n> +{\n> +\tstatic struct maintenance_opts opts;\n> +\tstatic struct option builtin_maintenance_options[] = {\n> +\t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n> +\t\t\t N_(\"run tasks based on the state of the repository\")),\n> +\t\tOPT_END()\n> +\t};\n\noptional: these could be local instead of static\n\n> +\n> +\tmemset(&opts, 0, sizeof(opts));\n\nnot needed if it's static.  If it's not static, could use `opts = {0}`\nwhere it's declared to do the same thing in a simpler way.\n\n> +\n> +\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n> +\t\tusage_with_options(builtin_maintenance_usage,\n> +\t\t\t\t   builtin_maintenance_options);\n> +\n> +\targc = parse_options(argc, argv, prefix,\n> +\t\t\t     builtin_maintenance_options,\n> +\t\t\t     builtin_maintenance_usage,\n> +\t\t\t     PARSE_OPT_KEEP_UNKNOWN);\n> +\n> +\tif (argc == 1) {\n> +\t\tif (!strcmp(argv[0], \"run\"))\n> +\t\t\treturn maintenance_run(&opts);\n> +\t}\n> +\n> +\tusage_with_options(builtin_maintenance_usage,\n> +\t\t\t   builtin_maintenance_options);\n\noptional: could reverse this test to get the uninteresting case out of\nthe way first:\n\n\tif (argc != 1)\n\t\tusage ...\n\n\t...\n\nThat would also allow making the usage string when argv[0] is not\n\"run\" more specific.\n\n[...]\n> --- /dev/null\n> +++ b/t/t7900-maintenance.sh\n> @@ -0,0 +1,19 @@\n> +#!/bin/sh\n> +\n> +test_description='git maintenance builtin'\n> +\n> +. ./test-lib.sh\n> +\n> +test_expect_success 'help text' '\n> +\ttest_expect_code 129 git maintenance -h 2>err &&\n> +\ttest_i18ngrep \"usage: git maintenance run\" err\n> +'\n> +\n> +test_expect_success 'run [--auto]' '\n> +\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-auto.txt\" git maintenance run &&\n> +\tGIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" git maintenance run --auto &&\n> +\ttest_subcommand git gc <run-no-auto.txt &&\n> +\ttest_subcommand git gc --auto <run-auto.txt\n\nNice.\n\n[...]\n> --- a/t/test-lib-functions.sh\n> +++ b/t/test-lib-functions.sh\n> @@ -1561,3 +1561,36 @@ test_path_is_hidden () {\n>  \tcase \"$(\"$SYSTEMROOT\"/system32/attrib \"$1\")\" in *H*?:*) return 0;; esac\n>  \treturn 1\n>  }\n> +\n> +# Check that the given command was invoked as part of the\n> +# trace2-format trace on stdin.\n> +#\n> +#\ttest_subcommand [!] <command> <args>... < <trace>\n> +#\n> +# For example, to look for an invocation of \"git upload-pack\n> +# /path/to/repo\"\n> +#\n> +#\tGIT_TRACE2_EVENT=event.log git fetch ... &&\n> +#\ttest_subcommand git upload-pack \"$PATH\" <event.log\n> +#\n> +# If the first parameter passed is !, this instead checks that\n> +# the given command was not called.\n> +#\n> +test_subcommand () {\n> +\tlocal negate=\n> +\tif test \"$1\" = \"!\"\n> +\tthen\n> +\t\tnegate=t\n> +\t\tshift\n> +\tfi\n> +\n> +\tlocal expr=$(printf '\"%s\",' \"$@\")\n> +\texpr=\"${expr%,}\"\n> +\n> +\tif test -n \"$negate\"\n> +\tthen\n> +\t\t! grep \"\\[$expr\\]\"\n> +\telse\n> +\t\tgrep \"\\[$expr\\]\"\n> +\tfi\n> +}\n\nWith whatever subset of the changes described above makes sense,\n\nReviewed-by: Jonathan Nieder <jrnieder@gmail.com>\n\nThanks for your patient work.\n"},{"id":"403511","messageId":"xmqqsgcr4ixo.fsf@gitster.c.googlers.com","threadId":"53997","inReplyTo":"20200812210326.GA104953@google.com","subject":"Re: [PATCH 01/11] maintenance: create basic maintenance runner","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-08-12T22:07:31Z","receivedAt":"2020-08-12T22:07:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n>> +static int maintenance_run(struct maintenance_opts *opts)\n>> +{\n>> +\treturn maintenance_task_gc(opts);\n>> +}\n>> +\n>> +int cmd_maintenance(int argc, const char **argv, const char *prefix)\n>> +{\n>> +\tstatic struct maintenance_opts opts;\n>> +\tstatic struct option builtin_maintenance_options[] = {\n>> +\t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n>> +\t\t\t N_(\"run tasks based on the state of the repository\")),\n>> +\t\tOPT_END()\n>> +\t};\n>\n> optional: these could be local instead of static\n\nDo we have preference?  I think it is more common in our codebase to\nhave these non-static but I do not think we've chosen which is more\npreferable with technical reasoning behind it.  As the top-level\nfunction in any callchain, cmd_foo() does not have multiple instances\nrunning at the same time, or it does not have to be prepared to be\ncalled twice, so they can afford to be static, but is there any upside\nfor them to be static?\n\n>> +\n>> +\tmemset(&opts, 0, sizeof(opts));\n>\n> not needed if it's static.  If it's not static, could use `opts = {0}`\n> where it's declared to do the same thing in a simpler way.\n\nOkay, so that's one upside, albeit a small one.\n\n"},{"id":"403515","messageId":"20200812225054.GB104953@google.com","threadId":"53997","inReplyTo":"xmqqsgcr4ixo.fsf@gitster.c.googlers.com","subject":"Re: [PATCH 01/11] maintenance: create basic maintenance runner","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2020-08-12T22:50:54Z","receivedAt":"2020-08-12T22:51:00Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Junio C Hamano wrote:\n> Jonathan Nieder <jrnieder@gmail.com> writes:\n\n>>> +static int maintenance_run(struct maintenance_opts *opts)\n>>> +{\n>>> +\treturn maintenance_task_gc(opts);\n>>> +}\n>>> +\n>>> +int cmd_maintenance(int argc, const char **argv, const char *prefix)\n>>> +{\n>>> +\tstatic struct maintenance_opts opts;\n>>> +\tstatic struct option builtin_maintenance_options[] = {\n>>> +\t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n>>> +\t\t\t N_(\"run tasks based on the state of the repository\")),\n>>> +\t\tOPT_END()\n>>> +\t};\n>>\n>> optional: these could be local instead of static\n>\n> Do we have preference?  I think it is more common in our codebase to\n> have these non-static but I do not think we've chosen which is more\n> preferable with technical reasoning behind it.  As the top-level\n> function in any callchain, cmd_foo() does not have multiple instances\n> running at the same time, or it does not have to be prepared to be\n> called twice, so they can afford to be static, but is there any upside\n> for them to be static?\n\nCode size, execution time:\n\n- benefit of static: can rely on automatic zero-initialization of\n  pages instead of spending cycles and text size on explicit\n  zero-initialization\n\n- benefit of local: avoids wasting address space in bss when the\n  function isn't called\n\nNeither of those seems important enough to care about. :)\n\nMaintainability:\n\n- benefit of static: address is determined at compile time, so can build\n  using C89 compilers that require struct initializers to be constant\n  (but Git already cannot be built with such compilers)\n\n- benefit of local: less likely to accidentally move the static var into\n  a function that needs to be reentrant\n\n- benefit of local: allows readers and reviewers to build the habit of\n  seeing non-const \"static\" declarations within a function as the\n  unusual case.  When a \"static\" is declared in a function body, this\n  means we are caching something or have some other performance reason\n  to sacrifice reentrancy\n\nIt's mostly that last aspect (saving readers from having to think\nabout it) that motivates my suggestion above.\n\nOlder Git code used static more because it was needed when building\nusing C89 compilers.\n\nThanks,\nJonathan\n"},{"id":"403606","messageId":"b9db68dc-95a8-f8c3-a015-9d3390e30f7c@gmail.com","threadId":"53997","inReplyTo":"20200812210326.GA104953@google.com","subject":"Re: [PATCH 01/11] maintenance: create basic maintenance runner","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2020-08-14T01:05:48Z","receivedAt":"2020-08-14T01:05:55Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 8/12/2020 5:03 PM, Jonathan Nieder wrote:\n> Derrick Stolee wrote:\n> \n>> Helped-by: Jonathan Nieder <jrnieder@gmail.com>\n>> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n>> ---\n>>  .gitignore                        |  1 +\n>>  Documentation/git-maintenance.txt | 57 +++++++++++++++++++++++++++++++\n>>  builtin.h                         |  1 +\n>>  builtin/gc.c                      | 57 +++++++++++++++++++++++++++++++\n>>  git.c                             |  1 +\n>>  t/t7900-maintenance.sh            | 19 +++++++++++\n>>  t/test-lib-functions.sh           | 33 ++++++++++++++++++\n>>  7 files changed, 169 insertions(+)\n>>  create mode 100644 Documentation/git-maintenance.txt\n>>  create mode 100755 t/t7900-maintenance.sh\n> \n> Looks reasonable.\n> \n> [...]\n>> --- /dev/null\n>> +++ b/Documentation/git-maintenance.txt\n>> @@ -0,0 +1,57 @@\n>> +git-maintenance(1)\n>> +==================\n>> +\n>> +NAME\n>> +----\n>> +git-maintenance - Run tasks to optimize Git repository data\n>> +\n>> +\n>> +SYNOPSIS\n>> +--------\n>> +[verse]\n>> +'git maintenance' run [<options>]\n>> +\n>> +\n>> +DESCRIPTION\n>> +-----------\n>> +Run tasks to optimize Git repository data, speeding up other Git commands\n>> +and reducing storage requirements for the repository.\n>> ++\n>> +Git commands that add repository data, such as `git add` or `git fetch`,\n>> +are optimized for a responsive user experience. These commands do not take\n>> +time to optimize the Git data, since such optimizations scale with the full\n>> +size of the repository while these user commands each perform a relatively\n>> +small action.\n>> ++\n>> +The `git maintenance` command provides flexibility for how to optimize the\n>> +Git repository.\n>> +\n>> +SUBCOMMANDS\n>> +-----------\n>> +\n>> +run::\n>> +\tRun one or more maintenance tasks.\n> \n> This is still confusing --- shouldn't it say something like \"Run the\n> `gc` maintenance task (see below)\"?\n\nAs I mentioned in the earlier version, I disagree with this.\n\n> [...]\n>> +TASKS\n>> +-----\n>> +\n>> +gc::\n>> +\tCleanup unnecessary files and optimize the local repository. \"GC\"\n> \n> nit: cleanup is the noun, \"clean up\" is the verb\n> \n>> +\tstands for \"garbage collection,\" but this task performs many\n>> +\tsmaller tasks. This task can be rather expensive for large\n> \n> nit: the word \"rather\" is not doing much work here, so we could leave\n> it out\n\nBoth good.\n\n> \n>> +\trepositories, as it repacks all Git objects into a single pack-file.\n>> +\tIt can also be disruptive in some situations, as it deletes stale\n>> +\tdata.\n> \n> What stale data is this referring to?  Where can I read more about\n> what disruption it causes (or in other words, as a user, how would I\n> decide when *not* to run this command)?\n\nFor this and the next comment, I prefer (for now) to keep all of the\ndeep details of the 'gc' task in the git-gc.txt documentation. However,\nI should use \"linkgit:git-gc[1]\" to point users to that for reference.\n\nEventually, the maintenance builtin might replace the gc builtin, but\nonly after all of its behavior has been extracted into smaller tasks.\nThose tasks would be documented in detail here, allowing us to make the\nblurb for the 'gc' task be \"Run the 'prune-reflog', 'pack-refs', ...,\nand 'full-repack' tasks.\"\n\n> [...]\n>> +OPTIONS\n>> +-------\n>> +--auto::\n>> +\tWhen combined with the `run` subcommand, run maintenance tasks\n>> +\tonly if certain thresholds are met. For example, the `gc` task\n>> +\truns when the number of loose objects exceeds the number stored\n>> +\tin the `gc.auto` config setting, or when the number of pack-files\n>> +\texceeds the `gc.autoPackLimit` config setting.\n> \n> Hm, today I learned.  I had thought that --auto doesn't only affect\n> thresholds but also would affect how aggressive the gc is when\n> triggered, but the git-gc(1) manpage agrees with what's said above.\n> \n> Is there a way we can share information between the two to avoid one\n> falling out of date?  For example, should git-gc.txt point to this\n> page for the authoritative description?\n\nI remember Junio mentioning something about how 'gc' interprets '--auto'\nas a \"quick\" mode, but like you I cannot find any reference to that in\nthe docs. Since it is not documented there and none of the other tasks\nI am implementing here use that interpretation, I'll plan to leave this\nportion as-is.\n \n> [...]\n>> --- a/builtin/gc.c\n>> +++ b/builtin/gc.c\n>> @@ -699,3 +699,60 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n>>  \n>>  \treturn 0;\n>>  }\n>> +\n>> +static const char * const builtin_maintenance_usage[] = {\n>> +\tN_(\"git maintenance run [<options>]\"),\n> \n> not about this patch: I wish we could automatically generate a usage\n> string in this simple kind of case, to decrease the burden on\n> translators.\n> \n> [...]\n>> +static int maintenance_task_gc(struct maintenance_opts *opts)\n>> +{\n>> +\tstruct child_process child = CHILD_PROCESS_INIT;\n>> +\n>> +\tchild.git_cmd = 1;\n>> +\tstrvec_push(&child.args, \"gc\");\n>> +\n>> +\tif (opts->auto_flag)\n>> +\t\tstrvec_push(&child.args, \"--auto\");\n>> +\n>> +\tclose_object_store(the_repository->objects);\n> \n> Interesting --- what does this function call do?\n\nWe need to close our file handles on the pack-files (and\ncommit-graph and multi-pack-index) or else the GC subcommand\ncannot delete the files on Windows.\n\n>> +\treturn run_command(&child);\n>> +}\n>> +\n>> +static int maintenance_run(struct maintenance_opts *opts)\n>> +{\n>> +\treturn maintenance_task_gc(opts);\n>> +}\n>> +\n>> +int cmd_maintenance(int argc, const char **argv, const char *prefix)\n>> +{\n>> +\tstatic struct maintenance_opts opts;\n>> +\tstatic struct option builtin_maintenance_options[] = {\n>> +\t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n>> +\t\t\t N_(\"run tasks based on the state of the repository\")),\n>> +\t\tOPT_END()\n>> +\t};\n> \n> optional: these could be local instead of static\n\nI see my problem here. The builtin_maintenance_options is static\n(copied from another builtin, I'm sure) which is why the 'opts'\nstruct needs to be static or else this doesn't compile.\n\n>> +\n>> +\tmemset(&opts, 0, sizeof(opts));\n> \n> not needed if it's static.  If it's not static, could use `opts = {0}`\n> where it's declared to do the same thing in a simpler way.\n> \n>> +\n>> +\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n>> +\t\tusage_with_options(builtin_maintenance_usage,\n>> +\t\t\t\t   builtin_maintenance_options);\n>> +\n>> +\targc = parse_options(argc, argv, prefix,\n>> +\t\t\t     builtin_maintenance_options,\n>> +\t\t\t     builtin_maintenance_usage,\n>> +\t\t\t     PARSE_OPT_KEEP_UNKNOWN);\n>> +\n>> +\tif (argc == 1) {\n>> +\t\tif (!strcmp(argv[0], \"run\"))\n>> +\t\t\treturn maintenance_run(&opts);\n>> +\t}\n>> +\n>> +\tusage_with_options(builtin_maintenance_usage,\n>> +\t\t\t   builtin_maintenance_options);\n> \n> optional: could reverse this test to get the uninteresting case out of\n> the way first:\n> \n> \tif (argc != 1)\n> \t\tusage ...\n> \n> \t...\n> \n> That would also allow making the usage string when argv[0] is not\n> \"run\" more specific.\n\nSure. Were you thinking of anything more specific than\n\n\tdie(_(\"invalid subcommand: %s\"), argv[0]);\n\n? Of course, running \"git maintenance barf\" would result in\n\n\tfatal: invalid subcommand: barf\n\nwith an exit code of 128 instead of 129 like the usage string does,\nso I'm not sure of the best way to make a custom \"usage\" error.\n\n> [...]\n>> --- /dev/null\n>> +++ b/t/t7900-maintenance.sh\n>> @@ -0,0 +1,19 @@\n>> +#!/bin/sh\n>> +\n>> +test_description='git maintenance builtin'\n>> +\n>> +. ./test-lib.sh\n>> +\n>> +test_expect_success 'help text' '\n>> +\ttest_expect_code 129 git maintenance -h 2>err &&\n>> +\ttest_i18ngrep \"usage: git maintenance run\" err\n>> +'\n>> +\n>> +test_expect_success 'run [--auto]' '\n>> +\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-auto.txt\" git maintenance run &&\n>> +\tGIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" git maintenance run --auto &&\n>> +\ttest_subcommand git gc <run-no-auto.txt &&\n>> +\ttest_subcommand git gc --auto <run-auto.txt\n> \n> Nice.\n> \n> [...]\n>> --- a/t/test-lib-functions.sh\n>> +++ b/t/test-lib-functions.sh\n>> @@ -1561,3 +1561,36 @@ test_path_is_hidden () {\n>>  \tcase \"$(\"$SYSTEMROOT\"/system32/attrib \"$1\")\" in *H*?:*) return 0;; esac\n>>  \treturn 1\n>>  }\n>> +\n>> +# Check that the given command was invoked as part of the\n>> +# trace2-format trace on stdin.\n>> +#\n>> +#\ttest_subcommand [!] <command> <args>... < <trace>\n>> +#\n>> +# For example, to look for an invocation of \"git upload-pack\n>> +# /path/to/repo\"\n>> +#\n>> +#\tGIT_TRACE2_EVENT=event.log git fetch ... &&\n>> +#\ttest_subcommand git upload-pack \"$PATH\" <event.log\n>> +#\n>> +# If the first parameter passed is !, this instead checks that\n>> +# the given command was not called.\n>> +#\n>> +test_subcommand () {\n>> +\tlocal negate=\n>> +\tif test \"$1\" = \"!\"\n>> +\tthen\n>> +\t\tnegate=t\n>> +\t\tshift\n>> +\tfi\n>> +\n>> +\tlocal expr=$(printf '\"%s\",' \"$@\")\n>> +\texpr=\"${expr%,}\"\n>> +\n>> +\tif test -n \"$negate\"\n>> +\tthen\n>> +\t\t! grep \"\\[$expr\\]\"\n>> +\telse\n>> +\t\tgrep \"\\[$expr\\]\"\n>> +\tfi\n>> +}\n> \n> With whatever subset of the changes described above makes sense,\n> \n> Reviewed-by: Jonathan Nieder <jrnieder@gmail.com>\n> \n> Thanks for your patient work.\n\nThanks,\n-Stolee\n\n"},{"id":"403652","messageId":"CAN0heSqoPAx84-o2JKSjM6b+Jp2rk_j6p68iJg30AGDa+6HeeA@mail.gmail.com","threadId":"53997","inReplyTo":"0380dcbf-82a0-68d0-b421-9f5c5a640ab5@gmail.com","subject":"Re: [PATCH 05/11] maintenance: add commit-graph task","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2020-08-14T12:23:05Z","receivedAt":"2020-08-14T12:23:21Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"On Wed, 12 Aug 2020 at 15:30, Derrick Stolee <stolee@gmail.com> wrote:\n>\n> On 8/7/2020 6:29 PM, Martin Ågren wrote:\n> > On Thu, 6 Aug 2020 at 18:50, Derrick Stolee via GitGitGadget\n> > <gitgitgadget@gmail.com> wrote:\n> >> diff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\n> >> index 089fa4cedc..35b0be7d40 100644\n> >> --- a/Documentation/git-maintenance.txt\n> >> +++ b/Documentation/git-maintenance.txt\n> >> @@ -35,6 +35,24 @@ run::\n> >>  TASKS\n> >>  -----\n> >>\n> >> +commit-graph::\n> >> +       The `commit-graph` job updates the `commit-graph` files incrementally,\n> >> +       then verifies that the written data is correct. If the new layer has an\n> >> +       issue, then the chain file is removed and the `commit-graph` is\n> >> +       rewritten from scratch.\n> >> ++\n> >> +The verification only checks the top layer of the `commit-graph` chain.\n> >> +If the incremental write merged the new commits with at least one\n> >> +existing layer, then there is potential for on-disk corruption being\n> >> +carried forward into the new file. This will be noticed and the new\n> >> +commit-graph file will be clean as Git reparses the commit data from\n> >> +the object database.\n> >\n> > This reads somewhat awkwardly. I think what you mean is something like\n> > \"is there a risk for on-disk corruption? yes, but no: we're clever\n> > enough to detect it and avoid it\". So from a user's point of view, I\n> > think this is too detailed.\n\n[snip quite a bit]\n\n> Back to the point of your comments: I'm not sure this second paragraph\n> is required at all in the documentation. The first paragraph already\n> says:\n>\n>         ...then verifies that the written data is correct.\n>\n> This \"written data\" _is_ the top layer of the chain. There is probably\n> no reason to dig deeper into _why_ we do this in this user-facing\n> documentation.\n>\n> So, I propose just deleting this paragraph. What do you think?\n\nYeah, that makes lots of sense. Thanks!\n\n\nMartin\n"},{"id":"403934","messageId":"pull.695.v2.git.1597760589.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.git.1596728921.gitgitgadget@gmail.com","subject":"[PATCH v2 00/11] Maintenance I: Command, gc and commit-graph tasks","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-18T14:22:57Z","receivedAt":"2020-08-18T14:23:31Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"This series is based on jk/strvec.\n\nThis patch series contains 11patches that were going to be part of v4 of\nds/maintenance [1], but the discussion has gotten really long. To help focus\nthe conversation, I'm splitting out the portions that create and test the\n'maintenance' builtin from the additional tasks (prefetch, loose-objects,\nincremental-repack) that can be brought in later.\n\n[1] \nhttps://lore.kernel.org/git/pull.671.git.1594131695.gitgitgadget@gmail.com/\n\nAs mentioned before, git gc already plays the role of maintaining Git\nrepositories. It has accumulated several smaller pieces in its long history,\nincluding:\n\n 1. Repacking all reachable objects into one pack-file (and deleting\n    unreachable objects).\n 2. Packing refs.\n 3. Expiring reflogs.\n 4. Clearing rerere logs.\n 5. Updating the commit-graph file.\n 6. Pruning worktrees.\n\nWhile expiring reflogs, clearing rererelogs, and deleting unreachable\nobjects are suitable under the guise of \"garbage collection\", packing refs\nand updating the commit-graph file are not as obviously fitting. Further,\nthese operations are \"all or nothing\" in that they rewrite almost all\nrepository data, which does not perform well at extremely large scales.\nThese operations can also be disruptive to foreground Git commands when git\ngc --auto triggers during routine use.\n\nThis series does not intend to change what git gc does, but instead create\nnew choices for automatic maintenance activities, of which git gc remains\nthe only one enabled by default.\n\nThe new maintenance tasks are:\n\n * 'commit-graph' : write and verify a single layer of an incremental\n   commit-graph.\n * 'loose-objects' : prune packed loose objects, then create a new pack from\n   a batch of loose objects.\n * 'pack-files' : expire redundant packs from the multi-pack-index, then\n   repack using the multi-pack-index's incremental repack strategy.\n * 'prefetch' : fetch from each remote, storing the refs in 'refs/prefetch/\n   /'.\n\nThe only included tasks are the 'gc' and 'commit-graph' tasks. The rest will\nfollow in a follow-up series. Including the 'commit-graph' task here allows\nus to build and test features like config settings and the --task= \ncommand-line argument.\n\nThese tasks are all disabled by default, but can be enabled with config\noptions or run explicitly using \"git maintenance run --task=\". There are\nadditional config options to allow customizing the conditions for which the\ntasks run during the '--auto' option.\n\n Because 'gc' is implemented as a maintenance task, the most dramatic change\nof this series is to convert the 'git gc --auto' calls into 'git maintenance\nrun --auto' calls at the end of some Git commands. By default, the only\nchange is that 'git gc --auto' will be run below an additional 'git\nmaintenance' process.\n\nThe 'git maintenance' builtin has a 'run' subcommand so it can be extended\nlater with subcommands that manage background maintenance, such as 'start'\nor 'stop'. These are not the subject of this series, as it is important to\nfocus on the maintenance activities themselves.\n\nUpdates since v1 (of this series)\n=================================\n\n * Documentation fixes.\n   \n   \n * The builtin code had some slight tweaks in PATCH 1.\n   \n   \n\nUPDATES since v3 of [1]\n=======================\n\n * The biggest change here is the use of \"test_subcommand\", based on\n   Jonathan Nieder's approach. This requires having the exact command-line\n   figured out, which now requires spelling out all --no- [quiet%7Cprogress] \n   options. I also added a bunch of \"2>/dev/null\" checks because of the\n   isatty(2) calls. Without that, the behavior will change depending on\n   whether the test is run with -x/-v or without.\n   \n   \n * The option parsing has changed to use a local struct and pass that struct\n   to the helper methods. This is instead of having a global singleton.\n   \n   \n\nThanks, -Stolee\n\nDerrick Stolee (11):\n  maintenance: create basic maintenance runner\n  maintenance: add --quiet option\n  maintenance: replace run_auto_gc()\n  maintenance: initialize task array\n  maintenance: add commit-graph task\n  maintenance: add --task option\n  maintenance: take a lock on the objects directory\n  maintenance: create maintenance.<task>.enabled config\n  maintenance: use pointers to check --auto\n  maintenance: add auto condition for commit-graph task\n  maintenance: add trace2 regions for task execution\n\n .gitignore                           |   1 +\n Documentation/config.txt             |   2 +\n Documentation/config/maintenance.txt |  14 ++\n Documentation/fetch-options.txt      |   6 +-\n Documentation/git-clone.txt          |   6 +-\n Documentation/git-maintenance.txt    |  79 ++++++\n builtin.h                            |   1 +\n builtin/am.c                         |   2 +-\n builtin/commit.c                     |   2 +-\n builtin/fetch.c                      |   6 +-\n builtin/gc.c                         | 354 +++++++++++++++++++++++++++\n builtin/merge.c                      |   2 +-\n builtin/rebase.c                     |   4 +-\n commit-graph.c                       |   8 +-\n commit-graph.h                       |   1 +\n git.c                                |   1 +\n object.h                             |   1 +\n run-command.c                        |  16 +-\n run-command.h                        |   2 +-\n t/t5510-fetch.sh                     |   2 +-\n t/t5514-fetch-multiple.sh            |   2 +-\n t/t7900-maintenance.sh               |  63 +++++\n t/test-lib-functions.sh              |  33 +++\n 23 files changed, 580 insertions(+), 28 deletions(-)\n create mode 100644 Documentation/config/maintenance.txt\n create mode 100644 Documentation/git-maintenance.txt\n create mode 100755 t/t7900-maintenance.sh\n\n\nbase-commit: d70a9eb611a9d242c1d26847d223b8677609305b\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-695%2Fderrickstolee%2Fmaintenance%2Fbuiltin-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-695/derrickstolee/maintenance/builtin-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/695\n\nRange-diff vs v1:\n\n  1:  2b9deb6d6a !  1:  e09e4a4a87 maintenance: create basic maintenance runner\n     @@ Documentation/git-maintenance.txt (new)\n      +-----------\n      +Run tasks to optimize Git repository data, speeding up other Git commands\n      +and reducing storage requirements for the repository.\n     -++\n     ++\n      +Git commands that add repository data, such as `git add` or `git fetch`,\n      +are optimized for a responsive user experience. These commands do not take\n      +time to optimize the Git data, since such optimizations scale with the full\n      +size of the repository while these user commands each perform a relatively\n      +small action.\n     -++\n     ++\n      +The `git maintenance` command provides flexibility for how to optimize the\n      +Git repository.\n      +\n     @@ Documentation/git-maintenance.txt (new)\n      +-----\n      +\n      +gc::\n     -+\tCleanup unnecessary files and optimize the local repository. \"GC\"\n     ++\tClean up unnecessary files and optimize the local repository. \"GC\"\n      +\tstands for \"garbage collection,\" but this task performs many\n     -+\tsmaller tasks. This task can be rather expensive for large\n     -+\trepositories, as it repacks all Git objects into a single pack-file.\n     -+\tIt can also be disruptive in some situations, as it deletes stale\n     -+\tdata.\n     ++\tsmaller tasks. This task can be expensive for large repositories,\n     ++\tas it repacks all Git objects into a single pack-file. It can also\n     ++\tbe disruptive in some situations, as it deletes stale data. See\n     ++\tlinkgit:git-gc[1] for more details on garbage collection in Git.\n      +\n      +OPTIONS\n      +-------\n     @@ builtin/gc.c: int cmd_gc(int argc, const char **argv, const char *prefix)\n      +\n      +int cmd_maintenance(int argc, const char **argv, const char *prefix)\n      +{\n     -+\tstatic struct maintenance_opts opts;\n     -+\tstatic struct option builtin_maintenance_options[] = {\n     ++\tstruct maintenance_opts opts;\n     ++\tstruct option builtin_maintenance_options[] = {\n      +\t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n      +\t\t\t N_(\"run tasks based on the state of the repository\")),\n      +\t\tOPT_END()\n     @@ builtin/gc.c: int cmd_gc(int argc, const char **argv, const char *prefix)\n      +\t\t\t     builtin_maintenance_usage,\n      +\t\t\t     PARSE_OPT_KEEP_UNKNOWN);\n      +\n     -+\tif (argc == 1) {\n     -+\t\tif (!strcmp(argv[0], \"run\"))\n     -+\t\t\treturn maintenance_run(&opts);\n     -+\t}\n     ++\tif (argc != 1)\n     ++\t\tusage_with_options(builtin_maintenance_usage,\n     ++\t\t\t\t   builtin_maintenance_options);\n     ++\n     ++\tif (!strcmp(argv[0], \"run\"))\n     ++\t\treturn maintenance_run(&opts);\n      +\n     -+\tusage_with_options(builtin_maintenance_usage,\n     -+\t\t\t   builtin_maintenance_options);\n     ++\tdie(_(\"invalid subcommand: %s\"), argv[0]);\n      +}\n      \n       ## git.c ##\n     @@ t/t7900-maintenance.sh (new)\n      +\n      +test_expect_success 'help text' '\n      +\ttest_expect_code 129 git maintenance -h 2>err &&\n     -+\ttest_i18ngrep \"usage: git maintenance run\" err\n     ++\ttest_i18ngrep \"usage: git maintenance run\" err &&\n     ++\ttest_expect_code 128 git maintenance barf 2>err &&\n     ++\ttest_i18ngrep \"invalid subcommand: barf\" err\n      +'\n      +\n      +test_expect_success 'run [--auto]' '\n  2:  d5faef26af !  2:  adae48d235 maintenance: add --quiet option\n     @@ builtin/gc.c: static int maintenance_task_gc(struct maintenance_opts *opts)\n       \tclose_object_store(the_repository->objects);\n       \treturn run_command(&child);\n      @@ builtin/gc.c: int cmd_maintenance(int argc, const char **argv, const char *prefix)\n     - \tstatic struct option builtin_maintenance_options[] = {\n     + \tstruct option builtin_maintenance_options[] = {\n       \t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n       \t\t\t N_(\"run tasks based on the state of the repository\")),\n      +\t\tOPT_BOOL(0, \"quiet\", &opts.quiet,\n     @@ builtin/gc.c: int cmd_maintenance(int argc, const char **argv, const char *prefi\n      \n       ## t/t7900-maintenance.sh ##\n      @@ t/t7900-maintenance.sh: test_expect_success 'help text' '\n     - \ttest_i18ngrep \"usage: git maintenance run\" err\n     + \ttest_i18ngrep \"invalid subcommand: barf\" err\n       '\n       \n      -test_expect_success 'run [--auto]' '\n  3:  233811310b =  3:  91741a0cfc maintenance: replace run_auto_gc()\n  4:  7efa23abc8 =  4:  1db3b96280 maintenance: initialize task array\n  5:  902b742032 !  5:  50b457fd57 maintenance: add commit-graph task\n     @@ Documentation/git-maintenance.txt: run::\n      +\tissue, then the chain file is removed and the `commit-graph` is\n      +\trewritten from scratch.\n      ++\n     -+The verification only checks the top layer of the `commit-graph` chain.\n     -+If the incremental write merged the new commits with at least one\n     -+existing layer, then there is potential for on-disk corruption being\n     -+carried forward into the new file. This will be noticed and the new\n     -+commit-graph file will be clean as Git reparses the commit data from\n     -+the object database.\n     -++\n      +The incremental write is safe to run alongside concurrent Git processes\n      +since it will not expire `.graph` files that were in the previous\n      +`commit-graph-chain` file. They will be deleted by a later run based on\n      +the expiration delay.\n      +\n       gc::\n     - \tCleanup unnecessary files and optimize the local repository. \"GC\"\n     + \tClean up unnecessary files and optimize the local repository. \"GC\"\n       \tstands for \"garbage collection,\" but this task performs many\n      \n       ## builtin/gc.c ##\n     @@ t/t7900-maintenance.sh: test_description='git maintenance builtin'\n      +\n       test_expect_success 'help text' '\n       \ttest_expect_code 129 git maintenance -h 2>err &&\n     - \ttest_i18ngrep \"usage: git maintenance run\" err\n     + \ttest_i18ngrep \"usage: git maintenance run\" err &&\n  6:  dddbcc4f3d !  6:  85268bd53e maintenance: add --task option\n     @@ builtin/gc.c: static int maintenance_run(struct maintenance_opts *opts)\n      +\n       int cmd_maintenance(int argc, const char **argv, const char *prefix)\n       {\n     - \tstatic struct maintenance_opts opts;\n     + \tstruct maintenance_opts opts;\n      @@ builtin/gc.c: int cmd_maintenance(int argc, const char **argv, const char *prefix)\n       \t\t\t N_(\"run tasks based on the state of the repository\")),\n       \t\tOPT_BOOL(0, \"quiet\", &opts.quiet,\n  7:  79af39be13 =  7:  6f86cfaa94 maintenance: take a lock on the objects directory\n  8:  69bfc6a4b2 =  8:  5c0f9d69d1 maintenance: create maintenance.<task>.enabled config\n  9:  df21bbb000 =  9:  68bf5bef4b maintenance: use pointers to check --auto\n 10:  e67e259aef = 10:  fc097c389a maintenance: add auto condition for commit-graph task\n 11:  a5d1914846 = 11:  46fbe161aa maintenance: add trace2 regions for task execution\n\n-- \ngitgitgadget\n"},{"id":"403935","messageId":"adae48d235aecf3c2f6e709a2855b9066a9a9e38.1597760589.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v2.git.1597760589.gitgitgadget@gmail.com","subject":"[PATCH v2 02/11] maintenance: add --quiet option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-18T14:22:59Z","receivedAt":"2020-08-18T14:23:32Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nMaintenance activities are commonly used as steps in larger scripts.\nProviding a '--quiet' option allows those scripts to be less noisy when\nrun on a terminal window. Turn this mode on by default when stderr is\nnot a terminal.\n\nPipe the option to the 'git gc' child process.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-maintenance.txt |  3 +++\n builtin/gc.c                      |  9 +++++++++\n t/t7900-maintenance.sh            | 15 ++++++++++-----\n 3 files changed, 22 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nindex ff47fb3641..04fa0fe329 100644\n--- a/Documentation/git-maintenance.txt\n+++ b/Documentation/git-maintenance.txt\n@@ -52,6 +52,9 @@ OPTIONS\n \tin the `gc.auto` config setting, or when the number of pack-files\n \texceeds the `gc.autoPackLimit` config setting.\n \n+--quiet::\n+\tDo not report progress or other information over `stderr`.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex ce91c754ee..b75ddb780b 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -707,6 +707,7 @@ static const char * const builtin_maintenance_usage[] = {\n \n struct maintenance_opts {\n \tint auto_flag;\n+\tint quiet;\n };\n \n static int maintenance_task_gc(struct maintenance_opts *opts)\n@@ -718,6 +719,10 @@ static int maintenance_task_gc(struct maintenance_opts *opts)\n \n \tif (opts->auto_flag)\n \t\tstrvec_push(&child.args, \"--auto\");\n+\tif (opts->quiet)\n+\t\tstrvec_push(&child.args, \"--quiet\");\n+\telse\n+\t\tstrvec_push(&child.args, \"--no-quiet\");\n \n \tclose_object_store(the_repository->objects);\n \treturn run_command(&child);\n@@ -734,6 +739,8 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix)\n \tstruct option builtin_maintenance_options[] = {\n \t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n \t\t\t N_(\"run tasks based on the state of the repository\")),\n+\t\tOPT_BOOL(0, \"quiet\", &opts.quiet,\n+\t\t\t N_(\"do not report progress or other information over stderr\")),\n \t\tOPT_END()\n \t};\n \n@@ -743,6 +750,8 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(builtin_maintenance_usage,\n \t\t\t\t   builtin_maintenance_options);\n \n+\topts.quiet = !isatty(2);\n+\n \targc = parse_options(argc, argv, prefix,\n \t\t\t     builtin_maintenance_options,\n \t\t\t     builtin_maintenance_usage,\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex 14aa691f19..47d512464c 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -11,11 +11,16 @@ test_expect_success 'help text' '\n \ttest_i18ngrep \"invalid subcommand: barf\" err\n '\n \n-test_expect_success 'run [--auto]' '\n-\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-auto.txt\" git maintenance run &&\n-\tGIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" git maintenance run --auto &&\n-\ttest_subcommand git gc <run-no-auto.txt &&\n-\ttest_subcommand git gc --auto <run-auto.txt\n+test_expect_success 'run [--auto|--quiet]' '\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-auto.txt\" \\\n+\t\tgit maintenance run 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" \\\n+\t\tgit maintenance run --auto 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-quiet.txt\" \\\n+\t\tgit maintenance run --no-quiet 2>/dev/null &&\n+\ttest_subcommand git gc --quiet <run-no-auto.txt &&\n+\ttest_subcommand git gc --auto --quiet <run-auto.txt &&\n+\ttest_subcommand git gc --no-quiet <run-no-quiet.txt\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"403936","messageId":"e09e4a4a87c09c88ceacf56a3d298de7b5ad3274.1597760589.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v2.git.1597760589.gitgitgadget@gmail.com","subject":"[PATCH v2 01/11] maintenance: create basic maintenance runner","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-18T14:22:58Z","receivedAt":"2020-08-18T14:23:36Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe 'gc' builtin is our current entrypoint for automatically maintaining\na repository. This one tool does many operations, such as repacking the\nrepository, packing refs, and rewriting the commit-graph file. The name\nimplies it performs \"garbage collection\" which means several different\nthings, and some users may not want to use this operation that rewrites\nthe entire object database.\n\nCreate a new 'maintenance' builtin that will become a more general-\npurpose command. To start, it will only support the 'run' subcommand,\nbut will later expand to add subcommands for scheduling maintenance in\nthe background.\n\nFor now, the 'maintenance' builtin is a thin shim over the 'gc' builtin.\nIn fact, the only option is the '--auto' toggle, which is handed\ndirectly to the 'gc' builtin. The current change is isolated to this\nsimple operation to prevent more interesting logic from being lost in\nall of the boilerplate of adding a new builtin.\n\nUse existing builtin/gc.c file because we want to share code between the\ntwo builtins. It is possible that we will have 'maintenance' replace the\n'gc' builtin entirely at some point, leaving 'git gc' as an alias for\nsome specific arguments to 'git maintenance run'.\n\nCreate a new test_subcommand helper that allows us to test if a certain\nsubcommand was run. It requires storing the GIT_TRACE2_EVENT logs in a\nfile. A negation mode is available that will be used in later tests.\n\nHelped-by: Jonathan Nieder <jrnieder@gmail.com>\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n .gitignore                        |  1 +\n Documentation/git-maintenance.txt | 57 ++++++++++++++++++++++++++++++\n builtin.h                         |  1 +\n builtin/gc.c                      | 58 +++++++++++++++++++++++++++++++\n git.c                             |  1 +\n t/t7900-maintenance.sh            | 21 +++++++++++\n t/test-lib-functions.sh           | 33 ++++++++++++++++++\n 7 files changed, 172 insertions(+)\n create mode 100644 Documentation/git-maintenance.txt\n create mode 100755 t/t7900-maintenance.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex ee509a2ad2..a5808fa30d 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -90,6 +90,7 @@\n /git-ls-tree\n /git-mailinfo\n /git-mailsplit\n+/git-maintenance\n /git-merge\n /git-merge-base\n /git-merge-index\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nnew file mode 100644\nindex 0000000000..ff47fb3641\n--- /dev/null\n+++ b/Documentation/git-maintenance.txt\n@@ -0,0 +1,57 @@\n+git-maintenance(1)\n+==================\n+\n+NAME\n+----\n+git-maintenance - Run tasks to optimize Git repository data\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git maintenance' run [<options>]\n+\n+\n+DESCRIPTION\n+-----------\n+Run tasks to optimize Git repository data, speeding up other Git commands\n+and reducing storage requirements for the repository.\n+\n+Git commands that add repository data, such as `git add` or `git fetch`,\n+are optimized for a responsive user experience. These commands do not take\n+time to optimize the Git data, since such optimizations scale with the full\n+size of the repository while these user commands each perform a relatively\n+small action.\n+\n+The `git maintenance` command provides flexibility for how to optimize the\n+Git repository.\n+\n+SUBCOMMANDS\n+-----------\n+\n+run::\n+\tRun one or more maintenance tasks.\n+\n+TASKS\n+-----\n+\n+gc::\n+\tClean up unnecessary files and optimize the local repository. \"GC\"\n+\tstands for \"garbage collection,\" but this task performs many\n+\tsmaller tasks. This task can be expensive for large repositories,\n+\tas it repacks all Git objects into a single pack-file. It can also\n+\tbe disruptive in some situations, as it deletes stale data. See\n+\tlinkgit:git-gc[1] for more details on garbage collection in Git.\n+\n+OPTIONS\n+-------\n+--auto::\n+\tWhen combined with the `run` subcommand, run maintenance tasks\n+\tonly if certain thresholds are met. For example, the `gc` task\n+\truns when the number of loose objects exceeds the number stored\n+\tin the `gc.auto` config setting, or when the number of pack-files\n+\texceeds the `gc.autoPackLimit` config setting.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/builtin.h b/builtin.h\nindex a5ae15bfe5..17c1c0ce49 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -167,6 +167,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix);\n int cmd_ls_remote(int argc, const char **argv, const char *prefix);\n int cmd_mailinfo(int argc, const char **argv, const char *prefix);\n int cmd_mailsplit(int argc, const char **argv, const char *prefix);\n+int cmd_maintenance(int argc, const char **argv, const char *prefix);\n int cmd_merge(int argc, const char **argv, const char *prefix);\n int cmd_merge_base(int argc, const char **argv, const char *prefix);\n int cmd_merge_index(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex aafa0946f5..ce91c754ee 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -699,3 +699,61 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n \n \treturn 0;\n }\n+\n+static const char * const builtin_maintenance_usage[] = {\n+\tN_(\"git maintenance run [<options>]\"),\n+\tNULL\n+};\n+\n+struct maintenance_opts {\n+\tint auto_flag;\n+};\n+\n+static int maintenance_task_gc(struct maintenance_opts *opts)\n+{\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\n+\tchild.git_cmd = 1;\n+\tstrvec_push(&child.args, \"gc\");\n+\n+\tif (opts->auto_flag)\n+\t\tstrvec_push(&child.args, \"--auto\");\n+\n+\tclose_object_store(the_repository->objects);\n+\treturn run_command(&child);\n+}\n+\n+static int maintenance_run(struct maintenance_opts *opts)\n+{\n+\treturn maintenance_task_gc(opts);\n+}\n+\n+int cmd_maintenance(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct maintenance_opts opts;\n+\tstruct option builtin_maintenance_options[] = {\n+\t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n+\t\t\t N_(\"run tasks based on the state of the repository\")),\n+\t\tOPT_END()\n+\t};\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n+\t\tusage_with_options(builtin_maintenance_usage,\n+\t\t\t\t   builtin_maintenance_options);\n+\n+\targc = parse_options(argc, argv, prefix,\n+\t\t\t     builtin_maintenance_options,\n+\t\t\t     builtin_maintenance_usage,\n+\t\t\t     PARSE_OPT_KEEP_UNKNOWN);\n+\n+\tif (argc != 1)\n+\t\tusage_with_options(builtin_maintenance_usage,\n+\t\t\t\t   builtin_maintenance_options);\n+\n+\tif (!strcmp(argv[0], \"run\"))\n+\t\treturn maintenance_run(&opts);\n+\n+\tdie(_(\"invalid subcommand: %s\"), argv[0]);\n+}\ndiff --git a/git.c b/git.c\nindex 8bd1d7551d..24f250d29a 100644\n--- a/git.c\n+++ b/git.c\n@@ -529,6 +529,7 @@ static struct cmd_struct commands[] = {\n \t{ \"ls-tree\", cmd_ls_tree, RUN_SETUP },\n \t{ \"mailinfo\", cmd_mailinfo, RUN_SETUP_GENTLY | NO_PARSEOPT },\n \t{ \"mailsplit\", cmd_mailsplit, NO_PARSEOPT },\n+\t{ \"maintenance\", cmd_maintenance, RUN_SETUP_GENTLY | NO_PARSEOPT },\n \t{ \"merge\", cmd_merge, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"merge-base\", cmd_merge_base, RUN_SETUP },\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nnew file mode 100755\nindex 0000000000..14aa691f19\n--- /dev/null\n+++ b/t/t7900-maintenance.sh\n@@ -0,0 +1,21 @@\n+#!/bin/sh\n+\n+test_description='git maintenance builtin'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'help text' '\n+\ttest_expect_code 129 git maintenance -h 2>err &&\n+\ttest_i18ngrep \"usage: git maintenance run\" err &&\n+\ttest_expect_code 128 git maintenance barf 2>err &&\n+\ttest_i18ngrep \"invalid subcommand: barf\" err\n+'\n+\n+test_expect_success 'run [--auto]' '\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-auto.txt\" git maintenance run &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" git maintenance run --auto &&\n+\ttest_subcommand git gc <run-no-auto.txt &&\n+\ttest_subcommand git gc --auto <run-auto.txt\n+'\n+\n+test_done\ndiff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh\nindex 3103be8a32..0adf2b85f8 100644\n--- a/t/test-lib-functions.sh\n+++ b/t/test-lib-functions.sh\n@@ -1561,3 +1561,36 @@ test_path_is_hidden () {\n \tcase \"$(\"$SYSTEMROOT\"/system32/attrib \"$1\")\" in *H*?:*) return 0;; esac\n \treturn 1\n }\n+\n+# Check that the given command was invoked as part of the\n+# trace2-format trace on stdin.\n+#\n+#\ttest_subcommand [!] <command> <args>... < <trace>\n+#\n+# For example, to look for an invocation of \"git upload-pack\n+# /path/to/repo\"\n+#\n+#\tGIT_TRACE2_EVENT=event.log git fetch ... &&\n+#\ttest_subcommand git upload-pack \"$PATH\" <event.log\n+#\n+# If the first parameter passed is !, this instead checks that\n+# the given command was not called.\n+#\n+test_subcommand () {\n+\tlocal negate=\n+\tif test \"$1\" = \"!\"\n+\tthen\n+\t\tnegate=t\n+\t\tshift\n+\tfi\n+\n+\tlocal expr=$(printf '\"%s\",' \"$@\")\n+\texpr=\"${expr%,}\"\n+\n+\tif test -n \"$negate\"\n+\tthen\n+\t\t! grep \"\\[$expr\\]\"\n+\telse\n+\t\tgrep \"\\[$expr\\]\"\n+\tfi\n+}\n-- \ngitgitgadget\n\n"},{"id":"403937","messageId":"91741a0cfc44009f6792c8d3d8d7cc086b1a0669.1597760589.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v2.git.1597760589.gitgitgadget@gmail.com","subject":"[PATCH v2 03/11] maintenance: replace run_auto_gc()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-18T14:23:00Z","receivedAt":"2020-08-18T14:23:43Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe run_auto_gc() method is used in several places to trigger a check\nfor repo maintenance after some Git commands, such as 'git commit' or\n'git fetch'.\n\nTo allow for extra customization of this maintenance activity, replace\nthe 'git gc --auto [--quiet]' call with one to 'git maintenance run\n--auto [--quiet]'. As we extend the maintenance builtin with other\nsteps, users will be able to select different maintenance activities.\n\nRename run_auto_gc() to run_auto_maintenance() to be clearer what is\nhappening on this call, and to expose all callers in the current diff.\nRewrite the method to use a struct child_process to simplify the calls\nslightly.\n\nSince 'git fetch' already allows disabling the 'git gc --auto'\nsubprocess, add an equivalent option with a different name to be more\ndescriptive of the new behavior: '--[no-]maintenance'. Update the\ndocumentation to include these options at the same time.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/fetch-options.txt |  6 ++++--\n Documentation/git-clone.txt     |  6 +++---\n builtin/am.c                    |  2 +-\n builtin/commit.c                |  2 +-\n builtin/fetch.c                 |  6 ++++--\n builtin/merge.c                 |  2 +-\n builtin/rebase.c                |  4 ++--\n run-command.c                   | 16 +++++++---------\n run-command.h                   |  2 +-\n t/t5510-fetch.sh                |  2 +-\n 10 files changed, 25 insertions(+), 23 deletions(-)\n\ndiff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt\nindex 6e2a160a47..495bc8ab5a 100644\n--- a/Documentation/fetch-options.txt\n+++ b/Documentation/fetch-options.txt\n@@ -86,9 +86,11 @@ ifndef::git-pull[]\n \tAllow several <repository> and <group> arguments to be\n \tspecified. No <refspec>s may be specified.\n \n+--[no-]auto-maintenance::\n --[no-]auto-gc::\n-\tRun `git gc --auto` at the end to perform garbage collection\n-\tif needed. This is enabled by default.\n+\tRun `git maintenance run --auto` at the end to perform automatic\n+\trepository maintenance if needed. (`--[no-]auto-gc` is a synonym.)\n+\tThis is enabled by default.\n \n --[no-]write-commit-graph::\n \tWrite a commit-graph after fetching. This overrides the config\ndiff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt\nindex c898310099..097e6a86c5 100644\n--- a/Documentation/git-clone.txt\n+++ b/Documentation/git-clone.txt\n@@ -78,9 +78,9 @@ repository using this option and then delete branches (or use any\n other Git command that makes any existing commit unreferenced) in the\n source repository, some objects may become unreferenced (or dangling).\n These objects may be removed by normal Git operations (such as `git commit`)\n-which automatically call `git gc --auto`. (See linkgit:git-gc[1].)\n-If these objects are removed and were referenced by the cloned repository,\n-then the cloned repository will become corrupt.\n+which automatically call `git maintenance run --auto`. (See\n+linkgit:git-maintenance[1].) If these objects are removed and were referenced\n+by the cloned repository, then the cloned repository will become corrupt.\n +\n Note that running `git repack` without the `--local` option in a repository\n cloned with `--shared` will copy objects from the source repository into a pack\ndiff --git a/builtin/am.c b/builtin/am.c\nindex dd4e6c2d9b..68e9d17379 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1795,7 +1795,7 @@ static void am_run(struct am_state *state, int resume)\n \tif (!state->rebasing) {\n \t\tam_destroy(state);\n \t\tclose_object_store(the_repository->objects);\n-\t\trun_auto_gc(state->quiet);\n+\t\trun_auto_maintenance(state->quiet);\n \t}\n }\n \ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 69ac78d5e5..f9b0a0c05d 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1702,7 +1702,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \tgit_test_write_commit_graph_or_die();\n \n \trepo_rerere(the_repository, 0);\n-\trun_auto_gc(quiet);\n+\trun_auto_maintenance(quiet);\n \trun_commit_hook(use_editor, get_index_file(), \"post-commit\", NULL);\n \tif (amend && !no_post_rewrite) {\n \t\tcommit_post_rewrite(the_repository, current_head, &oid);\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex c49f0e9752..c8b9366d3c 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -196,8 +196,10 @@ static struct option builtin_fetch_options[] = {\n \tOPT_STRING_LIST(0, \"negotiation-tip\", &negotiation_tip, N_(\"revision\"),\n \t\t\tN_(\"report that we have only objects reachable from this object\")),\n \tOPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),\n+\tOPT_BOOL(0, \"auto-maintenance\", &enable_auto_gc,\n+\t\t N_(\"run 'maintenance --auto' after fetching\")),\n \tOPT_BOOL(0, \"auto-gc\", &enable_auto_gc,\n-\t\t N_(\"run 'gc --auto' after fetching\")),\n+\t\t N_(\"run 'maintenance --auto' after fetching\")),\n \tOPT_BOOL(0, \"show-forced-updates\", &fetch_show_forced_updates,\n \t\t N_(\"check for forced-updates on all updated branches\")),\n \tOPT_BOOL(0, \"write-commit-graph\", &fetch_write_commit_graph,\n@@ -1882,7 +1884,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)\n \tclose_object_store(the_repository->objects);\n \n \tif (enable_auto_gc)\n-\t\trun_auto_gc(verbosity < 0);\n+\t\trun_auto_maintenance(verbosity < 0);\n \n \treturn result;\n }\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 7da707bf55..c068e73037 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -457,7 +457,7 @@ static void finish(struct commit *head_commit,\n \t\t\t * user should see them.\n \t\t\t */\n \t\t\tclose_object_store(the_repository->objects);\n-\t\t\trun_auto_gc(verbosity < 0);\n+\t\t\trun_auto_maintenance(verbosity < 0);\n \t\t}\n \t}\n \tif (new_head && show_diffstat) {\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex dadb52fa92..9ffba9009d 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -728,10 +728,10 @@ static int finish_rebase(struct rebase_options *opts)\n \tapply_autostash(state_dir_path(\"autostash\", opts));\n \tclose_object_store(the_repository->objects);\n \t/*\n-\t * We ignore errors in 'gc --auto', since the\n+\t * We ignore errors in 'git maintenance run --auto', since the\n \t * user should see them.\n \t */\n-\trun_auto_gc(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE)));\n+\trun_auto_maintenance(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE)));\n \tif (opts->type == REBASE_MERGE) {\n \t\tstruct replay_opts replay = REPLAY_OPTS_INIT;\n \ndiff --git a/run-command.c b/run-command.c\nindex cc9c3296ba..2ee59acdc8 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -1866,15 +1866,13 @@ int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,\n \treturn result;\n }\n \n-int run_auto_gc(int quiet)\n+int run_auto_maintenance(int quiet)\n {\n-\tstruct strvec argv_gc_auto = STRVEC_INIT;\n-\tint status;\n+\tstruct child_process maint = CHILD_PROCESS_INIT;\n \n-\tstrvec_pushl(&argv_gc_auto, \"gc\", \"--auto\", NULL);\n-\tif (quiet)\n-\t\tstrvec_push(&argv_gc_auto, \"--quiet\");\n-\tstatus = run_command_v_opt(argv_gc_auto.v, RUN_GIT_CMD);\n-\tstrvec_clear(&argv_gc_auto);\n-\treturn status;\n+\tmaint.git_cmd = 1;\n+\tstrvec_pushl(&maint.args, \"maintenance\", \"run\", \"--auto\", NULL);\n+\tstrvec_push(&maint.args, quiet ? \"--quiet\" : \"--no-quiet\");\n+\n+\treturn run_command(&maint);\n }\ndiff --git a/run-command.h b/run-command.h\nindex 8b9bfaef16..6472b38bde 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -221,7 +221,7 @@ int run_hook_ve(const char *const *env, const char *name, va_list args);\n /*\n  * Trigger an auto-gc\n  */\n-int run_auto_gc(int quiet);\n+int run_auto_maintenance(int quiet);\n \n #define RUN_COMMAND_NO_STDIN 1\n #define RUN_GIT_CMD\t     2\t/*If this is to be git sub-command */\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex a66dbe0bde..9850ecde5d 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -919,7 +919,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '\n \t\tgit config fetch.unpackLimit 1 &&\n \t\tgit config gc.autoPackLimit 1 &&\n \t\tgit config gc.autoDetach false &&\n-\t\tGIT_ASK_YESNO=\"$D/askyesno\" git fetch >fetch.out 2>&1 &&\n+\t\tGIT_ASK_YESNO=\"$D/askyesno\" git fetch --verbose >fetch.out 2>&1 &&\n \t\ttest_i18ngrep \"Auto packing the repository\" fetch.out &&\n \t\t! grep \"Should I try again\" fetch.out\n \t)\n-- \ngitgitgadget\n\n"},{"id":"403938","messageId":"1db3b96280d9bf7908b7dc9fa13b80c445164a99.1597760589.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v2.git.1597760589.gitgitgadget@gmail.com","subject":"[PATCH v2 04/11] maintenance: initialize task array","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-18T14:23:01Z","receivedAt":"2020-08-18T14:23:45Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nIn anticipation of implementing multiple maintenance tasks inside the\n'maintenance' builtin, use a list of structs to describe the work to be\ndone.\n\nThe struct maintenance_task stores the name of the task (as given by a\nfuture command-line argument) along with a function pointer to its\nimplementation and a boolean for whether the step is enabled.\n\nA list these structs are initialized with the full list of implemented\ntasks along with a default order. For now, this list only contains the\n\"gc\" task. This task is also the only task enabled by default.\n\nThe run subcommand will return a nonzero exit code if any task fails.\nHowever, it will attempt all tasks in its loop before returning with the\nfailure. Also each failed task will send an error message.\n\nHelped-by: Taylor Blau <me@ttaylorr.com>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/gc.c | 38 +++++++++++++++++++++++++++++++++++++-\n 1 file changed, 37 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex b75ddb780b..946d871d54 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -728,9 +728,45 @@ static int maintenance_task_gc(struct maintenance_opts *opts)\n \treturn run_command(&child);\n }\n \n+typedef int maintenance_task_fn(struct maintenance_opts *opts);\n+\n+struct maintenance_task {\n+\tconst char *name;\n+\tmaintenance_task_fn *fn;\n+\tunsigned enabled:1;\n+};\n+\n+enum maintenance_task_label {\n+\tTASK_GC,\n+\n+\t/* Leave as final value */\n+\tTASK__COUNT\n+};\n+\n+static struct maintenance_task tasks[] = {\n+\t[TASK_GC] = {\n+\t\t\"gc\",\n+\t\tmaintenance_task_gc,\n+\t\t1,\n+\t},\n+};\n+\n static int maintenance_run(struct maintenance_opts *opts)\n {\n-\treturn maintenance_task_gc(opts);\n+\tint i;\n+\tint result = 0;\n+\n+\tfor (i = 0; i < TASK__COUNT; i++) {\n+\t\tif (!tasks[i].enabled)\n+\t\t\tcontinue;\n+\n+\t\tif (tasks[i].fn(opts)) {\n+\t\t\terror(_(\"task '%s' failed\"), tasks[i].name);\n+\t\t\tresult = 1;\n+\t\t}\n+\t}\n+\n+\treturn result;\n }\n \n int cmd_maintenance(int argc, const char **argv, const char *prefix)\n-- \ngitgitgadget\n\n"},{"id":"403939","messageId":"85268bd53ef7f4e7b3d97a8ae091290e8e74441d.1597760589.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v2.git.1597760589.gitgitgadget@gmail.com","subject":"[PATCH v2 06/11] maintenance: add --task option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-18T14:23:03Z","receivedAt":"2020-08-18T14:23:47Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nA user may want to only run certain maintenance tasks in a certain\norder. Add the --task=<task> option, which allows a user to specify an\nordered list of tasks to run. These cannot be run multiple times,\nhowever.\n\nHere is where our array of maintenance_task pointers becomes critical.\nWe can sort the array of pointers based on the task order, but we do not\nwant to move the struct data itself in order to preserve the hashmap\nreferences. We use the hashmap to match the --task=<task> arguments into\nthe task struct data.\n\nKeep in mind that the 'enabled' member of the maintenance_task struct is\na placeholder for a future 'maintenance.<task>.enabled' config option.\nThus, we use the 'enabled' member to specify which tasks are run when\nthe user does not specify any --task=<task> arguments. The 'enabled'\nmember should be ignored if --task=<task> appears.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-maintenance.txt |  8 +++-\n builtin/gc.c                      | 61 +++++++++++++++++++++++++++++--\n t/t7900-maintenance.sh            | 27 ++++++++++++++\n 3 files changed, 92 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nindex c816fa1dcd..cf59eb258c 100644\n--- a/Documentation/git-maintenance.txt\n+++ b/Documentation/git-maintenance.txt\n@@ -30,7 +30,9 @@ SUBCOMMANDS\n -----------\n \n run::\n-\tRun one or more maintenance tasks.\n+\tRun one or more maintenance tasks. If one or more `--task=<task>`\n+\toptions are specified, then those tasks are run in the provided\n+\torder. Otherwise, only the `gc` task is run.\n \n TASKS\n -----\n@@ -66,6 +68,10 @@ OPTIONS\n --quiet::\n \tDo not report progress or other information over `stderr`.\n \n+--task=<task>::\n+\tIf this option is specified one or more times, then only run the\n+\tspecified tasks in the specified order.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 4f9352b9d0..0231d2f8c1 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -791,7 +791,9 @@ typedef int maintenance_task_fn(struct maintenance_opts *opts);\n struct maintenance_task {\n \tconst char *name;\n \tmaintenance_task_fn *fn;\n-\tunsigned enabled:1;\n+\tunsigned enabled:1,\n+\t\t selected:1;\n+\tint selected_order;\n };\n \n enum maintenance_task_label {\n@@ -814,13 +816,32 @@ static struct maintenance_task tasks[] = {\n \t},\n };\n \n+static int compare_tasks_by_selection(const void *a_, const void *b_)\n+{\n+\tconst struct maintenance_task *a, *b;\n+\n+\ta = (const struct maintenance_task *)&a_;\n+\tb = (const struct maintenance_task *)&b_;\n+\n+\treturn b->selected_order - a->selected_order;\n+}\n+\n static int maintenance_run(struct maintenance_opts *opts)\n {\n-\tint i;\n+\tint i, found_selected = 0;\n \tint result = 0;\n \n+\tfor (i = 0; !found_selected && i < TASK__COUNT; i++)\n+\t\tfound_selected = tasks[i].selected;\n+\n+\tif (found_selected)\n+\t\tQSORT(tasks, TASK__COUNT, compare_tasks_by_selection);\n+\n \tfor (i = 0; i < TASK__COUNT; i++) {\n-\t\tif (!tasks[i].enabled)\n+\t\tif (found_selected && !tasks[i].selected)\n+\t\t\tcontinue;\n+\n+\t\tif (!found_selected && !tasks[i].enabled)\n \t\t\tcontinue;\n \n \t\tif (tasks[i].fn(opts)) {\n@@ -832,6 +853,37 @@ static int maintenance_run(struct maintenance_opts *opts)\n \treturn result;\n }\n \n+static int task_option_parse(const struct option *opt,\n+\t\t\t     const char *arg, int unset)\n+{\n+\tint i, num_selected = 0;\n+\tstruct maintenance_task *task = NULL;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tfor (i = 0; i < TASK__COUNT; i++) {\n+\t\tnum_selected += tasks[i].selected;\n+\t\tif (!strcasecmp(tasks[i].name, arg)) {\n+\t\t\ttask = &tasks[i];\n+\t\t}\n+\t}\n+\n+\tif (!task) {\n+\t\terror(_(\"'%s' is not a valid task\"), arg);\n+\t\treturn 1;\n+\t}\n+\n+\tif (task->selected) {\n+\t\terror(_(\"task '%s' cannot be selected multiple times\"), arg);\n+\t\treturn 1;\n+\t}\n+\n+\ttask->selected = 1;\n+\ttask->selected_order = num_selected + 1;\n+\n+\treturn 0;\n+}\n+\n int cmd_maintenance(int argc, const char **argv, const char *prefix)\n {\n \tstruct maintenance_opts opts;\n@@ -840,6 +892,9 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix)\n \t\t\t N_(\"run tasks based on the state of the repository\")),\n \t\tOPT_BOOL(0, \"quiet\", &opts.quiet,\n \t\t\t N_(\"do not report progress or other information over stderr\")),\n+\t\tOPT_CALLBACK_F(0, \"task\", NULL, N_(\"task\"),\n+\t\t\tN_(\"run a specific task\"),\n+\t\t\tPARSE_OPT_NONEG, task_option_parse),\n \t\tOPT_END()\n \t};\n \ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex c0c4e6846e..792765aff7 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -25,4 +25,31 @@ test_expect_success 'run [--auto|--quiet]' '\n \ttest_subcommand git gc --no-quiet <run-no-quiet.txt\n '\n \n+test_expect_success 'run --task=<task>' '\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-commit-graph.txt\" \\\n+\t\tgit maintenance run --task=commit-graph 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-gc.txt\" \\\n+\t\tgit maintenance run --task=gc 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-commit-graph.txt\" \\\n+\t\tgit maintenance run --task=commit-graph 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-both.txt\" \\\n+\t\tgit maintenance run --task=commit-graph --task=gc 2>/dev/null &&\n+\ttest_subcommand ! git gc --quiet <run-commit-graph.txt &&\n+\ttest_subcommand git gc --quiet <run-gc.txt &&\n+\ttest_subcommand git gc --quiet <run-both.txt &&\n+\ttest_subcommand git commit-graph write --split --reachable --no-progress <run-commit-graph.txt &&\n+\ttest_subcommand ! git commit-graph write --split --reachable --no-progress <run-gc.txt &&\n+\ttest_subcommand git commit-graph write --split --reachable --no-progress <run-both.txt\n+'\n+\n+test_expect_success 'run --task=bogus' '\n+\ttest_must_fail git maintenance run --task=bogus 2>err &&\n+\ttest_i18ngrep \"is not a valid task\" err\n+'\n+\n+test_expect_success 'run --task duplicate' '\n+\ttest_must_fail git maintenance run --task=gc --task=gc 2>err &&\n+\ttest_i18ngrep \"cannot be selected multiple times\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"403940","messageId":"50b457fd57aef4e9ac6a15549561936dc962ef36.1597760589.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v2.git.1597760589.gitgitgadget@gmail.com","subject":"[PATCH v2 05/11] maintenance: add commit-graph task","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-18T14:23:02Z","receivedAt":"2020-08-18T14:23:50Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe first new task in the 'git maintenance' builtin is the\n'commit-graph' job. It is based on the sequence of events in the\n'commit-graph' job in Scalar [1]. This sequence is as follows:\n\n1. git commit-graph write --reachable --split\n2. git commit-graph verify --shallow\n3. If the verify succeeds, stop.\n4. Delete the commit-graph-chain file.\n5. git commit-graph write --reachable --split\n\nBy writing an incremental commit-graph file using the \"--split\"\noption we minimize the disruption from this operation. The default\nbehavior is to merge layers until the new \"top\" layer is less than\nhalf the size of the layer below. This provides quick writes most\nof the time, with the longer writes following a power law\ndistribution.\n\nMost importantly, concurrent Git processes only look at the\ncommit-graph-chain file for a very short amount of time, so they\nwill verly likely not be holding a handle to the file when we try\nto replace it. (This only matters on Windows.)\n\nIf a concurrent process reads the old commit-graph-chain file, but\nour job expires some of the .graph files before they can be read,\nthen those processes will see a warning message (but not fail).\nThis could be avoided by a future update to use the --expire-time\nargument when writing the commit-graph.\n\nBy using 'git commit-graph verify --shallow' we can ensure that\nthe file we just wrote is valid. This is an extra safety precaution\nthat is faster than our 'write' subcommand. In the rare situation\nthat the newest layer of the commit-graph is corrupt, we can \"fix\"\nthe corruption by deleting the commit-graph-chain file and rewrite\nthe full commit-graph as a new one-layer commit graph. This does\nnot completely prevent _that_ file from being corrupt, but it does\nrecompute the commit-graph by parsing commits from the object\ndatabase. In our use of this step in Scalar and VFS for Git, we\nhave only seen this issue arise because our microsoft/git fork\nreverted 43d3561 (\"commit-graph write: don't die if the existing\ngraph is corrupt\" 2019-03-25) for a while to keep commit-graph\nwrites very fast. We dropped the revert when updating to v2.23.0.\nThe verify still has potential for catching corrupt data across\nthe layer boundary: if the new file has commit X with parent Y\nin an old file but the commit ID for Y in the old file had a\nbitswap, then we will notice that in the 'verify' command.\n\n[1] https://github.com/microsoft/scalar/blob/master/Scalar.Common/Maintenance/CommitGraphStep.cs\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-maintenance.txt | 11 ++++++\n builtin/gc.c                      | 63 +++++++++++++++++++++++++++++++\n commit-graph.c                    |  8 ++--\n commit-graph.h                    |  1 +\n t/t7900-maintenance.sh            |  2 +\n 5 files changed, 81 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nindex 04fa0fe329..c816fa1dcd 100644\n--- a/Documentation/git-maintenance.txt\n+++ b/Documentation/git-maintenance.txt\n@@ -35,6 +35,17 @@ run::\n TASKS\n -----\n \n+commit-graph::\n+\tThe `commit-graph` job updates the `commit-graph` files incrementally,\n+\tthen verifies that the written data is correct. If the new layer has an\n+\tissue, then the chain file is removed and the `commit-graph` is\n+\trewritten from scratch.\n++\n+The incremental write is safe to run alongside concurrent Git processes\n+since it will not expire `.graph` files that were in the previous\n+`commit-graph-chain` file. They will be deleted by a later run based on\n+the expiration delay.\n+\n gc::\n \tClean up unnecessary files and optimize the local repository. \"GC\"\n \tstands for \"garbage collection,\" but this task performs many\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 946d871d54..4f9352b9d0 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -710,6 +710,64 @@ struct maintenance_opts {\n \tint quiet;\n };\n \n+static int run_write_commit_graph(struct maintenance_opts *opts)\n+{\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\n+\tchild.git_cmd = 1;\n+\tstrvec_pushl(&child.args, \"commit-graph\", \"write\",\n+\t\t     \"--split\", \"--reachable\", NULL);\n+\n+\tif (opts->quiet)\n+\t\tstrvec_push(&child.args, \"--no-progress\");\n+\n+\treturn !!run_command(&child);\n+}\n+\n+static int run_verify_commit_graph(struct maintenance_opts *opts)\n+{\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\n+\tchild.git_cmd = 1;\n+\tstrvec_pushl(&child.args, \"commit-graph\", \"verify\",\n+\t\t     \"--shallow\", NULL);\n+\n+\tif (opts->quiet)\n+\t\tstrvec_push(&child.args, \"--no-progress\");\n+\n+\treturn !!run_command(&child);\n+}\n+\n+static int maintenance_task_commit_graph(struct maintenance_opts *opts)\n+{\n+\tstruct repository *r = the_repository;\n+\tchar *chain_path;\n+\n+\tclose_object_store(r->objects);\n+\tif (run_write_commit_graph(opts)) {\n+\t\terror(_(\"failed to write commit-graph\"));\n+\t\treturn 1;\n+\t}\n+\n+\tif (!run_verify_commit_graph(opts))\n+\t\treturn 0;\n+\n+\twarning(_(\"commit-graph verify caught error, rewriting\"));\n+\n+\tchain_path = get_commit_graph_chain_filename(r->objects->odb);\n+\tif (unlink(chain_path)) {\n+\t\tUNLEAK(chain_path);\n+\t\tdie(_(\"failed to remove commit-graph at %s\"), chain_path);\n+\t}\n+\tfree(chain_path);\n+\n+\tif (!run_write_commit_graph(opts))\n+\t\treturn 0;\n+\n+\terror(_(\"failed to rewrite commit-graph\"));\n+\treturn 1;\n+}\n+\n static int maintenance_task_gc(struct maintenance_opts *opts)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n@@ -738,6 +796,7 @@ struct maintenance_task {\n \n enum maintenance_task_label {\n \tTASK_GC,\n+\tTASK_COMMIT_GRAPH,\n \n \t/* Leave as final value */\n \tTASK__COUNT\n@@ -749,6 +808,10 @@ static struct maintenance_task tasks[] = {\n \t\tmaintenance_task_gc,\n \t\t1,\n \t},\n+\t[TASK_COMMIT_GRAPH] = {\n+\t\t\"commit-graph\",\n+\t\tmaintenance_task_commit_graph,\n+\t},\n };\n \n static int maintenance_run(struct maintenance_opts *opts)\ndiff --git a/commit-graph.c b/commit-graph.c\nindex 1af68c297d..9705d237e4 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -172,7 +172,7 @@ static char *get_split_graph_filename(struct object_directory *odb,\n \t\t       oid_hex);\n }\n \n-static char *get_chain_filename(struct object_directory *odb)\n+char *get_commit_graph_chain_filename(struct object_directory *odb)\n {\n \treturn xstrfmt(\"%s/info/commit-graphs/commit-graph-chain\", odb->path);\n }\n@@ -521,7 +521,7 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r,\n \tstruct stat st;\n \tstruct object_id *oids;\n \tint i = 0, valid = 1, count;\n-\tchar *chain_name = get_chain_filename(odb);\n+\tchar *chain_name = get_commit_graph_chain_filename(odb);\n \tFILE *fp;\n \tint stat_res;\n \n@@ -1619,7 +1619,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)\n \t}\n \n \tif (ctx->split) {\n-\t\tchar *lock_name = get_chain_filename(ctx->odb);\n+\t\tchar *lock_name = get_commit_graph_chain_filename(ctx->odb);\n \n \t\thold_lock_file_for_update_mode(&lk, lock_name,\n \t\t\t\t\t       LOCK_DIE_ON_ERROR, 0444);\n@@ -1996,7 +1996,7 @@ static void expire_commit_graphs(struct write_commit_graph_context *ctx)\n \tif (ctx->split_opts && ctx->split_opts->expire_time)\n \t\texpire_time = ctx->split_opts->expire_time;\n \tif (!ctx->split) {\n-\t\tchar *chain_file_name = get_chain_filename(ctx->odb);\n+\t\tchar *chain_file_name = get_commit_graph_chain_filename(ctx->odb);\n \t\tunlink(chain_file_name);\n \t\tfree(chain_file_name);\n \t\tctx->num_commit_graphs_after = 0;\ndiff --git a/commit-graph.h b/commit-graph.h\nindex 28f89cdf3e..3c202748c3 100644\n--- a/commit-graph.h\n+++ b/commit-graph.h\n@@ -25,6 +25,7 @@ struct commit;\n struct bloom_filter_settings;\n \n char *get_commit_graph_filename(struct object_directory *odb);\n+char *get_commit_graph_chain_filename(struct object_directory *odb);\n int open_commit_graph(const char *graph_file, int *fd, struct stat *st);\n \n /*\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex 47d512464c..c0c4e6846e 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -4,6 +4,8 @@ test_description='git maintenance builtin'\n \n . ./test-lib.sh\n \n+GIT_TEST_COMMIT_GRAPH=0\n+\n test_expect_success 'help text' '\n \ttest_expect_code 129 git maintenance -h 2>err &&\n \ttest_i18ngrep \"usage: git maintenance run\" err &&\n-- \ngitgitgadget\n\n"},{"id":"403941","messageId":"5c0f9d69d18d51f19fcd76fca647c1df6711d70f.1597760589.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v2.git.1597760589.gitgitgadget@gmail.com","subject":"[PATCH v2 08/11] maintenance: create maintenance.<task>.enabled config","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-18T14:23:05Z","receivedAt":"2020-08-18T14:23:53Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nCurrently, a normal run of \"git maintenance run\" will only run the 'gc'\ntask, as it is the only one enabled. This is mostly for backwards-\ncompatible reasons since \"git maintenance run --auto\" commands replaced\nprevious \"git gc --auto\" commands after some Git processes. Users could\nmanually run specific maintenance tasks by calling \"git maintenance run\n--task=<task>\" directly.\n\nAllow users to customize which steps are run automatically using config.\nThe 'maintenance.<task>.enabled' option then can turn on these other\ntasks (or turn off the 'gc' task).\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config.txt             |  2 ++\n Documentation/config/maintenance.txt |  4 ++++\n Documentation/git-maintenance.txt    |  8 +++++---\n builtin/gc.c                         | 19 +++++++++++++++++++\n t/t7900-maintenance.sh               |  8 ++++++++\n 5 files changed, 38 insertions(+), 3 deletions(-)\n create mode 100644 Documentation/config/maintenance.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex ef0768b91a..2783b825f9 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -396,6 +396,8 @@ include::config/mailinfo.txt[]\n \n include::config/mailmap.txt[]\n \n+include::config/maintenance.txt[]\n+\n include::config/man.txt[]\n \n include::config/merge.txt[]\ndiff --git a/Documentation/config/maintenance.txt b/Documentation/config/maintenance.txt\nnew file mode 100644\nindex 0000000000..370cbfb42f\n--- /dev/null\n+++ b/Documentation/config/maintenance.txt\n@@ -0,0 +1,4 @@\n+maintenance.<task>.enabled::\n+\tThis boolean config option controls whether the maintenance task\n+\twith name `<task>` is run when no `--task` option is specified.\n+\tBy default, only `maintenance.gc.enabled` is true.\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nindex cf59eb258c..9af08c644f 100644\n--- a/Documentation/git-maintenance.txt\n+++ b/Documentation/git-maintenance.txt\n@@ -30,9 +30,11 @@ SUBCOMMANDS\n -----------\n \n run::\n-\tRun one or more maintenance tasks. If one or more `--task=<task>`\n-\toptions are specified, then those tasks are run in the provided\n-\torder. Otherwise, only the `gc` task is run.\n+\tRun one or more maintenance tasks. If one or more `--task` options\n+\tare specified, then those tasks are run in that order. Otherwise,\n+\tthe tasks are determined by which `maintenance.<task>.enabled`\n+\tconfig options are true. By default, only `maintenance.gc.enabled`\n+\tis true.\n \n TASKS\n -----\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex a47bf64623..e66326ede9 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -873,6 +873,24 @@ static int maintenance_run(struct maintenance_opts *opts)\n \treturn result;\n }\n \n+static void initialize_task_config(void)\n+{\n+\tint i;\n+\tstruct strbuf config_name = STRBUF_INIT;\n+\tfor (i = 0; i < TASK__COUNT; i++) {\n+\t\tint config_value;\n+\n+\t\tstrbuf_setlen(&config_name, 0);\n+\t\tstrbuf_addf(&config_name, \"maintenance.%s.enabled\",\n+\t\t\t    tasks[i].name);\n+\n+\t\tif (!git_config_get_bool(config_name.buf, &config_value))\n+\t\t\ttasks[i].enabled = config_value;\n+\t}\n+\n+\tstrbuf_release(&config_name);\n+}\n+\n static int task_option_parse(const struct option *opt,\n \t\t\t     const char *arg, int unset)\n {\n@@ -925,6 +943,7 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix)\n \t\t\t\t   builtin_maintenance_options);\n \n \topts.quiet = !isatty(2);\n+\tinitialize_task_config();\n \n \targc = parse_options(argc, argv, prefix,\n \t\t\t     builtin_maintenance_options,\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex 792765aff7..290abb7832 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -25,6 +25,14 @@ test_expect_success 'run [--auto|--quiet]' '\n \ttest_subcommand git gc --no-quiet <run-no-quiet.txt\n '\n \n+test_expect_success 'maintenance.<task>.enabled' '\n+\tgit config maintenance.gc.enabled false &&\n+\tgit config maintenance.commit-graph.enabled true &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-config.txt\" git maintenance run 2>err &&\n+\ttest_subcommand ! git gc --quiet <run-config.txt &&\n+\ttest_subcommand git commit-graph write --split --reachable --no-progress <run-config.txt\n+'\n+\n test_expect_success 'run --task=<task>' '\n \tGIT_TRACE2_EVENT=\"$(pwd)/run-commit-graph.txt\" \\\n \t\tgit maintenance run --task=commit-graph 2>/dev/null &&\n-- \ngitgitgadget\n\n"},{"id":"403942","messageId":"fc097c389a963065fc0bb5991bdebdca8824b121.1597760589.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v2.git.1597760589.gitgitgadget@gmail.com","subject":"[PATCH v2 10/11] maintenance: add auto condition for commit-graph task","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-18T14:23:07Z","receivedAt":"2020-08-18T14:24:00Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nInstead of writing a new commit-graph in every 'git maintenance run\n--auto' process (when maintenance.commit-graph.enalbed is configured to\nbe true), only write when there are \"enough\" commits not in a\ncommit-graph file.\n\nThis count is controlled by the maintenance.commit-graph.auto config\noption.\n\nTo compute the count, use a depth-first search starting at each ref, and\nleaving markers using the PARENT1 flag. If this count reaches the limit,\nthen terminate early and start the task. Otherwise, this operation will\npeel every ref and parse the commit it points to. If these are all in\nthe commit-graph, then this is typically a very fast operation. Users\nwith many refs might feel a slow-down, and hence could consider updating\ntheir limit to be very small. A negative value will force the step to\nrun every time.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/maintenance.txt | 10 ++++\n builtin/gc.c                         | 76 ++++++++++++++++++++++++++++\n object.h                             |  1 +\n 3 files changed, 87 insertions(+)\n\ndiff --git a/Documentation/config/maintenance.txt b/Documentation/config/maintenance.txt\nindex 370cbfb42f..9bd69b9df3 100644\n--- a/Documentation/config/maintenance.txt\n+++ b/Documentation/config/maintenance.txt\n@@ -2,3 +2,13 @@ maintenance.<task>.enabled::\n \tThis boolean config option controls whether the maintenance task\n \twith name `<task>` is run when no `--task` option is specified.\n \tBy default, only `maintenance.gc.enabled` is true.\n+\n+maintenance.commit-graph.auto::\n+\tThis integer config option controls how often the `commit-graph` task\n+\tshould be run as part of `git maintenance run --auto`. If zero, then\n+\tthe `commit-graph` task will not run with the `--auto` option. A\n+\tnegative value will force the task to run every time. Otherwise, a\n+\tpositive value implies the command should run when the number of\n+\treachable commits that are not in the commit-graph file is at least\n+\tthe value of `maintenance.commit-graph.auto`. The default value is\n+\t100.\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex c54c3070b8..73e485bfc0 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -28,6 +28,7 @@\n #include \"blob.h\"\n #include \"tree.h\"\n #include \"promisor-remote.h\"\n+#include \"refs.h\"\n \n #define FAILED_RUN \"failed to run %s\"\n \n@@ -710,6 +711,80 @@ struct maintenance_opts {\n \tint quiet;\n };\n \n+/* Remember to update object flag allocation in object.h */\n+#define PARENT1\t\t(1u<<16)\n+\n+static int num_commits_not_in_graph = 0;\n+static int limit_commits_not_in_graph = 100;\n+\n+static int dfs_on_ref(const char *refname,\n+\t\t      const struct object_id *oid, int flags,\n+\t\t      void *cb_data)\n+{\n+\tint result = 0;\n+\tstruct object_id peeled;\n+\tstruct commit_list *stack = NULL;\n+\tstruct commit *commit;\n+\n+\tif (!peel_ref(refname, &peeled))\n+\t\toid = &peeled;\n+\tif (oid_object_info(the_repository, oid, NULL) != OBJ_COMMIT)\n+\t\treturn 0;\n+\n+\tcommit = lookup_commit(the_repository, oid);\n+\tif (!commit)\n+\t\treturn 0;\n+\tif (parse_commit(commit))\n+\t\treturn 0;\n+\n+\tcommit_list_append(commit, &stack);\n+\n+\twhile (!result && stack) {\n+\t\tstruct commit_list *parent;\n+\n+\t\tcommit = pop_commit(&stack);\n+\n+\t\tfor (parent = commit->parents; parent; parent = parent->next) {\n+\t\t\tif (parse_commit(parent->item) ||\n+\t\t\t    commit_graph_position(parent->item) != COMMIT_NOT_FROM_GRAPH ||\n+\t\t\t    parent->item->object.flags & PARENT1)\n+\t\t\t\tcontinue;\n+\n+\t\t\tparent->item->object.flags |= PARENT1;\n+\t\t\tnum_commits_not_in_graph++;\n+\n+\t\t\tif (num_commits_not_in_graph >= limit_commits_not_in_graph) {\n+\t\t\t\tresult = 1;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tcommit_list_append(parent->item, &stack);\n+\t\t}\n+\t}\n+\n+\tfree_commit_list(stack);\n+\treturn result;\n+}\n+\n+static int should_write_commit_graph(void)\n+{\n+\tint result;\n+\n+\tgit_config_get_int(\"maintenance.commit-graph.auto\",\n+\t\t\t   &limit_commits_not_in_graph);\n+\n+\tif (!limit_commits_not_in_graph)\n+\t\treturn 0;\n+\tif (limit_commits_not_in_graph < 0)\n+\t\treturn 1;\n+\n+\tresult = for_each_ref(dfs_on_ref, NULL);\n+\n+\tclear_commit_marks_all(PARENT1);\n+\n+\treturn result;\n+}\n+\n static int run_write_commit_graph(struct maintenance_opts *opts)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n@@ -822,6 +897,7 @@ static struct maintenance_task tasks[] = {\n \t[TASK_COMMIT_GRAPH] = {\n \t\t\"commit-graph\",\n \t\tmaintenance_task_commit_graph,\n+\t\tshould_write_commit_graph,\n \t},\n };\n \ndiff --git a/object.h b/object.h\nindex 96a2105859..30d4e0f6a0 100644\n--- a/object.h\n+++ b/object.h\n@@ -74,6 +74,7 @@ struct object_array {\n  * list-objects-filter.c:                                      21\n  * builtin/fsck.c:           0--3\n  * builtin/index-pack.c:                                     2021\n+ * builtin/maintenance.c:                           16\n  * builtin/pack-objects.c:                                   20\n  * builtin/reflog.c:                   10--12\n  * builtin/show-branch.c:    0-------------------------------------------26\n-- \ngitgitgadget\n\n"},{"id":"403943","messageId":"46fbe161aa154de9406117ee916b1bd5f549905c.1597760589.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v2.git.1597760589.gitgitgadget@gmail.com","subject":"[PATCH v2 11/11] maintenance: add trace2 regions for task execution","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-18T14:23:08Z","receivedAt":"2020-08-18T14:24:00Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/gc.c | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 73e485bfc0..3fdb08655c 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -953,10 +953,12 @@ static int maintenance_run(struct maintenance_opts *opts)\n \t\t     !tasks[i].auto_condition()))\n \t\t\tcontinue;\n \n+\t\ttrace2_region_enter(\"maintenance\", tasks[i].name, r);\n \t\tif (tasks[i].fn(opts)) {\n \t\t\terror(_(\"task '%s' failed\"), tasks[i].name);\n \t\t\tresult = 1;\n \t\t}\n+\t\ttrace2_region_leave(\"maintenance\", tasks[i].name, r);\n \t}\n \n \trollback_lock_file(&lk);\n-- \ngitgitgadget\n"},{"id":"403944","messageId":"68bf5bef4bebea48234b6f5c75c7a07d38ca15c0.1597760589.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v2.git.1597760589.gitgitgadget@gmail.com","subject":"[PATCH v2 09/11] maintenance: use pointers to check --auto","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-18T14:23:06Z","receivedAt":"2020-08-18T14:24:01Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe 'git maintenance run' command has an '--auto' option. This is used\nby other Git commands such as 'git commit' or 'git fetch' to check if\nmaintenance should be run after adding data to the repository.\n\nPreviously, this --auto option was only used to add the argument to the\n'git gc' command as part of the 'gc' task. We will be expanding the\nother tasks to perform a check to see if they should do work as part of\nthe --auto flag, when they are enabled by config.\n\nFirst, update the 'gc' task to perform the auto check inside the\nmaintenance process. This prevents running an extra 'git gc --auto'\ncommand when not needed. It also shows a model for other tasks.\n\nSecond, use the 'auto_condition' function pointer as a signal for\nwhether we enable the maintenance task under '--auto'. For instance, we\ndo not want to enable the 'fetch' task in '--auto' mode, so that\nfunction pointer will remain NULL.\n\nNow that we are not automatically calling 'git gc', a test in\nt5514-fetch-multiple.sh must be changed to watch for 'git maintenance'\ninstead.\n\nWe continue to pass the '--auto' option to the 'git gc' command when\nnecessary, because of the gc.autoDetach config option changes behavior.\nLikely, we will want to absorb the daemonizing behavior implied by\ngc.autoDetach as a maintenance.autoDetach config option.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/gc.c              | 16 ++++++++++++++++\n t/t5514-fetch-multiple.sh |  2 +-\n t/t7900-maintenance.sh    |  2 +-\n 3 files changed, 18 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex e66326ede9..c54c3070b8 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -788,9 +788,17 @@ static int maintenance_task_gc(struct maintenance_opts *opts)\n \n typedef int maintenance_task_fn(struct maintenance_opts *opts);\n \n+/*\n+ * An auto condition function returns 1 if the task should run\n+ * and 0 if the task should NOT run. See needs_to_gc() for an\n+ * example.\n+ */\n+typedef int maintenance_auto_fn(void);\n+\n struct maintenance_task {\n \tconst char *name;\n \tmaintenance_task_fn *fn;\n+\tmaintenance_auto_fn *auto_condition;\n \tunsigned enabled:1,\n \t\t selected:1;\n \tint selected_order;\n@@ -808,6 +816,7 @@ static struct maintenance_task tasks[] = {\n \t[TASK_GC] = {\n \t\t\"gc\",\n \t\tmaintenance_task_gc,\n+\t\tneed_to_gc,\n \t\t1,\n \t},\n \t[TASK_COMMIT_GRAPH] = {\n@@ -863,6 +872,11 @@ static int maintenance_run(struct maintenance_opts *opts)\n \t\tif (!found_selected && !tasks[i].enabled)\n \t\t\tcontinue;\n \n+\t\tif (opts->auto_flag &&\n+\t\t    (!tasks[i].auto_condition ||\n+\t\t     !tasks[i].auto_condition()))\n+\t\t\tcontinue;\n+\n \t\tif (tasks[i].fn(opts)) {\n \t\t\terror(_(\"task '%s' failed\"), tasks[i].name);\n \t\t\tresult = 1;\n@@ -877,6 +891,8 @@ static void initialize_task_config(void)\n {\n \tint i;\n \tstruct strbuf config_name = STRBUF_INIT;\n+\tgc_config();\n+\n \tfor (i = 0; i < TASK__COUNT; i++) {\n \t\tint config_value;\n \ndiff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh\nindex de8e2f1531..bd202ec6f3 100755\n--- a/t/t5514-fetch-multiple.sh\n+++ b/t/t5514-fetch-multiple.sh\n@@ -108,7 +108,7 @@ test_expect_success 'git fetch --multiple (two remotes)' '\n \t GIT_TRACE=1 git fetch --multiple one two 2>trace &&\n \t git branch -r > output &&\n \t test_cmp ../expect output &&\n-\t grep \"built-in: git gc\" trace >gc &&\n+\t grep \"built-in: git maintenance\" trace >gc &&\n \t test_line_count = 1 gc\n \t)\n '\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex 290abb7832..4f6a04ddb1 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -21,7 +21,7 @@ test_expect_success 'run [--auto|--quiet]' '\n \tGIT_TRACE2_EVENT=\"$(pwd)/run-no-quiet.txt\" \\\n \t\tgit maintenance run --no-quiet 2>/dev/null &&\n \ttest_subcommand git gc --quiet <run-no-auto.txt &&\n-\ttest_subcommand git gc --auto --quiet <run-auto.txt &&\n+\ttest_subcommand ! git gc --auto --quiet <run-auto.txt &&\n \ttest_subcommand git gc --no-quiet <run-no-quiet.txt\n '\n \n-- \ngitgitgadget\n\n"},{"id":"403945","messageId":"6f86cfaa94cfeaf7a2af417991ca07e41b6b0c3d.1597760589.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v2.git.1597760589.gitgitgadget@gmail.com","subject":"[PATCH v2 07/11] maintenance: take a lock on the objects directory","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-18T14:23:04Z","receivedAt":"2020-08-18T14:24:04Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nPerforming maintenance on a Git repository involves writing data to the\n.git directory, which is not safe to do with multiple writers attempting\nthe same operation. Ensure that only one 'git maintenance' process is\nrunning at a time by holding a file-based lock. Simply the presence of\nthe .git/maintenance.lock file will prevent future maintenance. This\nlock is never committed, since it does not represent meaningful data.\nInstead, it is only a placeholder.\n\nIf the lock file already exists, then fail silently. This will become\nvery important later when we implement the 'fetch' task, as this is our\nstop-gap from creating a recursive process loop between 'git fetch' and\n'git maintenance run'.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/gc.c | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 0231d2f8c1..a47bf64623 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -830,6 +830,25 @@ static int maintenance_run(struct maintenance_opts *opts)\n {\n \tint i, found_selected = 0;\n \tint result = 0;\n+\tstruct lock_file lk;\n+\tstruct repository *r = the_repository;\n+\tchar *lock_path = xstrfmt(\"%s/maintenance\", r->objects->odb->path);\n+\n+\tif (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {\n+\t\t/*\n+\t\t * Another maintenance command is running.\n+\t\t *\n+\t\t * If --auto was provided, then it is likely due to a\n+\t\t * recursive process stack. Do not report an error in\n+\t\t * that case.\n+\t\t */\n+\t\tif (!opts->auto_flag && !opts->quiet)\n+\t\t\terror(_(\"lock file '%s' exists, skipping maintenance\"),\n+\t\t\t      lock_path);\n+\t\tfree(lock_path);\n+\t\treturn 0;\n+\t}\n+\tfree(lock_path);\n \n \tfor (i = 0; !found_selected && i < TASK__COUNT; i++)\n \t\tfound_selected = tasks[i].selected;\n@@ -850,6 +869,7 @@ static int maintenance_run(struct maintenance_opts *opts)\n \t\t}\n \t}\n \n+\trollback_lock_file(&lk);\n \treturn result;\n }\n \n-- \ngitgitgadget\n\n"},{"id":"403970","messageId":"xmqqa6yryag0.fsf@gitster.c.googlers.com","threadId":"53997","inReplyTo":"pull.695.v2.git.1597760589.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 00/11] Maintenance I: Command, gc and commit-graph tasks","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-08-18T20:18:39Z","receivedAt":"2020-08-18T20:18:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> This series is based on jk/strvec.\n\nAs jc/no-update-fetch-head topic seems to be getting popular for\nsome reason, let's do this:\n\n - recreate jc/no-update-fetch-head topic with patch [1/9] of the\n   maintenance-2 series directly on top of 'master';\n\n - fork ds/maintenance-part-1 topic off of jc/no-update-fetch-head\n   and queue these 11 patches;\n\n - fork ds/maintenance-part-2 topic off of ds/maintenance-part-1 and\n   queue its 8 patches [2/9 - 9/9].\n\nThanks.\n"},{"id":"403985","messageId":"20200818234622.2833790-1-jonathantanmy@google.com","threadId":"53997","inReplyTo":"1db3b96280d9bf7908b7dc9fa13b80c445164a99.1597760589.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 04/11] maintenance: initialize task array","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-08-18T23:46:22Z","receivedAt":"2020-08-18T23:46:30Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> From: Derrick Stolee <dstolee@microsoft.com>\n> \n> In anticipation of implementing multiple maintenance tasks inside the\n> 'maintenance' builtin, use a list of structs to describe the work to be\n> done.\n> \n> The struct maintenance_task stores the name of the task (as given by a\n> future command-line argument) along with a function pointer to its\n> implementation and a boolean for whether the step is enabled.\n> \n> A list these structs are initialized with the full list of implemented\n> tasks along with a default order. For now, this list only contains the\n> \"gc\" task. This task is also the only task enabled by default.\n> \n> The run subcommand will return a nonzero exit code if any task fails.\n> However, it will attempt all tasks in its loop before returning with the\n> failure. Also each failed task will send an error message.\n\nBetter to say \"will print an error message\", I think.\n\nOther than that, patches 1-4 look good to me.\n"},{"id":"403986","messageId":"20200818235126.2836309-1-jonathantanmy@google.com","threadId":"53997","inReplyTo":"50b457fd57aef4e9ac6a15549561936dc962ef36.1597760589.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 05/11] maintenance: add commit-graph task","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-08-18T23:51:26Z","receivedAt":"2020-08-18T23:51:34Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> By using 'git commit-graph verify --shallow' we can ensure that\n> the file we just wrote is valid. This is an extra safety precaution\n> that is faster than our 'write' subcommand. In the rare situation\n> that the newest layer of the commit-graph is corrupt, we can \"fix\"\n> the corruption by deleting the commit-graph-chain file and rewrite\n> the full commit-graph as a new one-layer commit graph. This does\n> not completely prevent _that_ file from being corrupt, but it does\n> recompute the commit-graph by parsing commits from the object\n> database. In our use of this step in Scalar and VFS for Git, we\n> have only seen this issue arise because our microsoft/git fork\n> reverted 43d3561 (\"commit-graph write: don't die if the existing\n> graph is corrupt\" 2019-03-25) for a while to keep commit-graph\n> writes very fast. We dropped the revert when updating to v2.23.0.\n> The verify still has potential for catching corrupt data across\n> the layer boundary: if the new file has commit X with parent Y\n> in an old file but the commit ID for Y in the old file had a\n> bitswap, then we will notice that in the 'verify' command.\n\nI'm concerned about having this extra precaution, because it is never\ntested (neither here or in a future patch). When you saw this issue\narise, was there ever an instance in which a valid set of commit graph\nfiles turned invalid after this maintenance step? (It seems from your\ndescription that the set was invalid to begin with, so the maintenance\nstep did not fix it but also did not make it worse. And it does not make\nit worse, that seems not too bad to me.)\n\nOther than that, this patch looks good to me.\n"},{"id":"403988","messageId":"20200819000015.2838345-1-jonathantanmy@google.com","threadId":"53997","inReplyTo":"85268bd53ef7f4e7b3d97a8ae091290e8e74441d.1597760589.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 06/11] maintenance: add --task option","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-08-19T00:00:15Z","receivedAt":"2020-08-19T00:00:21Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> @@ -66,6 +68,10 @@ OPTIONS\n>  --quiet::\n>  \tDo not report progress or other information over `stderr`.\n>  \n> +--task=<task>::\n> +\tIf this option is specified one or more times, then only run the\n> +\tspecified tasks in the specified order.\n\nWe should list the accepted tasks somewhere but maybe this can wait\nuntil after part 2.\n\n> @@ -791,7 +791,9 @@ typedef int maintenance_task_fn(struct maintenance_opts *opts);\n>  struct maintenance_task {\n>  \tconst char *name;\n>  \tmaintenance_task_fn *fn;\n> -\tunsigned enabled:1;\n> +\tunsigned enabled:1,\n> +\t\t selected:1;\n> +\tint selected_order;\n>  };\n\n\"selected\" and \"selected_order\" are redundant in some cases - I think\nthis would be better if selected_order is negative if this task is not\nselected, and non-negative otherwise.\n\nApart from that, maybe this should be documented. It is unusual (to me)\nthat a selection can override something being enabled, but that is the\ncase here.\n"},{"id":"403989","messageId":"20200819000425.2840491-1-jonathantanmy@google.com","threadId":"53997","inReplyTo":"6f86cfaa94cfeaf7a2af417991ca07e41b6b0c3d.1597760589.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 07/11] maintenance: take a lock on the objects directory","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-08-19T00:04:25Z","receivedAt":"2020-08-19T00:04:31Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> If the lock file already exists, then fail silently. This will become\n\nMaybe \"skip all maintenance steps silently\"?\n\n> +\tif (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {\n> +\t\t/*\n> +\t\t * Another maintenance command is running.\n> +\t\t *\n> +\t\t * If --auto was provided, then it is likely due to a\n> +\t\t * recursive process stack. Do not report an error in\n> +\t\t * that case.\n> +\t\t */\n> +\t\tif (!opts->auto_flag && !opts->quiet)\n> +\t\t\terror(_(\"lock file '%s' exists, skipping maintenance\"),\n> +\t\t\t      lock_path);\n> +\t\tfree(lock_path);\n> +\t\treturn 0;\n> +\t}\n\nAs it is, this doesn't seem very silent. :-) If we do want a message to\nbe printed maybe make it warning instead of error.\n\nOther than that, the idea of having a lock file and the implementation\nin this patch look good to me.\n"},{"id":"403990","messageId":"20200819000947.2844392-1-jonathantanmy@google.com","threadId":"53997","inReplyTo":"fc097c389a963065fc0bb5991bdebdca8824b121.1597760589.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 10/11] maintenance: add auto condition for commit-graph task","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-08-19T00:09:47Z","receivedAt":"2020-08-19T00:09:53Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> Instead of writing a new commit-graph in every 'git maintenance run\n> --auto' process (when maintenance.commit-graph.enalbed is configured to\n\ns/enalbed/enabled/\n\n> +/* Remember to update object flag allocation in object.h */\n> +#define PARENT1\t\t(1u<<16)\n\nWhy this name? \"SEEN\" seems perfectly fine.\n\n> +static int num_commits_not_in_graph = 0;\n> +static int limit_commits_not_in_graph = 100;\n> +\n> +static int dfs_on_ref(const char *refname,\n> +\t\t      const struct object_id *oid, int flags,\n> +\t\t      void *cb_data)\n> +{\n\n[snip]\n\n> +static int should_write_commit_graph(void)\n> +{\n> +\tint result;\n> +\n> +\tgit_config_get_int(\"maintenance.commit-graph.auto\",\n> +\t\t\t   &limit_commits_not_in_graph);\n> +\n> +\tif (!limit_commits_not_in_graph)\n> +\t\treturn 0;\n> +\tif (limit_commits_not_in_graph < 0)\n> +\t\treturn 1;\n> +\n> +\tresult = for_each_ref(dfs_on_ref, NULL);\n\nI don't like introducing the mutable global num_commits_not_in_graph\nespecially when there seems to be at least one easy alternative (e.g.\nputting it in cb_data) but I know that this is a pattern than existing\ncode.\n"},{"id":"403991","messageId":"20200819001133.2845536-1-jonathantanmy@google.com","threadId":"53997","inReplyTo":"46fbe161aa154de9406117ee916b1bd5f549905c.1597760589.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 11/11] maintenance: add trace2 regions for task execution","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-08-19T00:11:33Z","receivedAt":"2020-08-19T00:11:37Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> From: Derrick Stolee <dstolee@microsoft.com>\n> \n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n\nThanks. Overall this looks like a good patch set. As you can see from my\nother emails the only issues I have remaining are minor.\n"},{"id":"403992","messageId":"xmqq364jwjxg.fsf@gitster.c.googlers.com","threadId":"53997","inReplyTo":"20200819000015.2838345-1-jonathantanmy@google.com","subject":"Re: [PATCH v2 06/11] maintenance: add --task option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-08-19T00:36:43Z","receivedAt":"2020-08-19T00:36:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n>> @@ -66,6 +68,10 @@ OPTIONS\n>>  --quiet::\n>>  \tDo not report progress or other information over `stderr`.\n>>  \n>> +--task=<task>::\n>> +\tIf this option is specified one or more times, then only run the\n>> +\tspecified tasks in the specified order.\n>\n> We should list the accepted tasks somewhere but maybe this can wait\n> until after part 2.\n>\n>> @@ -791,7 +791,9 @@ typedef int maintenance_task_fn(struct maintenance_opts *opts);\n>>  struct maintenance_task {\n>>  \tconst char *name;\n>>  \tmaintenance_task_fn *fn;\n>> -\tunsigned enabled:1;\n>> +\tunsigned enabled:1,\n>> +\t\t selected:1;\n>> +\tint selected_order;\n>>  };\n>\n> \"selected\" and \"selected_order\" are redundant in some cases - I think\n> this would be better if selected_order is negative if this task is not\n> selected, and non-negative otherwise.\n\nIt is good to get rid of redundancies.\n\n> Apart from that, maybe this should be documented. It is unusual (to me)\n> that a selection can override something being enabled, but that is the\n> case here.\n\nI had the same reaction as \"(to me)\" above during an earlier review\nround, IIRC.  This definitely deserves documentation.\n"},{"id":"404015","messageId":"0d69af08-5d90-aa3c-80d5-2eb2ade57f8d@gmail.com","threadId":"53997","inReplyTo":"xmqqa6yryag0.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v2 00/11] Maintenance I: Command, gc and commit-graph tasks","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2020-08-19T14:51:28Z","receivedAt":"2020-08-19T14:51:42Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 8/18/2020 4:18 PM, Junio C Hamano wrote:\n> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> This series is based on jk/strvec.\n> \n> As jc/no-update-fetch-head topic seems to be getting popular for\n> some reason, let's do this:\n> \n>  - recreate jc/no-update-fetch-head topic with patch [1/9] of the\n>    maintenance-2 series directly on top of 'master';\n> \n>  - fork ds/maintenance-part-1 topic off of jc/no-update-fetch-head\n>    and queue these 11 patches;\n> \n>  - fork ds/maintenance-part-2 topic off of ds/maintenance-part-1 and\n>    queue its 8 patches [2/9 - 9/9].\n\nSounds good. I will update my branches before their next versions.\n\nThanks,\n-Stolee\n"},{"id":"404018","messageId":"66ae9121-80c9-4030-1119-a11eab8d392b@gmail.com","threadId":"53997","inReplyTo":"20200818235126.2836309-1-jonathantanmy@google.com","subject":"Re: [PATCH v2 05/11] maintenance: add commit-graph task","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2020-08-19T15:04:55Z","receivedAt":"2020-08-19T15:05:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 8/18/2020 7:51 PM, Jonathan Tan wrote:\n>> By using 'git commit-graph verify --shallow' we can ensure that\n>> the file we just wrote is valid. This is an extra safety precaution\n>> that is faster than our 'write' subcommand. In the rare situation\n>> that the newest layer of the commit-graph is corrupt, we can \"fix\"\n>> the corruption by deleting the commit-graph-chain file and rewrite\n>> the full commit-graph as a new one-layer commit graph. This does\n>> not completely prevent _that_ file from being corrupt, but it does\n>> recompute the commit-graph by parsing commits from the object\n>> database. In our use of this step in Scalar and VFS for Git, we\n>> have only seen this issue arise because our microsoft/git fork\n>> reverted 43d3561 (\"commit-graph write: don't die if the existing\n>> graph is corrupt\" 2019-03-25) for a while to keep commit-graph\n>> writes very fast. We dropped the revert when updating to v2.23.0.\n>> The verify still has potential for catching corrupt data across\n>> the layer boundary: if the new file has commit X with parent Y\n>> in an old file but the commit ID for Y in the old file had a\n>> bitswap, then we will notice that in the 'verify' command.\n> \n> I'm concerned about having this extra precaution, because it is never\n> tested (neither here or in a future patch). When you saw this issue\n> arise, was there ever an instance in which a valid set of commit graph\n> files turned invalid after this maintenance step? (It seems from your\n> description that the set was invalid to begin with, so the maintenance\n> step did not fix it but also did not make it worse. And it does not make\n> it worse, that seems not too bad to me.)\n\nThe cases we've seen this happen were root-caused to hardware\nproblems (disk or RAM), and the invalid data was present immediately\nafter the \"git commit-graph write\" command. Since implementing the\n\"verify\" step after each write, we have not had any user reports of\nproblems with these files.\n\nAre you suggesting one of these options?\n\n 1. Remove this validation and rewrite entirely.\n\n 2. Remove the rewrite and only delete the known-bad data.\n\n 3. Insert a way to cause the verify to return failure mid-process,\n    so that this function can be covered by tests.\n\nThanks,\n-Stolee\n"},{"id":"404019","messageId":"3a5328c0-15f0-a427-1f21-2c8e3ef8277a@gmail.com","threadId":"53997","inReplyTo":"xmqq364jwjxg.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v2 06/11] maintenance: add --task option","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2020-08-19T15:09:13Z","receivedAt":"2020-08-19T15:09:21Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 8/18/2020 8:36 PM, Junio C Hamano wrote:\n> Jonathan Tan <jonathantanmy@google.com> writes:\n> \n>>> @@ -66,6 +68,10 @@ OPTIONS\n>>>  --quiet::\n>>>  \tDo not report progress or other information over `stderr`.\n>>>  \n>>> +--task=<task>::\n>>> +\tIf this option is specified one or more times, then only run the\n>>> +\tspecified tasks in the specified order.\n>>\n>> We should list the accepted tasks somewhere but maybe this can wait\n>> until after part 2.\n\nIt's hard to see from the patch-context, but there is a section titled\n\"TASKS\" that lists the 'gc' and 'commit-graph' tasks from the earlier\npatches. Would a reference to that section be helpful?\n\n\tSee the TASKS section for the list of available tasks.\n\n>>> @@ -791,7 +791,9 @@ typedef int maintenance_task_fn(struct maintenance_opts *opts);\n>>>  struct maintenance_task {\n>>>  \tconst char *name;\n>>>  \tmaintenance_task_fn *fn;\n>>> -\tunsigned enabled:1;\n>>> +\tunsigned enabled:1,\n>>> +\t\t selected:1;\n>>> +\tint selected_order;\n>>>  };\n>>\n>> \"selected\" and \"selected_order\" are redundant in some cases - I think\n>> this would be better if selected_order is negative if this task is not\n>> selected, and non-negative otherwise.\n> \n> It is good to get rid of redundancies.\n> \n>> Apart from that, maybe this should be documented. It is unusual (to me)\n>> that a selection can override something being enabled, but that is the\n>> case here.\n> \n> I had the same reaction as \"(to me)\" above during an earlier review\n> round, IIRC.  This definitely deserves documentation.\n\nWill do. Thanks.\n\n-Stolee\n \n\n"},{"id":"404020","messageId":"2fb1fdf2-54b1-4597-4b58-96208bebeb6b@gmail.com","threadId":"53997","inReplyTo":"20200819000425.2840491-1-jonathantanmy@google.com","subject":"Re: [PATCH v2 07/11] maintenance: take a lock on the objects directory","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2020-08-19T15:10:36Z","receivedAt":"2020-08-19T15:10:57Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 8/18/2020 8:04 PM, Jonathan Tan wrote:\n>> If the lock file already exists, then fail silently. This will become\n> \n> Maybe \"skip all maintenance steps silently\"?\n> \n>> +\tif (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {\n>> +\t\t/*\n>> +\t\t * Another maintenance command is running.\n>> +\t\t *\n>> +\t\t * If --auto was provided, then it is likely due to a\n>> +\t\t * recursive process stack. Do not report an error in\n>> +\t\t * that case.\n>> +\t\t */\n>> +\t\tif (!opts->auto_flag && !opts->quiet)\n>> +\t\t\terror(_(\"lock file '%s' exists, skipping maintenance\"),\n>> +\t\t\t      lock_path);\n>> +\t\tfree(lock_path);\n>> +\t\treturn 0;\n>> +\t}\n> \n> As it is, this doesn't seem very silent. :-) If we do want a message to\n> be printed maybe make it warning instead of error.\n\nSorry, it is silent when \"--auto\" is specified. The commit message needs\nto reflect this properly. This could easily be downgraded to a warning.\n\n> Other than that, the idea of having a lock file and the implementation\n> in this patch look good to me.\n\nThanks,\n-Stolee\n\n"},{"id":"404021","messageId":"8a9f5ca0-73b2-b9f8-b342-a8fff8c938be@gmail.com","threadId":"53997","inReplyTo":"20200819000947.2844392-1-jonathantanmy@google.com","subject":"Re: [PATCH v2 10/11] maintenance: add auto condition for commit-graph task","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2020-08-19T15:15:20Z","receivedAt":"2020-08-19T15:15:26Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 8/18/2020 8:09 PM, Jonathan Tan wrote:\n>> Instead of writing a new commit-graph in every 'git maintenance run\n>> --auto' process (when maintenance.commit-graph.enalbed is configured to\n> \n> s/enalbed/enabled/\n> \n>> +/* Remember to update object flag allocation in object.h */\n>> +#define PARENT1\t\t(1u<<16)\n> \n> Why this name? \"SEEN\" seems perfectly fine.\n\nFor some reason I thought that there might be issues using\nSEEN (1u<<0) but trying it locally does not seem to be a\nproblem. I think I've just been bit by how revision.c uses\nit a bit too often. The use here is independent enough to\nnot cause problems.\n\n>> +static int num_commits_not_in_graph = 0;\n>> +static int limit_commits_not_in_graph = 100;\n>> +\n>> +static int dfs_on_ref(const char *refname,\n>> +\t\t      const struct object_id *oid, int flags,\n>> +\t\t      void *cb_data)\n>> +{\n> \n> [snip]\n> \n>> +static int should_write_commit_graph(void)\n>> +{\n>> +\tint result;\n>> +\n>> +\tgit_config_get_int(\"maintenance.commit-graph.auto\",\n>> +\t\t\t   &limit_commits_not_in_graph);\n>> +\n>> +\tif (!limit_commits_not_in_graph)\n>> +\t\treturn 0;\n>> +\tif (limit_commits_not_in_graph < 0)\n>> +\t\treturn 1;\n>> +\n>> +\tresult = for_each_ref(dfs_on_ref, NULL);\n> \n> I don't like introducing the mutable global num_commits_not_in_graph\n> especially when there seems to be at least one easy alternative (e.g.\n> putting it in cb_data) but I know that this is a pattern than existing\n> code.\n\nI should have done this right in the first place. Thanks for\ncatching it.\n\n-Stolee\n"},{"id":"404045","messageId":"20200819173547.3084636-1-jonathantanmy@google.com","threadId":"53997","inReplyTo":"3a5328c0-15f0-a427-1f21-2c8e3ef8277a@gmail.com","subject":"Re: [PATCH v2 06/11] maintenance: add --task option","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-08-19T17:35:47Z","receivedAt":"2020-08-19T17:35:53Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> On 8/18/2020 8:36 PM, Junio C Hamano wrote:\n> > Jonathan Tan <jonathantanmy@google.com> writes:\n> > \n> >>> @@ -66,6 +68,10 @@ OPTIONS\n> >>>  --quiet::\n> >>>  \tDo not report progress or other information over `stderr`.\n> >>>  \n> >>> +--task=<task>::\n> >>> +\tIf this option is specified one or more times, then only run the\n> >>> +\tspecified tasks in the specified order.\n> >>\n> >> We should list the accepted tasks somewhere but maybe this can wait\n> >> until after part 2.\n> \n> It's hard to see from the patch-context, but there is a section titled\n> \"TASKS\" that lists the 'gc' and 'commit-graph' tasks from the earlier\n> patches. Would a reference to that section be helpful?\n> \n> \tSee the TASKS section for the list of available tasks.\n\nAh, I missed that. What you have now is fine then, but a reference might\nbe helpful if this man page ever grows.\n"},{"id":"404046","messageId":"20200819174322.3087791-1-jonathantanmy@google.com","threadId":"53997","inReplyTo":"66ae9121-80c9-4030-1119-a11eab8d392b@gmail.com","subject":"Re: [PATCH v2 05/11] maintenance: add commit-graph task","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-08-19T17:43:22Z","receivedAt":"2020-08-19T17:43:28Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> The cases we've seen this happen were root-caused to hardware\n> problems (disk or RAM), and the invalid data was present immediately\n> after the \"git commit-graph write\" command. Since implementing the\n> \"verify\" step after each write, we have not had any user reports of\n> problems with these files.\n> \n> Are you suggesting one of these options?\n> \n>  1. Remove this validation and rewrite entirely.\n> \n>  2. Remove the rewrite and only delete the known-bad data.\n> \n>  3. Insert a way to cause the verify to return failure mid-process,\n>     so that this function can be covered by tests.\n\nI was suggesting 1, although 2 and 3 would assuage my concerns also (2\nless so, but just deleting the file is relatively simple and easier to\nreason about). I don't think Git in general defends much against\nhardware problems, but if an issue is noticeable in some hardware (as is\nin this case, at least in the past) I can see why we would want to\ndefend against it. My concern is that if we do defend against it, but\nleave it untested, several versions of Git later we might find that we\nneed this defense but it does not work.\n"},{"id":"404440","messageId":"pull.695.v3.git.1598380426.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v2.git.1597760589.gitgitgadget@gmail.com","subject":"[PATCH v3 00/11] Maintenance I: Command, gc and commit-graph tasks","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-25T18:33:35Z","receivedAt":"2020-08-25T18:33:57Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"This series is based on jc/no-update-fetch-head.\n\nThis patch series contains 11patches that were going to be part of v4 of\nds/maintenance [1], but the discussion has gotten really long. To help focus\nthe conversation, I'm splitting out the portions that create and test the\n'maintenance' builtin from the additional tasks (prefetch, loose-objects,\nincremental-repack) that can be brought in later.\n\n[1] \nhttps://lore.kernel.org/git/pull.671.git.1594131695.gitgitgadget@gmail.com/\n\nAs mentioned before, git gc already plays the role of maintaining Git\nrepositories. It has accumulated several smaller pieces in its long history,\nincluding:\n\n 1. Repacking all reachable objects into one pack-file (and deleting\n    unreachable objects).\n 2. Packing refs.\n 3. Expiring reflogs.\n 4. Clearing rerere logs.\n 5. Updating the commit-graph file.\n 6. Pruning worktrees.\n\nWhile expiring reflogs, clearing rererelogs, and deleting unreachable\nobjects are suitable under the guise of \"garbage collection\", packing refs\nand updating the commit-graph file are not as obviously fitting. Further,\nthese operations are \"all or nothing\" in that they rewrite almost all\nrepository data, which does not perform well at extremely large scales.\nThese operations can also be disruptive to foreground Git commands when git\ngc --auto triggers during routine use.\n\nThis series does not intend to change what git gc does, but instead create\nnew choices for automatic maintenance activities, of which git gc remains\nthe only one enabled by default.\n\nThe new maintenance tasks are:\n\n * 'commit-graph' : write and verify a single layer of an incremental\n   commit-graph.\n * 'loose-objects' : prune packed loose objects, then create a new pack from\n   a batch of loose objects.\n * 'pack-files' : expire redundant packs from the multi-pack-index, then\n   repack using the multi-pack-index's incremental repack strategy.\n * 'prefetch' : fetch from each remote, storing the refs in 'refs/prefetch/\n   /'.\n\nThe only included tasks are the 'gc' and 'commit-graph' tasks. The rest will\nfollow in a follow-up series. Including the 'commit-graph' task here allows\nus to build and test features like config settings and the --task= \ncommand-line argument.\n\nThese tasks are all disabled by default, but can be enabled with config\noptions or run explicitly using \"git maintenance run --task=\". There are\nadditional config options to allow customizing the conditions for which the\ntasks run during the '--auto' option.\n\n Because 'gc' is implemented as a maintenance task, the most dramatic change\nof this series is to convert the 'git gc --auto' calls into 'git maintenance\nrun --auto' calls at the end of some Git commands. By default, the only\nchange is that 'git gc --auto' will be run below an additional 'git\nmaintenance' process.\n\nThe 'git maintenance' builtin has a 'run' subcommand so it can be extended\nlater with subcommands that manage background maintenance, such as 'start'\nor 'stop'. These are not the subject of this series, as it is important to\nfocus on the maintenance activities themselves.\n\nUpdates since v2\n================\n\n * Based on jc/no-update-fetch-head instead of jk/strvec.\n   \n   \n * I realized that the other maintenance subcommands should not accept the\n   options for the 'run' subcommand, so I reorganized the option parsing\n   into that subcommand. This makes the range-diff noisier than it would\n   have been otherwise.\n   \n   \n * While updating the parsing, I also updated the usage strings.\n   \n   \n * The \"verify, then delete and rewrite on failure\" logic is removed. I'll\n   consider bringing this back with a way to test the behavior in a later\n   patch series.\n   \n   \n * Other feedback from Jonathan Tan is applied.\n   \n   \n\nUpdates since v1 (of this series)\n=================================\n\n * Documentation fixes.\n   \n   \n * The builtin code had some slight tweaks in PATCH 1.\n   \n   \n\nUPDATES since v3 of [1]\n=======================\n\n * The biggest change here is the use of \"test_subcommand\", based on\n   Jonathan Nieder's approach. This requires having the exact command-line\n   figured out, which now requires spelling out all --no- [quiet%7Cprogress] \n   options. I also added a bunch of \"2>/dev/null\" checks because of the\n   isatty(2) calls. Without that, the behavior will change depending on\n   whether the test is run with -x/-v or without.\n   \n   \n * The option parsing has changed to use a local struct and pass that struct\n   to the helper methods. This is instead of having a global singleton.\n   \n   \n\nThanks, -Stolee\n\nDerrick Stolee (11):\n  maintenance: create basic maintenance runner\n  maintenance: add --quiet option\n  maintenance: replace run_auto_gc()\n  maintenance: initialize task array\n  maintenance: add commit-graph task\n  maintenance: add --task option\n  maintenance: take a lock on the objects directory\n  maintenance: create maintenance.<task>.enabled config\n  maintenance: use pointers to check --auto\n  maintenance: add auto condition for commit-graph task\n  maintenance: add trace2 regions for task execution\n\n .gitignore                           |   1 +\n Documentation/config.txt             |   2 +\n Documentation/config/maintenance.txt |  16 ++\n Documentation/fetch-options.txt      |   6 +-\n Documentation/git-clone.txt          |   6 +-\n Documentation/git-maintenance.txt    |  79 +++++++\n builtin.h                            |   1 +\n builtin/am.c                         |   2 +-\n builtin/commit.c                     |   2 +-\n builtin/fetch.c                      |   6 +-\n builtin/gc.c                         | 336 +++++++++++++++++++++++++++\n builtin/merge.c                      |   2 +-\n builtin/rebase.c                     |   4 +-\n command-list.txt                     |   1 +\n commit-graph.c                       |   8 +-\n commit-graph.h                       |   1 +\n git.c                                |   1 +\n object.h                             |   1 +\n run-command.c                        |  16 +-\n run-command.h                        |   2 +-\n t/t5510-fetch.sh                     |   2 +-\n t/t5514-fetch-multiple.sh            |   2 +-\n t/t7900-maintenance.sh               |  63 +++++\n t/test-lib-functions.sh              |  33 +++\n 24 files changed, 565 insertions(+), 28 deletions(-)\n create mode 100644 Documentation/config/maintenance.txt\n create mode 100644 Documentation/git-maintenance.txt\n create mode 100755 t/t7900-maintenance.sh\n\n\nbase-commit: 887952b8c680626f4721cb5fa57704478801aca4\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-695%2Fderrickstolee%2Fmaintenance%2Fbuiltin-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-695/derrickstolee/maintenance/builtin-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/695\n\nRange-diff vs v2:\n\n  1:  e09e4a4a87 !  1:  aa961af387 maintenance: create basic maintenance runner\n     @@ builtin/gc.c: int cmd_gc(int argc, const char **argv, const char *prefix)\n       \treturn 0;\n       }\n      +\n     -+static const char * const builtin_maintenance_usage[] = {\n     -+\tN_(\"git maintenance run [<options>]\"),\n     ++static const char * const builtin_maintenance_run_usage[] = {\n     ++\tN_(\"git maintenance run [--auto]\"),\n      +\tNULL\n      +};\n      +\n     -+struct maintenance_opts {\n     ++struct maintenance_run_opts {\n      +\tint auto_flag;\n      +};\n      +\n     -+static int maintenance_task_gc(struct maintenance_opts *opts)\n     ++static int maintenance_task_gc(struct maintenance_run_opts *opts)\n      +{\n      +\tstruct child_process child = CHILD_PROCESS_INIT;\n      +\n     @@ builtin/gc.c: int cmd_gc(int argc, const char **argv, const char *prefix)\n      +\treturn run_command(&child);\n      +}\n      +\n     -+static int maintenance_run(struct maintenance_opts *opts)\n     ++static int maintenance_run(int argc, const char **argv, const char *prefix)\n      +{\n     -+\treturn maintenance_task_gc(opts);\n     -+}\n     -+\n     -+int cmd_maintenance(int argc, const char **argv, const char *prefix)\n     -+{\n     -+\tstruct maintenance_opts opts;\n     -+\tstruct option builtin_maintenance_options[] = {\n     ++\tstruct maintenance_run_opts opts;\n     ++\tstruct option builtin_maintenance_run_options[] = {\n      +\t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n      +\t\t\t N_(\"run tasks based on the state of the repository\")),\n      +\t\tOPT_END()\n      +\t};\n     -+\n      +\tmemset(&opts, 0, sizeof(opts));\n      +\n     -+\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n     -+\t\tusage_with_options(builtin_maintenance_usage,\n     -+\t\t\t\t   builtin_maintenance_options);\n     -+\n      +\targc = parse_options(argc, argv, prefix,\n     -+\t\t\t     builtin_maintenance_options,\n     -+\t\t\t     builtin_maintenance_usage,\n     -+\t\t\t     PARSE_OPT_KEEP_UNKNOWN);\n     ++\t\t\t     builtin_maintenance_run_options,\n     ++\t\t\t     builtin_maintenance_run_usage,\n     ++\t\t\t     PARSE_OPT_STOP_AT_NON_OPTION);\n     ++\n     ++\tif (argc != 0)\n     ++\t\tusage_with_options(builtin_maintenance_run_usage,\n     ++\t\t\t\t   builtin_maintenance_run_options);\n     ++\treturn maintenance_task_gc(&opts);\n     ++}\n     ++\n     ++static const char builtin_maintenance_usage[] = N_(\"git maintenance run [<options>]\");\n      +\n     -+\tif (argc != 1)\n     -+\t\tusage_with_options(builtin_maintenance_usage,\n     -+\t\t\t\t   builtin_maintenance_options);\n     ++int cmd_maintenance(int argc, const char **argv, const char *prefix)\n     ++{\n     ++\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n     ++\t\tusage(builtin_maintenance_usage);\n      +\n     -+\tif (!strcmp(argv[0], \"run\"))\n     -+\t\treturn maintenance_run(&opts);\n     ++\tif (!strcmp(argv[1], \"run\"))\n     ++\t\treturn maintenance_run(argc - 1, argv + 1, prefix);\n      +\n     -+\tdie(_(\"invalid subcommand: %s\"), argv[0]);\n     ++\tdie(_(\"invalid subcommand: %s\"), argv[1]);\n      +}\n      \n     + ## command-list.txt ##\n     +@@ command-list.txt: git-ls-remote                           plumbinginterrogators\n     + git-ls-tree                             plumbinginterrogators\n     + git-mailinfo                            purehelpers\n     + git-mailsplit                           purehelpers\n     ++git-maintenance                         mainporcelain\n     + git-merge                               mainporcelain           history\n     + git-merge-base                          plumbinginterrogators\n     + git-merge-file                          plumbingmanipulators\n     +\n       ## git.c ##\n      @@ git.c: static struct cmd_struct commands[] = {\n       \t{ \"ls-tree\", cmd_ls_tree, RUN_SETUP },\n  2:  adae48d235 !  2:  5386d8a628 maintenance: add --quiet option\n     @@ Documentation/git-maintenance.txt: OPTIONS\n       Part of the linkgit:git[1] suite\n      \n       ## builtin/gc.c ##\n     -@@ builtin/gc.c: static const char * const builtin_maintenance_usage[] = {\n     +@@ builtin/gc.c: int cmd_gc(int argc, const char **argv, const char *prefix)\n     + }\n       \n     - struct maintenance_opts {\n     + static const char * const builtin_maintenance_run_usage[] = {\n     +-\tN_(\"git maintenance run [--auto]\"),\n     ++\tN_(\"git maintenance run [--auto] [--[no-]quiet]\"),\n     + \tNULL\n     + };\n     + \n     + struct maintenance_run_opts {\n       \tint auto_flag;\n      +\tint quiet;\n       };\n       \n     - static int maintenance_task_gc(struct maintenance_opts *opts)\n     -@@ builtin/gc.c: static int maintenance_task_gc(struct maintenance_opts *opts)\n     + static int maintenance_task_gc(struct maintenance_run_opts *opts)\n     +@@ builtin/gc.c: static int maintenance_task_gc(struct maintenance_run_opts *opts)\n       \n       \tif (opts->auto_flag)\n       \t\tstrvec_push(&child.args, \"--auto\");\n     @@ builtin/gc.c: static int maintenance_task_gc(struct maintenance_opts *opts)\n       \n       \tclose_object_store(the_repository->objects);\n       \treturn run_command(&child);\n     -@@ builtin/gc.c: int cmd_maintenance(int argc, const char **argv, const char *prefix)\n     - \tstruct option builtin_maintenance_options[] = {\n     +@@ builtin/gc.c: static int maintenance_run(int argc, const char **argv, const char *prefix)\n     + \tstruct option builtin_maintenance_run_options[] = {\n       \t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n       \t\t\t N_(\"run tasks based on the state of the repository\")),\n      +\t\tOPT_BOOL(0, \"quiet\", &opts.quiet,\n      +\t\t\t N_(\"do not report progress or other information over stderr\")),\n       \t\tOPT_END()\n       \t};\n     - \n     -@@ builtin/gc.c: int cmd_maintenance(int argc, const char **argv, const char *prefix)\n     - \t\tusage_with_options(builtin_maintenance_usage,\n     - \t\t\t\t   builtin_maintenance_options);\n     + \tmemset(&opts, 0, sizeof(opts));\n       \n      +\topts.quiet = !isatty(2);\n      +\n       \targc = parse_options(argc, argv, prefix,\n     - \t\t\t     builtin_maintenance_options,\n     - \t\t\t     builtin_maintenance_usage,\n     + \t\t\t     builtin_maintenance_run_options,\n     + \t\t\t     builtin_maintenance_run_usage,\n      \n       ## t/t7900-maintenance.sh ##\n      @@ t/t7900-maintenance.sh: test_expect_success 'help text' '\n  3:  91741a0cfc =  3:  e28b332df4 maintenance: replace run_auto_gc()\n  4:  1db3b96280 !  4:  82326c1c38 maintenance: initialize task array\n     @@ Commit message\n      \n          The run subcommand will return a nonzero exit code if any task fails.\n          However, it will attempt all tasks in its loop before returning with the\n     -    failure. Also each failed task will send an error message.\n     +    failure. Also each failed task will print an error message.\n      \n          Helped-by: Taylor Blau <me@ttaylorr.com>\n          Helped-by: Junio C Hamano <gitster@pobox.com>\n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n      \n       ## builtin/gc.c ##\n     -@@ builtin/gc.c: static int maintenance_task_gc(struct maintenance_opts *opts)\n     +@@ builtin/gc.c: static int maintenance_task_gc(struct maintenance_run_opts *opts)\n       \treturn run_command(&child);\n       }\n       \n     -+typedef int maintenance_task_fn(struct maintenance_opts *opts);\n     ++typedef int maintenance_task_fn(struct maintenance_run_opts *opts);\n      +\n      +struct maintenance_task {\n      +\tconst char *name;\n     @@ builtin/gc.c: static int maintenance_task_gc(struct maintenance_opts *opts)\n      +\t},\n      +};\n      +\n     - static int maintenance_run(struct maintenance_opts *opts)\n     - {\n     --\treturn maintenance_task_gc(opts);\n     ++static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n     ++{\n      +\tint i;\n      +\tint result = 0;\n      +\n     @@ builtin/gc.c: static int maintenance_task_gc(struct maintenance_opts *opts)\n      +\t}\n      +\n      +\treturn result;\n     ++}\n     ++\n     + static int maintenance_run(int argc, const char **argv, const char *prefix)\n     + {\n     + \tstruct maintenance_run_opts opts;\n     +@@ builtin/gc.c: static int maintenance_run(int argc, const char **argv, const char *prefix)\n     + \tif (argc != 0)\n     + \t\tusage_with_options(builtin_maintenance_run_usage,\n     + \t\t\t\t   builtin_maintenance_run_options);\n     +-\treturn maintenance_task_gc(&opts);\n     ++\treturn maintenance_run_tasks(&opts);\n       }\n       \n     - int cmd_maintenance(int argc, const char **argv, const char *prefix)\n     + static const char builtin_maintenance_usage[] = N_(\"git maintenance run [<options>]\");\n  5:  50b457fd57 !  5:  06984a42bf maintenance: add commit-graph task\n     @@ Commit message\n          maintenance: add commit-graph task\n      \n          The first new task in the 'git maintenance' builtin is the\n     -    'commit-graph' job. It is based on the sequence of events in the\n     -    'commit-graph' job in Scalar [1]. This sequence is as follows:\n     +    'commit-graph' task. This updates the commit-graph file\n     +    incrementally with the command\n      \n     -    1. git commit-graph write --reachable --split\n     -    2. git commit-graph verify --shallow\n     -    3. If the verify succeeds, stop.\n     -    4. Delete the commit-graph-chain file.\n     -    5. git commit-graph write --reachable --split\n     +            git commit-graph write --reachable --split\n      \n          By writing an incremental commit-graph file using the \"--split\"\n          option we minimize the disruption from this operation. The default\n     @@ Commit message\n          This could be avoided by a future update to use the --expire-time\n          argument when writing the commit-graph.\n      \n     -    By using 'git commit-graph verify --shallow' we can ensure that\n     -    the file we just wrote is valid. This is an extra safety precaution\n     -    that is faster than our 'write' subcommand. In the rare situation\n     -    that the newest layer of the commit-graph is corrupt, we can \"fix\"\n     -    the corruption by deleting the commit-graph-chain file and rewrite\n     -    the full commit-graph as a new one-layer commit graph. This does\n     -    not completely prevent _that_ file from being corrupt, but it does\n     -    recompute the commit-graph by parsing commits from the object\n     -    database. In our use of this step in Scalar and VFS for Git, we\n     -    have only seen this issue arise because our microsoft/git fork\n     -    reverted 43d3561 (\"commit-graph write: don't die if the existing\n     -    graph is corrupt\" 2019-03-25) for a while to keep commit-graph\n     -    writes very fast. We dropped the revert when updating to v2.23.0.\n     -    The verify still has potential for catching corrupt data across\n     -    the layer boundary: if the new file has commit X with parent Y\n     -    in an old file but the commit ID for Y in the old file had a\n     -    bitswap, then we will notice that in the 'verify' command.\n     -\n     -    [1] https://github.com/microsoft/scalar/blob/master/Scalar.Common/Maintenance/CommitGraphStep.cs\n     -\n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n      \n       ## Documentation/git-maintenance.txt ##\n     @@ Documentation/git-maintenance.txt: run::\n       \n      +commit-graph::\n      +\tThe `commit-graph` job updates the `commit-graph` files incrementally,\n     -+\tthen verifies that the written data is correct. If the new layer has an\n     -+\tissue, then the chain file is removed and the `commit-graph` is\n     -+\trewritten from scratch.\n     -++\n     -+The incremental write is safe to run alongside concurrent Git processes\n     -+since it will not expire `.graph` files that were in the previous\n     -+`commit-graph-chain` file. They will be deleted by a later run based on\n     -+the expiration delay.\n     ++\tthen verifies that the written data is correct. The incremental\n     ++\twrite is safe to run alongside concurrent Git processes since it\n     ++\twill not expire `.graph` files that were in the previous\n     ++\t`commit-graph-chain` file. They will be deleted by a later run based\n     ++\ton the expiration delay.\n      +\n       gc::\n       \tClean up unnecessary files and optimize the local repository. \"GC\"\n       \tstands for \"garbage collection,\" but this task performs many\n      \n       ## builtin/gc.c ##\n     -@@ builtin/gc.c: struct maintenance_opts {\n     +@@ builtin/gc.c: struct maintenance_run_opts {\n       \tint quiet;\n       };\n       \n     -+static int run_write_commit_graph(struct maintenance_opts *opts)\n     ++static int run_write_commit_graph(struct maintenance_run_opts *opts)\n      +{\n      +\tstruct child_process child = CHILD_PROCESS_INIT;\n      +\n     @@ builtin/gc.c: struct maintenance_opts {\n      +\treturn !!run_command(&child);\n      +}\n      +\n     -+static int run_verify_commit_graph(struct maintenance_opts *opts)\n     ++static int maintenance_task_commit_graph(struct maintenance_run_opts *opts)\n      +{\n     -+\tstruct child_process child = CHILD_PROCESS_INIT;\n     -+\n     -+\tchild.git_cmd = 1;\n     -+\tstrvec_pushl(&child.args, \"commit-graph\", \"verify\",\n     -+\t\t     \"--shallow\", NULL);\n     -+\n     -+\tif (opts->quiet)\n     -+\t\tstrvec_push(&child.args, \"--no-progress\");\n     -+\n     -+\treturn !!run_command(&child);\n     -+}\n     -+\n     -+static int maintenance_task_commit_graph(struct maintenance_opts *opts)\n     -+{\n     -+\tstruct repository *r = the_repository;\n     -+\tchar *chain_path;\n     -+\n     -+\tclose_object_store(r->objects);\n     ++\tclose_object_store(the_repository->objects);\n      +\tif (run_write_commit_graph(opts)) {\n      +\t\terror(_(\"failed to write commit-graph\"));\n      +\t\treturn 1;\n      +\t}\n      +\n     -+\tif (!run_verify_commit_graph(opts))\n     -+\t\treturn 0;\n     -+\n     -+\twarning(_(\"commit-graph verify caught error, rewriting\"));\n     -+\n     -+\tchain_path = get_commit_graph_chain_filename(r->objects->odb);\n     -+\tif (unlink(chain_path)) {\n     -+\t\tUNLEAK(chain_path);\n     -+\t\tdie(_(\"failed to remove commit-graph at %s\"), chain_path);\n     -+\t}\n     -+\tfree(chain_path);\n     -+\n     -+\tif (!run_write_commit_graph(opts))\n     -+\t\treturn 0;\n     -+\n     -+\terror(_(\"failed to rewrite commit-graph\"));\n     -+\treturn 1;\n     ++\treturn 0;\n      +}\n      +\n     - static int maintenance_task_gc(struct maintenance_opts *opts)\n     + static int maintenance_task_gc(struct maintenance_run_opts *opts)\n       {\n       \tstruct child_process child = CHILD_PROCESS_INIT;\n      @@ builtin/gc.c: struct maintenance_task {\n     @@ builtin/gc.c: static struct maintenance_task tasks[] = {\n      +\t},\n       };\n       \n     - static int maintenance_run(struct maintenance_opts *opts)\n     + static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n      \n       ## commit-graph.c ##\n      @@ commit-graph.c: static char *get_split_graph_filename(struct object_directory *odb,\n     @@ commit-graph.c: static void expire_commit_graphs(struct write_commit_graph_conte\n       \t\tctx->num_commit_graphs_after = 0;\n      \n       ## commit-graph.h ##\n     -@@ commit-graph.h: struct commit;\n     - struct bloom_filter_settings;\n     +@@ commit-graph.h: struct raw_object_store;\n     + struct string_list;\n       \n       char *get_commit_graph_filename(struct object_directory *odb);\n      +char *get_commit_graph_chain_filename(struct object_directory *odb);\n  6:  85268bd53e !  6:  69298aee24 maintenance: add --task option\n     @@ Documentation/git-maintenance.txt: OPTIONS\n       \n      +--task=<task>::\n      +\tIf this option is specified one or more times, then only run the\n     -+\tspecified tasks in the specified order.\n     ++\tspecified tasks in the specified order. See the 'TASKS' section\n     ++\tfor the list of accepted `<task>` values.\n      +\n       GIT\n       ---\n       Part of the linkgit:git[1] suite\n      \n       ## builtin/gc.c ##\n     -@@ builtin/gc.c: typedef int maintenance_task_fn(struct maintenance_opts *opts);\n     - struct maintenance_task {\n     +@@ builtin/gc.c: int cmd_gc(int argc, const char **argv, const char *prefix)\n     + }\n     + \n     + static const char * const builtin_maintenance_run_usage[] = {\n     +-\tN_(\"git maintenance run [--auto] [--[no-]quiet]\"),\n     ++\tN_(\"git maintenance run [--auto] [--[no-]quiet] [--task=<task>]\"),\n     + \tNULL\n     + };\n     + \n     +@@ builtin/gc.c: struct maintenance_task {\n       \tconst char *name;\n       \tmaintenance_task_fn *fn;\n     --\tunsigned enabled:1;\n     -+\tunsigned enabled:1,\n     -+\t\t selected:1;\n     + \tunsigned enabled:1;\n     ++\n     ++\t/* -1 if not selected. */\n      +\tint selected_order;\n       };\n       \n     @@ builtin/gc.c: static struct maintenance_task tasks[] = {\n      +\treturn b->selected_order - a->selected_order;\n      +}\n      +\n     - static int maintenance_run(struct maintenance_opts *opts)\n     + static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n       {\n      -\tint i;\n      +\tint i, found_selected = 0;\n       \tint result = 0;\n       \n      +\tfor (i = 0; !found_selected && i < TASK__COUNT; i++)\n     -+\t\tfound_selected = tasks[i].selected;\n     ++\t\tfound_selected = tasks[i].selected_order >= 0;\n      +\n      +\tif (found_selected)\n      +\t\tQSORT(tasks, TASK__COUNT, compare_tasks_by_selection);\n      +\n       \tfor (i = 0; i < TASK__COUNT; i++) {\n      -\t\tif (!tasks[i].enabled)\n     -+\t\tif (found_selected && !tasks[i].selected)\n     ++\t\tif (found_selected && tasks[i].selected_order < 0)\n      +\t\t\tcontinue;\n      +\n      +\t\tif (!found_selected && !tasks[i].enabled)\n       \t\t\tcontinue;\n       \n       \t\tif (tasks[i].fn(opts)) {\n     -@@ builtin/gc.c: static int maintenance_run(struct maintenance_opts *opts)\n     +@@ builtin/gc.c: static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n       \treturn result;\n       }\n       \n     @@ builtin/gc.c: static int maintenance_run(struct maintenance_opts *opts)\n      +\tBUG_ON_OPT_NEG(unset);\n      +\n      +\tfor (i = 0; i < TASK__COUNT; i++) {\n     -+\t\tnum_selected += tasks[i].selected;\n     ++\t\tif (tasks[i].selected_order >= 0)\n     ++\t\t\tnum_selected++;\n      +\t\tif (!strcasecmp(tasks[i].name, arg)) {\n      +\t\t\ttask = &tasks[i];\n      +\t\t}\n     @@ builtin/gc.c: static int maintenance_run(struct maintenance_opts *opts)\n      +\t\treturn 1;\n      +\t}\n      +\n     -+\tif (task->selected) {\n     ++\tif (task->selected_order >= 0) {\n      +\t\terror(_(\"task '%s' cannot be selected multiple times\"), arg);\n      +\t\treturn 1;\n      +\t}\n      +\n     -+\ttask->selected = 1;\n      +\ttask->selected_order = num_selected + 1;\n      +\n      +\treturn 0;\n      +}\n      +\n     - int cmd_maintenance(int argc, const char **argv, const char *prefix)\n     + static int maintenance_run(int argc, const char **argv, const char *prefix)\n       {\n     - \tstruct maintenance_opts opts;\n     -@@ builtin/gc.c: int cmd_maintenance(int argc, const char **argv, const char *prefix)\n     ++\tint i;\n     + \tstruct maintenance_run_opts opts;\n     + \tstruct option builtin_maintenance_run_options[] = {\n     + \t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n       \t\t\t N_(\"run tasks based on the state of the repository\")),\n       \t\tOPT_BOOL(0, \"quiet\", &opts.quiet,\n       \t\t\t N_(\"do not report progress or other information over stderr\")),\n     @@ builtin/gc.c: int cmd_maintenance(int argc, const char **argv, const char *prefi\n      +\t\t\tPARSE_OPT_NONEG, task_option_parse),\n       \t\tOPT_END()\n       \t};\n     + \tmemset(&opts, 0, sizeof(opts));\n     + \n     + \topts.quiet = !isatty(2);\n       \n     ++\tfor (i = 0; i < TASK__COUNT; i++)\n     ++\t\ttasks[i].selected_order = -1;\n     ++\n     + \targc = parse_options(argc, argv, prefix,\n     + \t\t\t     builtin_maintenance_run_options,\n     + \t\t\t     builtin_maintenance_run_usage,\n      \n       ## t/t7900-maintenance.sh ##\n      @@ t/t7900-maintenance.sh: test_expect_success 'run [--auto|--quiet]' '\n  7:  6f86cfaa94 !  7:  3d513acdd8 maintenance: take a lock on the objects directory\n     @@ Commit message\n          lock is never committed, since it does not represent meaningful data.\n          Instead, it is only a placeholder.\n      \n     -    If the lock file already exists, then fail silently. This will become\n     -    very important later when we implement the 'fetch' task, as this is our\n     -    stop-gap from creating a recursive process loop between 'git fetch' and\n     -    'git maintenance run'.\n     +    If the lock file already exists, then fail with a warning. If '--auto'\n     +    is specified, then instead no warning is shown and no tasks are attempted.\n     +    This will become very important later when we implement the 'prefetch'\n     +    task, as this is our stop-gap from creating a recursive process loop\n     +    between 'git fetch' and 'git maintenance run --auto'.\n      \n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n      \n       ## builtin/gc.c ##\n     -@@ builtin/gc.c: static int maintenance_run(struct maintenance_opts *opts)\n     +@@ builtin/gc.c: static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n       {\n       \tint i, found_selected = 0;\n       \tint result = 0;\n     @@ builtin/gc.c: static int maintenance_run(struct maintenance_opts *opts)\n      +\t\t * that case.\n      +\t\t */\n      +\t\tif (!opts->auto_flag && !opts->quiet)\n     -+\t\t\terror(_(\"lock file '%s' exists, skipping maintenance\"),\n     -+\t\t\t      lock_path);\n     ++\t\t\twarning(_(\"lock file '%s' exists, skipping maintenance\"),\n     ++\t\t\t\tlock_path);\n      +\t\tfree(lock_path);\n      +\t\treturn 0;\n      +\t}\n      +\tfree(lock_path);\n       \n       \tfor (i = 0; !found_selected && i < TASK__COUNT; i++)\n     - \t\tfound_selected = tasks[i].selected;\n     -@@ builtin/gc.c: static int maintenance_run(struct maintenance_opts *opts)\n     + \t\tfound_selected = tasks[i].selected_order >= 0;\n     +@@ builtin/gc.c: static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n       \t\t}\n       \t}\n       \n  8:  5c0f9d69d1 !  8:  712f5f2d8e maintenance: create maintenance.<task>.enabled config\n     @@ Documentation/config/maintenance.txt (new)\n      @@\n      +maintenance.<task>.enabled::\n      +\tThis boolean config option controls whether the maintenance task\n     -+\twith name `<task>` is run when no `--task` option is specified.\n     -+\tBy default, only `maintenance.gc.enabled` is true.\n     ++\twith name `<task>` is run when no `--task` option is specified to\n     ++\t`git maintenance run`. These config values are ignored if a\n     ++\t`--task` option exists. By default, only `maintenance.gc.enabled`\n     ++\tis true.\n      \n       ## Documentation/git-maintenance.txt ##\n      @@ Documentation/git-maintenance.txt: SUBCOMMANDS\n     @@ Documentation/git-maintenance.txt: SUBCOMMANDS\n       \n       TASKS\n       -----\n     +@@ Documentation/git-maintenance.txt: OPTIONS\n     + \n     + --task=<task>::\n     + \tIf this option is specified one or more times, then only run the\n     +-\tspecified tasks in the specified order. See the 'TASKS' section\n     +-\tfor the list of accepted `<task>` values.\n     ++\tspecified tasks in the specified order. If no `--task=<task>`\n     ++\targuments are specified, then only the tasks with\n     ++\t`maintenance.<task>.enabled` configured as `true` are considered.\n     ++\tSee the 'TASKS' section for the list of accepted `<task>` values.\n     + \n     + GIT\n     + ---\n      \n       ## builtin/gc.c ##\n     -@@ builtin/gc.c: static int maintenance_run(struct maintenance_opts *opts)\n     +@@ builtin/gc.c: static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n       \treturn result;\n       }\n       \n     @@ builtin/gc.c: static int maintenance_run(struct maintenance_opts *opts)\n       static int task_option_parse(const struct option *opt,\n       \t\t\t     const char *arg, int unset)\n       {\n     -@@ builtin/gc.c: int cmd_maintenance(int argc, const char **argv, const char *prefix)\n     - \t\t\t\t   builtin_maintenance_options);\n     +@@ builtin/gc.c: static int maintenance_run(int argc, const char **argv, const char *prefix)\n     + \tmemset(&opts, 0, sizeof(opts));\n       \n       \topts.quiet = !isatty(2);\n      +\tinitialize_task_config();\n       \n     - \targc = parse_options(argc, argv, prefix,\n     - \t\t\t     builtin_maintenance_options,\n     + \tfor (i = 0; i < TASK__COUNT; i++)\n     + \t\ttasks[i].selected_order = -1;\n      \n       ## t/t7900-maintenance.sh ##\n      @@ t/t7900-maintenance.sh: test_expect_success 'run [--auto|--quiet]' '\n  9:  68bf5bef4b !  9:  69d3b48fd4 maintenance: use pointers to check --auto\n     @@ Commit message\n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n      \n       ## builtin/gc.c ##\n     -@@ builtin/gc.c: static int maintenance_task_gc(struct maintenance_opts *opts)\n     +@@ builtin/gc.c: static int maintenance_task_gc(struct maintenance_run_opts *opts)\n       \n     - typedef int maintenance_task_fn(struct maintenance_opts *opts);\n     + typedef int maintenance_task_fn(struct maintenance_run_opts *opts);\n       \n      +/*\n      + * An auto condition function returns 1 if the task should run\n     @@ builtin/gc.c: static int maintenance_task_gc(struct maintenance_opts *opts)\n       \tconst char *name;\n       \tmaintenance_task_fn *fn;\n      +\tmaintenance_auto_fn *auto_condition;\n     - \tunsigned enabled:1,\n     - \t\t selected:1;\n     - \tint selected_order;\n     + \tunsigned enabled:1;\n     + \n     + \t/* -1 if not selected. */\n      @@ builtin/gc.c: static struct maintenance_task tasks[] = {\n       \t[TASK_GC] = {\n       \t\t\"gc\",\n     @@ builtin/gc.c: static struct maintenance_task tasks[] = {\n       \t\t1,\n       \t},\n       \t[TASK_COMMIT_GRAPH] = {\n     -@@ builtin/gc.c: static int maintenance_run(struct maintenance_opts *opts)\n     +@@ builtin/gc.c: static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n       \t\tif (!found_selected && !tasks[i].enabled)\n       \t\t\tcontinue;\n       \n 10:  fc097c389a ! 10:  4c3115fe35 maintenance: add auto condition for commit-graph task\n     @@ Commit message\n      \n       ## Documentation/config/maintenance.txt ##\n      @@ Documentation/config/maintenance.txt: maintenance.<task>.enabled::\n     - \tThis boolean config option controls whether the maintenance task\n     - \twith name `<task>` is run when no `--task` option is specified.\n     - \tBy default, only `maintenance.gc.enabled` is true.\n     + \t`git maintenance run`. These config values are ignored if a\n     + \t`--task` option exists. By default, only `maintenance.gc.enabled`\n     + \tis true.\n      +\n      +maintenance.commit-graph.auto::\n      +\tThis integer config option controls how often the `commit-graph` task\n     @@ builtin/gc.c\n       \n       #define FAILED_RUN \"failed to run %s\"\n       \n     -@@ builtin/gc.c: struct maintenance_opts {\n     +@@ builtin/gc.c: struct maintenance_run_opts {\n       \tint quiet;\n       };\n       \n      +/* Remember to update object flag allocation in object.h */\n     -+#define PARENT1\t\t(1u<<16)\n     ++#define SEEN\t\t(1u<<0)\n      +\n     -+static int num_commits_not_in_graph = 0;\n     -+static int limit_commits_not_in_graph = 100;\n     ++struct cg_auto_data {\n     ++\tint num_not_in_graph;\n     ++\tint limit;\n     ++};\n      +\n      +static int dfs_on_ref(const char *refname,\n      +\t\t      const struct object_id *oid, int flags,\n      +\t\t      void *cb_data)\n      +{\n     ++\tstruct cg_auto_data *data = (struct cg_auto_data *)cb_data;\n      +\tint result = 0;\n      +\tstruct object_id peeled;\n      +\tstruct commit_list *stack = NULL;\n     @@ builtin/gc.c: struct maintenance_opts {\n      +\t\tfor (parent = commit->parents; parent; parent = parent->next) {\n      +\t\t\tif (parse_commit(parent->item) ||\n      +\t\t\t    commit_graph_position(parent->item) != COMMIT_NOT_FROM_GRAPH ||\n     -+\t\t\t    parent->item->object.flags & PARENT1)\n     ++\t\t\t    parent->item->object.flags & SEEN)\n      +\t\t\t\tcontinue;\n      +\n     -+\t\t\tparent->item->object.flags |= PARENT1;\n     -+\t\t\tnum_commits_not_in_graph++;\n     ++\t\t\tparent->item->object.flags |= SEEN;\n     ++\t\t\tdata->num_not_in_graph++;\n      +\n     -+\t\t\tif (num_commits_not_in_graph >= limit_commits_not_in_graph) {\n     ++\t\t\tif (data->num_not_in_graph >= data->limit) {\n      +\t\t\t\tresult = 1;\n      +\t\t\t\tbreak;\n      +\t\t\t}\n     @@ builtin/gc.c: struct maintenance_opts {\n      +static int should_write_commit_graph(void)\n      +{\n      +\tint result;\n     ++\tstruct cg_auto_data data;\n      +\n     ++\tdata.num_not_in_graph = 0;\n     ++\tdata.limit = 100;\n      +\tgit_config_get_int(\"maintenance.commit-graph.auto\",\n     -+\t\t\t   &limit_commits_not_in_graph);\n     ++\t\t\t   &data.limit);\n      +\n     -+\tif (!limit_commits_not_in_graph)\n     ++\tif (!data.limit)\n      +\t\treturn 0;\n     -+\tif (limit_commits_not_in_graph < 0)\n     ++\tif (data.limit < 0)\n      +\t\treturn 1;\n      +\n     -+\tresult = for_each_ref(dfs_on_ref, NULL);\n     ++\tresult = for_each_ref(dfs_on_ref, &data);\n      +\n     -+\tclear_commit_marks_all(PARENT1);\n     ++\tclear_commit_marks_all(SEEN);\n      +\n      +\treturn result;\n      +}\n      +\n     - static int run_write_commit_graph(struct maintenance_opts *opts)\n     + static int run_write_commit_graph(struct maintenance_run_opts *opts)\n       {\n       \tstruct child_process child = CHILD_PROCESS_INIT;\n      @@ builtin/gc.c: static struct maintenance_task tasks[] = {\n     @@ builtin/gc.c: static struct maintenance_task tasks[] = {\n      \n       ## object.h ##\n      @@ object.h: struct object_array {\n     +  * sha1-name.c:                                              20\n        * list-objects-filter.c:                                      21\n        * builtin/fsck.c:           0--3\n     ++ * builtin/gc.c:             0\n        * builtin/index-pack.c:                                     2021\n     -+ * builtin/maintenance.c:                           16\n        * builtin/pack-objects.c:                                   20\n        * builtin/reflog.c:                   10--12\n     -  * builtin/show-branch.c:    0-------------------------------------------26\n 11:  46fbe161aa ! 11:  652a8eac57 maintenance: add trace2 regions for task execution\n     @@ Commit message\n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n      \n       ## builtin/gc.c ##\n     -@@ builtin/gc.c: static int maintenance_run(struct maintenance_opts *opts)\n     +@@ builtin/gc.c: static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n       \t\t     !tasks[i].auto_condition()))\n       \t\t\tcontinue;\n       \n\n-- \ngitgitgadget\n"},{"id":"404441","messageId":"aa961af387b7f458f75ad60b9a2a45da4bb43794.1598380427.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v3.git.1598380426.gitgitgadget@gmail.com","subject":"[PATCH v3 01/11] maintenance: create basic maintenance runner","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-25T18:33:36Z","receivedAt":"2020-08-25T18:34:00Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe 'gc' builtin is our current entrypoint for automatically maintaining\na repository. This one tool does many operations, such as repacking the\nrepository, packing refs, and rewriting the commit-graph file. The name\nimplies it performs \"garbage collection\" which means several different\nthings, and some users may not want to use this operation that rewrites\nthe entire object database.\n\nCreate a new 'maintenance' builtin that will become a more general-\npurpose command. To start, it will only support the 'run' subcommand,\nbut will later expand to add subcommands for scheduling maintenance in\nthe background.\n\nFor now, the 'maintenance' builtin is a thin shim over the 'gc' builtin.\nIn fact, the only option is the '--auto' toggle, which is handed\ndirectly to the 'gc' builtin. The current change is isolated to this\nsimple operation to prevent more interesting logic from being lost in\nall of the boilerplate of adding a new builtin.\n\nUse existing builtin/gc.c file because we want to share code between the\ntwo builtins. It is possible that we will have 'maintenance' replace the\n'gc' builtin entirely at some point, leaving 'git gc' as an alias for\nsome specific arguments to 'git maintenance run'.\n\nCreate a new test_subcommand helper that allows us to test if a certain\nsubcommand was run. It requires storing the GIT_TRACE2_EVENT logs in a\nfile. A negation mode is available that will be used in later tests.\n\nHelped-by: Jonathan Nieder <jrnieder@gmail.com>\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n .gitignore                        |  1 +\n Documentation/git-maintenance.txt | 57 +++++++++++++++++++++++++++++++\n builtin.h                         |  1 +\n builtin/gc.c                      | 57 +++++++++++++++++++++++++++++++\n command-list.txt                  |  1 +\n git.c                             |  1 +\n t/t7900-maintenance.sh            | 21 ++++++++++++\n t/test-lib-functions.sh           | 33 ++++++++++++++++++\n 8 files changed, 172 insertions(+)\n create mode 100644 Documentation/git-maintenance.txt\n create mode 100755 t/t7900-maintenance.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex ee509a2ad2..a5808fa30d 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -90,6 +90,7 @@\n /git-ls-tree\n /git-mailinfo\n /git-mailsplit\n+/git-maintenance\n /git-merge\n /git-merge-base\n /git-merge-index\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nnew file mode 100644\nindex 0000000000..ff47fb3641\n--- /dev/null\n+++ b/Documentation/git-maintenance.txt\n@@ -0,0 +1,57 @@\n+git-maintenance(1)\n+==================\n+\n+NAME\n+----\n+git-maintenance - Run tasks to optimize Git repository data\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git maintenance' run [<options>]\n+\n+\n+DESCRIPTION\n+-----------\n+Run tasks to optimize Git repository data, speeding up other Git commands\n+and reducing storage requirements for the repository.\n+\n+Git commands that add repository data, such as `git add` or `git fetch`,\n+are optimized for a responsive user experience. These commands do not take\n+time to optimize the Git data, since such optimizations scale with the full\n+size of the repository while these user commands each perform a relatively\n+small action.\n+\n+The `git maintenance` command provides flexibility for how to optimize the\n+Git repository.\n+\n+SUBCOMMANDS\n+-----------\n+\n+run::\n+\tRun one or more maintenance tasks.\n+\n+TASKS\n+-----\n+\n+gc::\n+\tClean up unnecessary files and optimize the local repository. \"GC\"\n+\tstands for \"garbage collection,\" but this task performs many\n+\tsmaller tasks. This task can be expensive for large repositories,\n+\tas it repacks all Git objects into a single pack-file. It can also\n+\tbe disruptive in some situations, as it deletes stale data. See\n+\tlinkgit:git-gc[1] for more details on garbage collection in Git.\n+\n+OPTIONS\n+-------\n+--auto::\n+\tWhen combined with the `run` subcommand, run maintenance tasks\n+\tonly if certain thresholds are met. For example, the `gc` task\n+\truns when the number of loose objects exceeds the number stored\n+\tin the `gc.auto` config setting, or when the number of pack-files\n+\texceeds the `gc.autoPackLimit` config setting.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/builtin.h b/builtin.h\nindex a5ae15bfe5..17c1c0ce49 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -167,6 +167,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix);\n int cmd_ls_remote(int argc, const char **argv, const char *prefix);\n int cmd_mailinfo(int argc, const char **argv, const char *prefix);\n int cmd_mailsplit(int argc, const char **argv, const char *prefix);\n+int cmd_maintenance(int argc, const char **argv, const char *prefix);\n int cmd_merge(int argc, const char **argv, const char *prefix);\n int cmd_merge_base(int argc, const char **argv, const char *prefix);\n int cmd_merge_index(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex aafa0946f5..7f2771e786 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -699,3 +699,60 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n \n \treturn 0;\n }\n+\n+static const char * const builtin_maintenance_run_usage[] = {\n+\tN_(\"git maintenance run [--auto]\"),\n+\tNULL\n+};\n+\n+struct maintenance_run_opts {\n+\tint auto_flag;\n+};\n+\n+static int maintenance_task_gc(struct maintenance_run_opts *opts)\n+{\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\n+\tchild.git_cmd = 1;\n+\tstrvec_push(&child.args, \"gc\");\n+\n+\tif (opts->auto_flag)\n+\t\tstrvec_push(&child.args, \"--auto\");\n+\n+\tclose_object_store(the_repository->objects);\n+\treturn run_command(&child);\n+}\n+\n+static int maintenance_run(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct maintenance_run_opts opts;\n+\tstruct option builtin_maintenance_run_options[] = {\n+\t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n+\t\t\t N_(\"run tasks based on the state of the repository\")),\n+\t\tOPT_END()\n+\t};\n+\tmemset(&opts, 0, sizeof(opts));\n+\n+\targc = parse_options(argc, argv, prefix,\n+\t\t\t     builtin_maintenance_run_options,\n+\t\t\t     builtin_maintenance_run_usage,\n+\t\t\t     PARSE_OPT_STOP_AT_NON_OPTION);\n+\n+\tif (argc != 0)\n+\t\tusage_with_options(builtin_maintenance_run_usage,\n+\t\t\t\t   builtin_maintenance_run_options);\n+\treturn maintenance_task_gc(&opts);\n+}\n+\n+static const char builtin_maintenance_usage[] = N_(\"git maintenance run [<options>]\");\n+\n+int cmd_maintenance(int argc, const char **argv, const char *prefix)\n+{\n+\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n+\t\tusage(builtin_maintenance_usage);\n+\n+\tif (!strcmp(argv[1], \"run\"))\n+\t\treturn maintenance_run(argc - 1, argv + 1, prefix);\n+\n+\tdie(_(\"invalid subcommand: %s\"), argv[1]);\n+}\ndiff --git a/command-list.txt b/command-list.txt\nindex e5901f2213..0e3204e7d1 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -117,6 +117,7 @@ git-ls-remote                           plumbinginterrogators\n git-ls-tree                             plumbinginterrogators\n git-mailinfo                            purehelpers\n git-mailsplit                           purehelpers\n+git-maintenance                         mainporcelain\n git-merge                               mainporcelain           history\n git-merge-base                          plumbinginterrogators\n git-merge-file                          plumbingmanipulators\ndiff --git a/git.c b/git.c\nindex 8bd1d7551d..24f250d29a 100644\n--- a/git.c\n+++ b/git.c\n@@ -529,6 +529,7 @@ static struct cmd_struct commands[] = {\n \t{ \"ls-tree\", cmd_ls_tree, RUN_SETUP },\n \t{ \"mailinfo\", cmd_mailinfo, RUN_SETUP_GENTLY | NO_PARSEOPT },\n \t{ \"mailsplit\", cmd_mailsplit, NO_PARSEOPT },\n+\t{ \"maintenance\", cmd_maintenance, RUN_SETUP_GENTLY | NO_PARSEOPT },\n \t{ \"merge\", cmd_merge, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"merge-base\", cmd_merge_base, RUN_SETUP },\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nnew file mode 100755\nindex 0000000000..14aa691f19\n--- /dev/null\n+++ b/t/t7900-maintenance.sh\n@@ -0,0 +1,21 @@\n+#!/bin/sh\n+\n+test_description='git maintenance builtin'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'help text' '\n+\ttest_expect_code 129 git maintenance -h 2>err &&\n+\ttest_i18ngrep \"usage: git maintenance run\" err &&\n+\ttest_expect_code 128 git maintenance barf 2>err &&\n+\ttest_i18ngrep \"invalid subcommand: barf\" err\n+'\n+\n+test_expect_success 'run [--auto]' '\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-auto.txt\" git maintenance run &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" git maintenance run --auto &&\n+\ttest_subcommand git gc <run-no-auto.txt &&\n+\ttest_subcommand git gc --auto <run-auto.txt\n+'\n+\n+test_done\ndiff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh\nindex 6a8e194a99..d805e73f45 100644\n--- a/t/test-lib-functions.sh\n+++ b/t/test-lib-functions.sh\n@@ -1628,3 +1628,36 @@ test_path_is_hidden () {\n \tcase \"$(\"$SYSTEMROOT\"/system32/attrib \"$1\")\" in *H*?:*) return 0;; esac\n \treturn 1\n }\n+\n+# Check that the given command was invoked as part of the\n+# trace2-format trace on stdin.\n+#\n+#\ttest_subcommand [!] <command> <args>... < <trace>\n+#\n+# For example, to look for an invocation of \"git upload-pack\n+# /path/to/repo\"\n+#\n+#\tGIT_TRACE2_EVENT=event.log git fetch ... &&\n+#\ttest_subcommand git upload-pack \"$PATH\" <event.log\n+#\n+# If the first parameter passed is !, this instead checks that\n+# the given command was not called.\n+#\n+test_subcommand () {\n+\tlocal negate=\n+\tif test \"$1\" = \"!\"\n+\tthen\n+\t\tnegate=t\n+\t\tshift\n+\tfi\n+\n+\tlocal expr=$(printf '\"%s\",' \"$@\")\n+\texpr=\"${expr%,}\"\n+\n+\tif test -n \"$negate\"\n+\tthen\n+\t\t! grep \"\\[$expr\\]\"\n+\telse\n+\t\tgrep \"\\[$expr\\]\"\n+\tfi\n+}\n-- \ngitgitgadget\n\n"},{"id":"404442","messageId":"e28b332df428d7f929715eea37e923ce6505313c.1598380427.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v3.git.1598380426.gitgitgadget@gmail.com","subject":"[PATCH v3 03/11] maintenance: replace run_auto_gc()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-25T18:33:38Z","receivedAt":"2020-08-25T18:34:07Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe run_auto_gc() method is used in several places to trigger a check\nfor repo maintenance after some Git commands, such as 'git commit' or\n'git fetch'.\n\nTo allow for extra customization of this maintenance activity, replace\nthe 'git gc --auto [--quiet]' call with one to 'git maintenance run\n--auto [--quiet]'. As we extend the maintenance builtin with other\nsteps, users will be able to select different maintenance activities.\n\nRename run_auto_gc() to run_auto_maintenance() to be clearer what is\nhappening on this call, and to expose all callers in the current diff.\nRewrite the method to use a struct child_process to simplify the calls\nslightly.\n\nSince 'git fetch' already allows disabling the 'git gc --auto'\nsubprocess, add an equivalent option with a different name to be more\ndescriptive of the new behavior: '--[no-]maintenance'. Update the\ndocumentation to include these options at the same time.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/fetch-options.txt |  6 ++++--\n Documentation/git-clone.txt     |  6 +++---\n builtin/am.c                    |  2 +-\n builtin/commit.c                |  2 +-\n builtin/fetch.c                 |  6 ++++--\n builtin/merge.c                 |  2 +-\n builtin/rebase.c                |  4 ++--\n run-command.c                   | 16 +++++++---------\n run-command.h                   |  2 +-\n t/t5510-fetch.sh                |  2 +-\n 10 files changed, 25 insertions(+), 23 deletions(-)\n\ndiff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt\nindex 07d06ff445..b65a758661 100644\n--- a/Documentation/fetch-options.txt\n+++ b/Documentation/fetch-options.txt\n@@ -95,9 +95,11 @@ ifndef::git-pull[]\n \tAllow several <repository> and <group> arguments to be\n \tspecified. No <refspec>s may be specified.\n \n+--[no-]auto-maintenance::\n --[no-]auto-gc::\n-\tRun `git gc --auto` at the end to perform garbage collection\n-\tif needed. This is enabled by default.\n+\tRun `git maintenance run --auto` at the end to perform automatic\n+\trepository maintenance if needed. (`--[no-]auto-gc` is a synonym.)\n+\tThis is enabled by default.\n \n --[no-]write-commit-graph::\n \tWrite a commit-graph after fetching. This overrides the config\ndiff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt\nindex c898310099..097e6a86c5 100644\n--- a/Documentation/git-clone.txt\n+++ b/Documentation/git-clone.txt\n@@ -78,9 +78,9 @@ repository using this option and then delete branches (or use any\n other Git command that makes any existing commit unreferenced) in the\n source repository, some objects may become unreferenced (or dangling).\n These objects may be removed by normal Git operations (such as `git commit`)\n-which automatically call `git gc --auto`. (See linkgit:git-gc[1].)\n-If these objects are removed and were referenced by the cloned repository,\n-then the cloned repository will become corrupt.\n+which automatically call `git maintenance run --auto`. (See\n+linkgit:git-maintenance[1].) If these objects are removed and were referenced\n+by the cloned repository, then the cloned repository will become corrupt.\n +\n Note that running `git repack` without the `--local` option in a repository\n cloned with `--shared` will copy objects from the source repository into a pack\ndiff --git a/builtin/am.c b/builtin/am.c\nindex dd4e6c2d9b..68e9d17379 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1795,7 +1795,7 @@ static void am_run(struct am_state *state, int resume)\n \tif (!state->rebasing) {\n \t\tam_destroy(state);\n \t\tclose_object_store(the_repository->objects);\n-\t\trun_auto_gc(state->quiet);\n+\t\trun_auto_maintenance(state->quiet);\n \t}\n }\n \ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 69ac78d5e5..f9b0a0c05d 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1702,7 +1702,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \tgit_test_write_commit_graph_or_die();\n \n \trepo_rerere(the_repository, 0);\n-\trun_auto_gc(quiet);\n+\trun_auto_maintenance(quiet);\n \trun_commit_hook(use_editor, get_index_file(), \"post-commit\", NULL);\n \tif (amend && !no_post_rewrite) {\n \t\tcommit_post_rewrite(the_repository, current_head, &oid);\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 2eb8d6a5a5..cb38e6f5ec 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -199,8 +199,10 @@ static struct option builtin_fetch_options[] = {\n \tOPT_STRING_LIST(0, \"negotiation-tip\", &negotiation_tip, N_(\"revision\"),\n \t\t\tN_(\"report that we have only objects reachable from this object\")),\n \tOPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),\n+\tOPT_BOOL(0, \"auto-maintenance\", &enable_auto_gc,\n+\t\t N_(\"run 'maintenance --auto' after fetching\")),\n \tOPT_BOOL(0, \"auto-gc\", &enable_auto_gc,\n-\t\t N_(\"run 'gc --auto' after fetching\")),\n+\t\t N_(\"run 'maintenance --auto' after fetching\")),\n \tOPT_BOOL(0, \"show-forced-updates\", &fetch_show_forced_updates,\n \t\t N_(\"check for forced-updates on all updated branches\")),\n \tOPT_BOOL(0, \"write-commit-graph\", &fetch_write_commit_graph,\n@@ -1891,7 +1893,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)\n \tclose_object_store(the_repository->objects);\n \n \tif (enable_auto_gc)\n-\t\trun_auto_gc(verbosity < 0);\n+\t\trun_auto_maintenance(verbosity < 0);\n \n \treturn result;\n }\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 74829a838e..8f31bfab56 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -456,7 +456,7 @@ static void finish(struct commit *head_commit,\n \t\t\t * user should see them.\n \t\t\t */\n \t\t\tclose_object_store(the_repository->objects);\n-\t\t\trun_auto_gc(verbosity < 0);\n+\t\t\trun_auto_maintenance(verbosity < 0);\n \t\t}\n \t}\n \tif (new_head && show_diffstat) {\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex dadb52fa92..9ffba9009d 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -728,10 +728,10 @@ static int finish_rebase(struct rebase_options *opts)\n \tapply_autostash(state_dir_path(\"autostash\", opts));\n \tclose_object_store(the_repository->objects);\n \t/*\n-\t * We ignore errors in 'gc --auto', since the\n+\t * We ignore errors in 'git maintenance run --auto', since the\n \t * user should see them.\n \t */\n-\trun_auto_gc(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE)));\n+\trun_auto_maintenance(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE)));\n \tif (opts->type == REBASE_MERGE) {\n \t\tstruct replay_opts replay = REPLAY_OPTS_INIT;\n \ndiff --git a/run-command.c b/run-command.c\nindex cc9c3296ba..2ee59acdc8 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -1866,15 +1866,13 @@ int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,\n \treturn result;\n }\n \n-int run_auto_gc(int quiet)\n+int run_auto_maintenance(int quiet)\n {\n-\tstruct strvec argv_gc_auto = STRVEC_INIT;\n-\tint status;\n+\tstruct child_process maint = CHILD_PROCESS_INIT;\n \n-\tstrvec_pushl(&argv_gc_auto, \"gc\", \"--auto\", NULL);\n-\tif (quiet)\n-\t\tstrvec_push(&argv_gc_auto, \"--quiet\");\n-\tstatus = run_command_v_opt(argv_gc_auto.v, RUN_GIT_CMD);\n-\tstrvec_clear(&argv_gc_auto);\n-\treturn status;\n+\tmaint.git_cmd = 1;\n+\tstrvec_pushl(&maint.args, \"maintenance\", \"run\", \"--auto\", NULL);\n+\tstrvec_push(&maint.args, quiet ? \"--quiet\" : \"--no-quiet\");\n+\n+\treturn run_command(&maint);\n }\ndiff --git a/run-command.h b/run-command.h\nindex 8b9bfaef16..6472b38bde 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -221,7 +221,7 @@ int run_hook_ve(const char *const *env, const char *name, va_list args);\n /*\n  * Trigger an auto-gc\n  */\n-int run_auto_gc(int quiet);\n+int run_auto_maintenance(int quiet);\n \n #define RUN_COMMAND_NO_STDIN 1\n #define RUN_GIT_CMD\t     2\t/*If this is to be git sub-command */\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 2a1abe91f0..a9682c5669 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -934,7 +934,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '\n \t\tgit config fetch.unpackLimit 1 &&\n \t\tgit config gc.autoPackLimit 1 &&\n \t\tgit config gc.autoDetach false &&\n-\t\tGIT_ASK_YESNO=\"$D/askyesno\" git fetch >fetch.out 2>&1 &&\n+\t\tGIT_ASK_YESNO=\"$D/askyesno\" git fetch --verbose >fetch.out 2>&1 &&\n \t\ttest_i18ngrep \"Auto packing the repository\" fetch.out &&\n \t\t! grep \"Should I try again\" fetch.out\n \t)\n-- \ngitgitgadget\n\n"},{"id":"404443","messageId":"5386d8a6283d814c0d0d2b7a180409f064cea709.1598380427.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v3.git.1598380426.gitgitgadget@gmail.com","subject":"[PATCH v3 02/11] maintenance: add --quiet option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-25T18:33:37Z","receivedAt":"2020-08-25T18:34:10Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nMaintenance activities are commonly used as steps in larger scripts.\nProviding a '--quiet' option allows those scripts to be less noisy when\nrun on a terminal window. Turn this mode on by default when stderr is\nnot a terminal.\n\nPipe the option to the 'git gc' child process.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-maintenance.txt |  3 +++\n builtin/gc.c                      | 11 ++++++++++-\n t/t7900-maintenance.sh            | 15 ++++++++++-----\n 3 files changed, 23 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nindex ff47fb3641..04fa0fe329 100644\n--- a/Documentation/git-maintenance.txt\n+++ b/Documentation/git-maintenance.txt\n@@ -52,6 +52,9 @@ OPTIONS\n \tin the `gc.auto` config setting, or when the number of pack-files\n \texceeds the `gc.autoPackLimit` config setting.\n \n+--quiet::\n+\tDo not report progress or other information over `stderr`.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 7f2771e786..da11f1edcd 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -701,12 +701,13 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n }\n \n static const char * const builtin_maintenance_run_usage[] = {\n-\tN_(\"git maintenance run [--auto]\"),\n+\tN_(\"git maintenance run [--auto] [--[no-]quiet]\"),\n \tNULL\n };\n \n struct maintenance_run_opts {\n \tint auto_flag;\n+\tint quiet;\n };\n \n static int maintenance_task_gc(struct maintenance_run_opts *opts)\n@@ -718,6 +719,10 @@ static int maintenance_task_gc(struct maintenance_run_opts *opts)\n \n \tif (opts->auto_flag)\n \t\tstrvec_push(&child.args, \"--auto\");\n+\tif (opts->quiet)\n+\t\tstrvec_push(&child.args, \"--quiet\");\n+\telse\n+\t\tstrvec_push(&child.args, \"--no-quiet\");\n \n \tclose_object_store(the_repository->objects);\n \treturn run_command(&child);\n@@ -729,10 +734,14 @@ static int maintenance_run(int argc, const char **argv, const char *prefix)\n \tstruct option builtin_maintenance_run_options[] = {\n \t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n \t\t\t N_(\"run tasks based on the state of the repository\")),\n+\t\tOPT_BOOL(0, \"quiet\", &opts.quiet,\n+\t\t\t N_(\"do not report progress or other information over stderr\")),\n \t\tOPT_END()\n \t};\n \tmemset(&opts, 0, sizeof(opts));\n \n+\topts.quiet = !isatty(2);\n+\n \targc = parse_options(argc, argv, prefix,\n \t\t\t     builtin_maintenance_run_options,\n \t\t\t     builtin_maintenance_run_usage,\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex 14aa691f19..47d512464c 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -11,11 +11,16 @@ test_expect_success 'help text' '\n \ttest_i18ngrep \"invalid subcommand: barf\" err\n '\n \n-test_expect_success 'run [--auto]' '\n-\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-auto.txt\" git maintenance run &&\n-\tGIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" git maintenance run --auto &&\n-\ttest_subcommand git gc <run-no-auto.txt &&\n-\ttest_subcommand git gc --auto <run-auto.txt\n+test_expect_success 'run [--auto|--quiet]' '\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-auto.txt\" \\\n+\t\tgit maintenance run 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" \\\n+\t\tgit maintenance run --auto 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-quiet.txt\" \\\n+\t\tgit maintenance run --no-quiet 2>/dev/null &&\n+\ttest_subcommand git gc --quiet <run-no-auto.txt &&\n+\ttest_subcommand git gc --auto --quiet <run-auto.txt &&\n+\ttest_subcommand git gc --no-quiet <run-no-quiet.txt\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"404444","messageId":"82326c1c38ca6d7ec71bddb50d33c1e5bc3421fb.1598380427.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v3.git.1598380426.gitgitgadget@gmail.com","subject":"[PATCH v3 04/11] maintenance: initialize task array","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-25T18:33:39Z","receivedAt":"2020-08-25T18:34:12Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nIn anticipation of implementing multiple maintenance tasks inside the\n'maintenance' builtin, use a list of structs to describe the work to be\ndone.\n\nThe struct maintenance_task stores the name of the task (as given by a\nfuture command-line argument) along with a function pointer to its\nimplementation and a boolean for whether the step is enabled.\n\nA list these structs are initialized with the full list of implemented\ntasks along with a default order. For now, this list only contains the\n\"gc\" task. This task is also the only task enabled by default.\n\nThe run subcommand will return a nonzero exit code if any task fails.\nHowever, it will attempt all tasks in its loop before returning with the\nfailure. Also each failed task will print an error message.\n\nHelped-by: Taylor Blau <me@ttaylorr.com>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/gc.c | 43 ++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 42 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex da11f1edcd..81643515e5 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -728,6 +728,47 @@ static int maintenance_task_gc(struct maintenance_run_opts *opts)\n \treturn run_command(&child);\n }\n \n+typedef int maintenance_task_fn(struct maintenance_run_opts *opts);\n+\n+struct maintenance_task {\n+\tconst char *name;\n+\tmaintenance_task_fn *fn;\n+\tunsigned enabled:1;\n+};\n+\n+enum maintenance_task_label {\n+\tTASK_GC,\n+\n+\t/* Leave as final value */\n+\tTASK__COUNT\n+};\n+\n+static struct maintenance_task tasks[] = {\n+\t[TASK_GC] = {\n+\t\t\"gc\",\n+\t\tmaintenance_task_gc,\n+\t\t1,\n+\t},\n+};\n+\n+static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n+{\n+\tint i;\n+\tint result = 0;\n+\n+\tfor (i = 0; i < TASK__COUNT; i++) {\n+\t\tif (!tasks[i].enabled)\n+\t\t\tcontinue;\n+\n+\t\tif (tasks[i].fn(opts)) {\n+\t\t\terror(_(\"task '%s' failed\"), tasks[i].name);\n+\t\t\tresult = 1;\n+\t\t}\n+\t}\n+\n+\treturn result;\n+}\n+\n static int maintenance_run(int argc, const char **argv, const char *prefix)\n {\n \tstruct maintenance_run_opts opts;\n@@ -750,7 +791,7 @@ static int maintenance_run(int argc, const char **argv, const char *prefix)\n \tif (argc != 0)\n \t\tusage_with_options(builtin_maintenance_run_usage,\n \t\t\t\t   builtin_maintenance_run_options);\n-\treturn maintenance_task_gc(&opts);\n+\treturn maintenance_run_tasks(&opts);\n }\n \n static const char builtin_maintenance_usage[] = N_(\"git maintenance run [<options>]\");\n-- \ngitgitgadget\n\n"},{"id":"404445","messageId":"69298aee24f317b03c39eef341f4d6ebebd88bc7.1598380427.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v3.git.1598380426.gitgitgadget@gmail.com","subject":"[PATCH v3 06/11] maintenance: add --task option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-25T18:33:41Z","receivedAt":"2020-08-25T18:34:18Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nA user may want to only run certain maintenance tasks in a certain\norder. Add the --task=<task> option, which allows a user to specify an\nordered list of tasks to run. These cannot be run multiple times,\nhowever.\n\nHere is where our array of maintenance_task pointers becomes critical.\nWe can sort the array of pointers based on the task order, but we do not\nwant to move the struct data itself in order to preserve the hashmap\nreferences. We use the hashmap to match the --task=<task> arguments into\nthe task struct data.\n\nKeep in mind that the 'enabled' member of the maintenance_task struct is\na placeholder for a future 'maintenance.<task>.enabled' config option.\nThus, we use the 'enabled' member to specify which tasks are run when\nthe user does not specify any --task=<task> arguments. The 'enabled'\nmember should be ignored if --task=<task> appears.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-maintenance.txt |  9 ++++-\n builtin/gc.c                      | 66 +++++++++++++++++++++++++++++--\n t/t7900-maintenance.sh            | 27 +++++++++++++\n 3 files changed, 98 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nindex fc5dbcf0b9..819ca41ab6 100644\n--- a/Documentation/git-maintenance.txt\n+++ b/Documentation/git-maintenance.txt\n@@ -30,7 +30,9 @@ SUBCOMMANDS\n -----------\n \n run::\n-\tRun one or more maintenance tasks.\n+\tRun one or more maintenance tasks. If one or more `--task=<task>`\n+\toptions are specified, then those tasks are run in the provided\n+\torder. Otherwise, only the `gc` task is run.\n \n TASKS\n -----\n@@ -63,6 +65,11 @@ OPTIONS\n --quiet::\n \tDo not report progress or other information over `stderr`.\n \n+--task=<task>::\n+\tIf this option is specified one or more times, then only run the\n+\tspecified tasks in the specified order. See the 'TASKS' section\n+\tfor the list of accepted `<task>` values.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 16e567992e..e94f263c77 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -701,7 +701,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n }\n \n static const char * const builtin_maintenance_run_usage[] = {\n-\tN_(\"git maintenance run [--auto] [--[no-]quiet]\"),\n+\tN_(\"git maintenance run [--auto] [--[no-]quiet] [--task=<task>]\"),\n \tNULL\n };\n \n@@ -759,6 +759,9 @@ struct maintenance_task {\n \tconst char *name;\n \tmaintenance_task_fn *fn;\n \tunsigned enabled:1;\n+\n+\t/* -1 if not selected. */\n+\tint selected_order;\n };\n \n enum maintenance_task_label {\n@@ -781,13 +784,32 @@ static struct maintenance_task tasks[] = {\n \t},\n };\n \n+static int compare_tasks_by_selection(const void *a_, const void *b_)\n+{\n+\tconst struct maintenance_task *a, *b;\n+\n+\ta = (const struct maintenance_task *)&a_;\n+\tb = (const struct maintenance_task *)&b_;\n+\n+\treturn b->selected_order - a->selected_order;\n+}\n+\n static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n {\n-\tint i;\n+\tint i, found_selected = 0;\n \tint result = 0;\n \n+\tfor (i = 0; !found_selected && i < TASK__COUNT; i++)\n+\t\tfound_selected = tasks[i].selected_order >= 0;\n+\n+\tif (found_selected)\n+\t\tQSORT(tasks, TASK__COUNT, compare_tasks_by_selection);\n+\n \tfor (i = 0; i < TASK__COUNT; i++) {\n-\t\tif (!tasks[i].enabled)\n+\t\tif (found_selected && tasks[i].selected_order < 0)\n+\t\t\tcontinue;\n+\n+\t\tif (!found_selected && !tasks[i].enabled)\n \t\t\tcontinue;\n \n \t\tif (tasks[i].fn(opts)) {\n@@ -799,20 +821,58 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n \treturn result;\n }\n \n+static int task_option_parse(const struct option *opt,\n+\t\t\t     const char *arg, int unset)\n+{\n+\tint i, num_selected = 0;\n+\tstruct maintenance_task *task = NULL;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tfor (i = 0; i < TASK__COUNT; i++) {\n+\t\tif (tasks[i].selected_order >= 0)\n+\t\t\tnum_selected++;\n+\t\tif (!strcasecmp(tasks[i].name, arg)) {\n+\t\t\ttask = &tasks[i];\n+\t\t}\n+\t}\n+\n+\tif (!task) {\n+\t\terror(_(\"'%s' is not a valid task\"), arg);\n+\t\treturn 1;\n+\t}\n+\n+\tif (task->selected_order >= 0) {\n+\t\terror(_(\"task '%s' cannot be selected multiple times\"), arg);\n+\t\treturn 1;\n+\t}\n+\n+\ttask->selected_order = num_selected + 1;\n+\n+\treturn 0;\n+}\n+\n static int maintenance_run(int argc, const char **argv, const char *prefix)\n {\n+\tint i;\n \tstruct maintenance_run_opts opts;\n \tstruct option builtin_maintenance_run_options[] = {\n \t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n \t\t\t N_(\"run tasks based on the state of the repository\")),\n \t\tOPT_BOOL(0, \"quiet\", &opts.quiet,\n \t\t\t N_(\"do not report progress or other information over stderr\")),\n+\t\tOPT_CALLBACK_F(0, \"task\", NULL, N_(\"task\"),\n+\t\t\tN_(\"run a specific task\"),\n+\t\t\tPARSE_OPT_NONEG, task_option_parse),\n \t\tOPT_END()\n \t};\n \tmemset(&opts, 0, sizeof(opts));\n \n \topts.quiet = !isatty(2);\n \n+\tfor (i = 0; i < TASK__COUNT; i++)\n+\t\ttasks[i].selected_order = -1;\n+\n \targc = parse_options(argc, argv, prefix,\n \t\t\t     builtin_maintenance_run_options,\n \t\t\t     builtin_maintenance_run_usage,\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex c0c4e6846e..792765aff7 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -25,4 +25,31 @@ test_expect_success 'run [--auto|--quiet]' '\n \ttest_subcommand git gc --no-quiet <run-no-quiet.txt\n '\n \n+test_expect_success 'run --task=<task>' '\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-commit-graph.txt\" \\\n+\t\tgit maintenance run --task=commit-graph 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-gc.txt\" \\\n+\t\tgit maintenance run --task=gc 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-commit-graph.txt\" \\\n+\t\tgit maintenance run --task=commit-graph 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-both.txt\" \\\n+\t\tgit maintenance run --task=commit-graph --task=gc 2>/dev/null &&\n+\ttest_subcommand ! git gc --quiet <run-commit-graph.txt &&\n+\ttest_subcommand git gc --quiet <run-gc.txt &&\n+\ttest_subcommand git gc --quiet <run-both.txt &&\n+\ttest_subcommand git commit-graph write --split --reachable --no-progress <run-commit-graph.txt &&\n+\ttest_subcommand ! git commit-graph write --split --reachable --no-progress <run-gc.txt &&\n+\ttest_subcommand git commit-graph write --split --reachable --no-progress <run-both.txt\n+'\n+\n+test_expect_success 'run --task=bogus' '\n+\ttest_must_fail git maintenance run --task=bogus 2>err &&\n+\ttest_i18ngrep \"is not a valid task\" err\n+'\n+\n+test_expect_success 'run --task duplicate' '\n+\ttest_must_fail git maintenance run --task=gc --task=gc 2>err &&\n+\ttest_i18ngrep \"cannot be selected multiple times\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"404446","messageId":"06984a42bfc892b82f7b39a1aa366158fcf3fd20.1598380427.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v3.git.1598380426.gitgitgadget@gmail.com","subject":"[PATCH v3 05/11] maintenance: add commit-graph task","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-25T18:33:40Z","receivedAt":"2020-08-25T18:34:20Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe first new task in the 'git maintenance' builtin is the\n'commit-graph' task. This updates the commit-graph file\nincrementally with the command\n\n\tgit commit-graph write --reachable --split\n\nBy writing an incremental commit-graph file using the \"--split\"\noption we minimize the disruption from this operation. The default\nbehavior is to merge layers until the new \"top\" layer is less than\nhalf the size of the layer below. This provides quick writes most\nof the time, with the longer writes following a power law\ndistribution.\n\nMost importantly, concurrent Git processes only look at the\ncommit-graph-chain file for a very short amount of time, so they\nwill verly likely not be holding a handle to the file when we try\nto replace it. (This only matters on Windows.)\n\nIf a concurrent process reads the old commit-graph-chain file, but\nour job expires some of the .graph files before they can be read,\nthen those processes will see a warning message (but not fail).\nThis could be avoided by a future update to use the --expire-time\nargument when writing the commit-graph.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-maintenance.txt |  8 ++++++++\n builtin/gc.c                      | 30 ++++++++++++++++++++++++++++++\n commit-graph.c                    |  8 ++++----\n commit-graph.h                    |  1 +\n t/t7900-maintenance.sh            |  2 ++\n 5 files changed, 45 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nindex 04fa0fe329..fc5dbcf0b9 100644\n--- a/Documentation/git-maintenance.txt\n+++ b/Documentation/git-maintenance.txt\n@@ -35,6 +35,14 @@ run::\n TASKS\n -----\n \n+commit-graph::\n+\tThe `commit-graph` job updates the `commit-graph` files incrementally,\n+\tthen verifies that the written data is correct. The incremental\n+\twrite is safe to run alongside concurrent Git processes since it\n+\twill not expire `.graph` files that were in the previous\n+\t`commit-graph-chain` file. They will be deleted by a later run based\n+\ton the expiration delay.\n+\n gc::\n \tClean up unnecessary files and optimize the local repository. \"GC\"\n \tstands for \"garbage collection,\" but this task performs many\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 81643515e5..16e567992e 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -710,6 +710,31 @@ struct maintenance_run_opts {\n \tint quiet;\n };\n \n+static int run_write_commit_graph(struct maintenance_run_opts *opts)\n+{\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\n+\tchild.git_cmd = 1;\n+\tstrvec_pushl(&child.args, \"commit-graph\", \"write\",\n+\t\t     \"--split\", \"--reachable\", NULL);\n+\n+\tif (opts->quiet)\n+\t\tstrvec_push(&child.args, \"--no-progress\");\n+\n+\treturn !!run_command(&child);\n+}\n+\n+static int maintenance_task_commit_graph(struct maintenance_run_opts *opts)\n+{\n+\tclose_object_store(the_repository->objects);\n+\tif (run_write_commit_graph(opts)) {\n+\t\terror(_(\"failed to write commit-graph\"));\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n static int maintenance_task_gc(struct maintenance_run_opts *opts)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n@@ -738,6 +763,7 @@ struct maintenance_task {\n \n enum maintenance_task_label {\n \tTASK_GC,\n+\tTASK_COMMIT_GRAPH,\n \n \t/* Leave as final value */\n \tTASK__COUNT\n@@ -749,6 +775,10 @@ static struct maintenance_task tasks[] = {\n \t\tmaintenance_task_gc,\n \t\t1,\n \t},\n+\t[TASK_COMMIT_GRAPH] = {\n+\t\t\"commit-graph\",\n+\t\tmaintenance_task_commit_graph,\n+\t},\n };\n \n static int maintenance_run_tasks(struct maintenance_run_opts *opts)\ndiff --git a/commit-graph.c b/commit-graph.c\nindex e51c91dd5b..a9b0db7d4a 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -172,7 +172,7 @@ static char *get_split_graph_filename(struct object_directory *odb,\n \t\t       oid_hex);\n }\n \n-static char *get_chain_filename(struct object_directory *odb)\n+char *get_commit_graph_chain_filename(struct object_directory *odb)\n {\n \treturn xstrfmt(\"%s/info/commit-graphs/commit-graph-chain\", odb->path);\n }\n@@ -516,7 +516,7 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r,\n \tstruct stat st;\n \tstruct object_id *oids;\n \tint i = 0, valid = 1, count;\n-\tchar *chain_name = get_chain_filename(odb);\n+\tchar *chain_name = get_commit_graph_chain_filename(odb);\n \tFILE *fp;\n \tint stat_res;\n \n@@ -1668,7 +1668,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)\n \t}\n \n \tif (ctx->split) {\n-\t\tchar *lock_name = get_chain_filename(ctx->odb);\n+\t\tchar *lock_name = get_commit_graph_chain_filename(ctx->odb);\n \n \t\thold_lock_file_for_update_mode(&lk, lock_name,\n \t\t\t\t\t       LOCK_DIE_ON_ERROR, 0444);\n@@ -2038,7 +2038,7 @@ static void expire_commit_graphs(struct write_commit_graph_context *ctx)\n \tif (ctx->split_opts && ctx->split_opts->expire_time)\n \t\texpire_time = ctx->split_opts->expire_time;\n \tif (!ctx->split) {\n-\t\tchar *chain_file_name = get_chain_filename(ctx->odb);\n+\t\tchar *chain_file_name = get_commit_graph_chain_filename(ctx->odb);\n \t\tunlink(chain_file_name);\n \t\tfree(chain_file_name);\n \t\tctx->num_commit_graphs_after = 0;\ndiff --git a/commit-graph.h b/commit-graph.h\nindex 09a97030dc..765221cdcc 100644\n--- a/commit-graph.h\n+++ b/commit-graph.h\n@@ -25,6 +25,7 @@ struct raw_object_store;\n struct string_list;\n \n char *get_commit_graph_filename(struct object_directory *odb);\n+char *get_commit_graph_chain_filename(struct object_directory *odb);\n int open_commit_graph(const char *graph_file, int *fd, struct stat *st);\n \n /*\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex 47d512464c..c0c4e6846e 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -4,6 +4,8 @@ test_description='git maintenance builtin'\n \n . ./test-lib.sh\n \n+GIT_TEST_COMMIT_GRAPH=0\n+\n test_expect_success 'help text' '\n \ttest_expect_code 129 git maintenance -h 2>err &&\n \ttest_i18ngrep \"usage: git maintenance run\" err &&\n-- \ngitgitgadget\n\n"},{"id":"404447","messageId":"3d513acdd8885d90393cbfa847c38e802ffddac9.1598380427.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v3.git.1598380426.gitgitgadget@gmail.com","subject":"[PATCH v3 07/11] maintenance: take a lock on the objects directory","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-25T18:33:42Z","receivedAt":"2020-08-25T18:34:22Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nPerforming maintenance on a Git repository involves writing data to the\n.git directory, which is not safe to do with multiple writers attempting\nthe same operation. Ensure that only one 'git maintenance' process is\nrunning at a time by holding a file-based lock. Simply the presence of\nthe .git/maintenance.lock file will prevent future maintenance. This\nlock is never committed, since it does not represent meaningful data.\nInstead, it is only a placeholder.\n\nIf the lock file already exists, then fail with a warning. If '--auto'\nis specified, then instead no warning is shown and no tasks are attempted.\nThis will become very important later when we implement the 'prefetch'\ntask, as this is our stop-gap from creating a recursive process loop\nbetween 'git fetch' and 'git maintenance run --auto'.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/gc.c | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex e94f263c77..1cebb7282d 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -798,6 +798,25 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n {\n \tint i, found_selected = 0;\n \tint result = 0;\n+\tstruct lock_file lk;\n+\tstruct repository *r = the_repository;\n+\tchar *lock_path = xstrfmt(\"%s/maintenance\", r->objects->odb->path);\n+\n+\tif (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {\n+\t\t/*\n+\t\t * Another maintenance command is running.\n+\t\t *\n+\t\t * If --auto was provided, then it is likely due to a\n+\t\t * recursive process stack. Do not report an error in\n+\t\t * that case.\n+\t\t */\n+\t\tif (!opts->auto_flag && !opts->quiet)\n+\t\t\twarning(_(\"lock file '%s' exists, skipping maintenance\"),\n+\t\t\t\tlock_path);\n+\t\tfree(lock_path);\n+\t\treturn 0;\n+\t}\n+\tfree(lock_path);\n \n \tfor (i = 0; !found_selected && i < TASK__COUNT; i++)\n \t\tfound_selected = tasks[i].selected_order >= 0;\n@@ -818,6 +837,7 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n \t\t}\n \t}\n \n+\trollback_lock_file(&lk);\n \treturn result;\n }\n \n-- \ngitgitgadget\n\n"},{"id":"404448","messageId":"712f5f2d8ec3548c152c20b50b75a58b773ce3f4.1598380427.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v3.git.1598380426.gitgitgadget@gmail.com","subject":"[PATCH v3 08/11] maintenance: create maintenance.<task>.enabled config","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-25T18:33:43Z","receivedAt":"2020-08-25T18:34:24Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nCurrently, a normal run of \"git maintenance run\" will only run the 'gc'\ntask, as it is the only one enabled. This is mostly for backwards-\ncompatible reasons since \"git maintenance run --auto\" commands replaced\nprevious \"git gc --auto\" commands after some Git processes. Users could\nmanually run specific maintenance tasks by calling \"git maintenance run\n--task=<task>\" directly.\n\nAllow users to customize which steps are run automatically using config.\nThe 'maintenance.<task>.enabled' option then can turn on these other\ntasks (or turn off the 'gc' task).\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config.txt             |  2 ++\n Documentation/config/maintenance.txt |  6 ++++++\n Documentation/git-maintenance.txt    | 14 +++++++++-----\n builtin/gc.c                         | 19 +++++++++++++++++++\n t/t7900-maintenance.sh               |  8 ++++++++\n 5 files changed, 44 insertions(+), 5 deletions(-)\n create mode 100644 Documentation/config/maintenance.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 3042d80978..f93b6837e4 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -398,6 +398,8 @@ include::config/mailinfo.txt[]\n \n include::config/mailmap.txt[]\n \n+include::config/maintenance.txt[]\n+\n include::config/man.txt[]\n \n include::config/merge.txt[]\ndiff --git a/Documentation/config/maintenance.txt b/Documentation/config/maintenance.txt\nnew file mode 100644\nindex 0000000000..4402b8b49f\n--- /dev/null\n+++ b/Documentation/config/maintenance.txt\n@@ -0,0 +1,6 @@\n+maintenance.<task>.enabled::\n+\tThis boolean config option controls whether the maintenance task\n+\twith name `<task>` is run when no `--task` option is specified to\n+\t`git maintenance run`. These config values are ignored if a\n+\t`--task` option exists. By default, only `maintenance.gc.enabled`\n+\tis true.\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nindex 819ca41ab6..6abcb8255a 100644\n--- a/Documentation/git-maintenance.txt\n+++ b/Documentation/git-maintenance.txt\n@@ -30,9 +30,11 @@ SUBCOMMANDS\n -----------\n \n run::\n-\tRun one or more maintenance tasks. If one or more `--task=<task>`\n-\toptions are specified, then those tasks are run in the provided\n-\torder. Otherwise, only the `gc` task is run.\n+\tRun one or more maintenance tasks. If one or more `--task` options\n+\tare specified, then those tasks are run in that order. Otherwise,\n+\tthe tasks are determined by which `maintenance.<task>.enabled`\n+\tconfig options are true. By default, only `maintenance.gc.enabled`\n+\tis true.\n \n TASKS\n -----\n@@ -67,8 +69,10 @@ OPTIONS\n \n --task=<task>::\n \tIf this option is specified one or more times, then only run the\n-\tspecified tasks in the specified order. See the 'TASKS' section\n-\tfor the list of accepted `<task>` values.\n+\tspecified tasks in the specified order. If no `--task=<task>`\n+\targuments are specified, then only the tasks with\n+\t`maintenance.<task>.enabled` configured as `true` are considered.\n+\tSee the 'TASKS' section for the list of accepted `<task>` values.\n \n GIT\n ---\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 1cebb7282d..67a8d405a1 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -841,6 +841,24 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n \treturn result;\n }\n \n+static void initialize_task_config(void)\n+{\n+\tint i;\n+\tstruct strbuf config_name = STRBUF_INIT;\n+\tfor (i = 0; i < TASK__COUNT; i++) {\n+\t\tint config_value;\n+\n+\t\tstrbuf_setlen(&config_name, 0);\n+\t\tstrbuf_addf(&config_name, \"maintenance.%s.enabled\",\n+\t\t\t    tasks[i].name);\n+\n+\t\tif (!git_config_get_bool(config_name.buf, &config_value))\n+\t\t\ttasks[i].enabled = config_value;\n+\t}\n+\n+\tstrbuf_release(&config_name);\n+}\n+\n static int task_option_parse(const struct option *opt,\n \t\t\t     const char *arg, int unset)\n {\n@@ -889,6 +907,7 @@ static int maintenance_run(int argc, const char **argv, const char *prefix)\n \tmemset(&opts, 0, sizeof(opts));\n \n \topts.quiet = !isatty(2);\n+\tinitialize_task_config();\n \n \tfor (i = 0; i < TASK__COUNT; i++)\n \t\ttasks[i].selected_order = -1;\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex 792765aff7..290abb7832 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -25,6 +25,14 @@ test_expect_success 'run [--auto|--quiet]' '\n \ttest_subcommand git gc --no-quiet <run-no-quiet.txt\n '\n \n+test_expect_success 'maintenance.<task>.enabled' '\n+\tgit config maintenance.gc.enabled false &&\n+\tgit config maintenance.commit-graph.enabled true &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-config.txt\" git maintenance run 2>err &&\n+\ttest_subcommand ! git gc --quiet <run-config.txt &&\n+\ttest_subcommand git commit-graph write --split --reachable --no-progress <run-config.txt\n+'\n+\n test_expect_success 'run --task=<task>' '\n \tGIT_TRACE2_EVENT=\"$(pwd)/run-commit-graph.txt\" \\\n \t\tgit maintenance run --task=commit-graph 2>/dev/null &&\n-- \ngitgitgadget\n\n"},{"id":"404449","messageId":"4c3115fe3522bee47ba1f8f5e847e99ad7e56d40.1598380427.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v3.git.1598380426.gitgitgadget@gmail.com","subject":"[PATCH v3 10/11] maintenance: add auto condition for commit-graph task","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-25T18:33:45Z","receivedAt":"2020-08-25T18:34:32Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nInstead of writing a new commit-graph in every 'git maintenance run\n--auto' process (when maintenance.commit-graph.enalbed is configured to\nbe true), only write when there are \"enough\" commits not in a\ncommit-graph file.\n\nThis count is controlled by the maintenance.commit-graph.auto config\noption.\n\nTo compute the count, use a depth-first search starting at each ref, and\nleaving markers using the PARENT1 flag. If this count reaches the limit,\nthen terminate early and start the task. Otherwise, this operation will\npeel every ref and parse the commit it points to. If these are all in\nthe commit-graph, then this is typically a very fast operation. Users\nwith many refs might feel a slow-down, and hence could consider updating\ntheir limit to be very small. A negative value will force the step to\nrun every time.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/maintenance.txt | 10 ++++\n builtin/gc.c                         | 82 ++++++++++++++++++++++++++++\n object.h                             |  1 +\n 3 files changed, 93 insertions(+)\n\ndiff --git a/Documentation/config/maintenance.txt b/Documentation/config/maintenance.txt\nindex 4402b8b49f..7cc6700d57 100644\n--- a/Documentation/config/maintenance.txt\n+++ b/Documentation/config/maintenance.txt\n@@ -4,3 +4,13 @@ maintenance.<task>.enabled::\n \t`git maintenance run`. These config values are ignored if a\n \t`--task` option exists. By default, only `maintenance.gc.enabled`\n \tis true.\n+\n+maintenance.commit-graph.auto::\n+\tThis integer config option controls how often the `commit-graph` task\n+\tshould be run as part of `git maintenance run --auto`. If zero, then\n+\tthe `commit-graph` task will not run with the `--auto` option. A\n+\tnegative value will force the task to run every time. Otherwise, a\n+\tpositive value implies the command should run when the number of\n+\treachable commits that are not in the commit-graph file is at least\n+\tthe value of `maintenance.commit-graph.auto`. The default value is\n+\t100.\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 709d13553b..8c4edf19ba 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -28,6 +28,7 @@\n #include \"blob.h\"\n #include \"tree.h\"\n #include \"promisor-remote.h\"\n+#include \"refs.h\"\n \n #define FAILED_RUN \"failed to run %s\"\n \n@@ -710,6 +711,86 @@ struct maintenance_run_opts {\n \tint quiet;\n };\n \n+/* Remember to update object flag allocation in object.h */\n+#define SEEN\t\t(1u<<0)\n+\n+struct cg_auto_data {\n+\tint num_not_in_graph;\n+\tint limit;\n+};\n+\n+static int dfs_on_ref(const char *refname,\n+\t\t      const struct object_id *oid, int flags,\n+\t\t      void *cb_data)\n+{\n+\tstruct cg_auto_data *data = (struct cg_auto_data *)cb_data;\n+\tint result = 0;\n+\tstruct object_id peeled;\n+\tstruct commit_list *stack = NULL;\n+\tstruct commit *commit;\n+\n+\tif (!peel_ref(refname, &peeled))\n+\t\toid = &peeled;\n+\tif (oid_object_info(the_repository, oid, NULL) != OBJ_COMMIT)\n+\t\treturn 0;\n+\n+\tcommit = lookup_commit(the_repository, oid);\n+\tif (!commit)\n+\t\treturn 0;\n+\tif (parse_commit(commit))\n+\t\treturn 0;\n+\n+\tcommit_list_append(commit, &stack);\n+\n+\twhile (!result && stack) {\n+\t\tstruct commit_list *parent;\n+\n+\t\tcommit = pop_commit(&stack);\n+\n+\t\tfor (parent = commit->parents; parent; parent = parent->next) {\n+\t\t\tif (parse_commit(parent->item) ||\n+\t\t\t    commit_graph_position(parent->item) != COMMIT_NOT_FROM_GRAPH ||\n+\t\t\t    parent->item->object.flags & SEEN)\n+\t\t\t\tcontinue;\n+\n+\t\t\tparent->item->object.flags |= SEEN;\n+\t\t\tdata->num_not_in_graph++;\n+\n+\t\t\tif (data->num_not_in_graph >= data->limit) {\n+\t\t\t\tresult = 1;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tcommit_list_append(parent->item, &stack);\n+\t\t}\n+\t}\n+\n+\tfree_commit_list(stack);\n+\treturn result;\n+}\n+\n+static int should_write_commit_graph(void)\n+{\n+\tint result;\n+\tstruct cg_auto_data data;\n+\n+\tdata.num_not_in_graph = 0;\n+\tdata.limit = 100;\n+\tgit_config_get_int(\"maintenance.commit-graph.auto\",\n+\t\t\t   &data.limit);\n+\n+\tif (!data.limit)\n+\t\treturn 0;\n+\tif (data.limit < 0)\n+\t\treturn 1;\n+\n+\tresult = for_each_ref(dfs_on_ref, &data);\n+\n+\tclear_commit_marks_all(SEEN);\n+\n+\treturn result;\n+}\n+\n static int run_write_commit_graph(struct maintenance_run_opts *opts)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n@@ -790,6 +871,7 @@ static struct maintenance_task tasks[] = {\n \t[TASK_COMMIT_GRAPH] = {\n \t\t\"commit-graph\",\n \t\tmaintenance_task_commit_graph,\n+\t\tshould_write_commit_graph,\n \t},\n };\n \ndiff --git a/object.h b/object.h\nindex 96a2105859..20b18805f0 100644\n--- a/object.h\n+++ b/object.h\n@@ -73,6 +73,7 @@ struct object_array {\n  * sha1-name.c:                                              20\n  * list-objects-filter.c:                                      21\n  * builtin/fsck.c:           0--3\n+ * builtin/gc.c:             0\n  * builtin/index-pack.c:                                     2021\n  * builtin/pack-objects.c:                                   20\n  * builtin/reflog.c:                   10--12\n-- \ngitgitgadget\n\n"},{"id":"404450","messageId":"652a8eac57d04a51820c7a5b45031b50c5188e7b.1598380427.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v3.git.1598380426.gitgitgadget@gmail.com","subject":"[PATCH v3 11/11] maintenance: add trace2 regions for task execution","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-25T18:33:46Z","receivedAt":"2020-08-25T18:34:38Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/gc.c | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 8c4edf19ba..c3bcdc1167 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -927,10 +927,12 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n \t\t     !tasks[i].auto_condition()))\n \t\t\tcontinue;\n \n+\t\ttrace2_region_enter(\"maintenance\", tasks[i].name, r);\n \t\tif (tasks[i].fn(opts)) {\n \t\t\terror(_(\"task '%s' failed\"), tasks[i].name);\n \t\t\tresult = 1;\n \t\t}\n+\t\ttrace2_region_leave(\"maintenance\", tasks[i].name, r);\n \t}\n \n \trollback_lock_file(&lk);\n-- \ngitgitgadget\n"},{"id":"404451","messageId":"69d3b48fd4474783579297d3d25f04dd8f1e7673.1598380427.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v3.git.1598380426.gitgitgadget@gmail.com","subject":"[PATCH v3 09/11] maintenance: use pointers to check --auto","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-25T18:33:44Z","receivedAt":"2020-08-25T18:34:41Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe 'git maintenance run' command has an '--auto' option. This is used\nby other Git commands such as 'git commit' or 'git fetch' to check if\nmaintenance should be run after adding data to the repository.\n\nPreviously, this --auto option was only used to add the argument to the\n'git gc' command as part of the 'gc' task. We will be expanding the\nother tasks to perform a check to see if they should do work as part of\nthe --auto flag, when they are enabled by config.\n\nFirst, update the 'gc' task to perform the auto check inside the\nmaintenance process. This prevents running an extra 'git gc --auto'\ncommand when not needed. It also shows a model for other tasks.\n\nSecond, use the 'auto_condition' function pointer as a signal for\nwhether we enable the maintenance task under '--auto'. For instance, we\ndo not want to enable the 'fetch' task in '--auto' mode, so that\nfunction pointer will remain NULL.\n\nNow that we are not automatically calling 'git gc', a test in\nt5514-fetch-multiple.sh must be changed to watch for 'git maintenance'\ninstead.\n\nWe continue to pass the '--auto' option to the 'git gc' command when\nnecessary, because of the gc.autoDetach config option changes behavior.\nLikely, we will want to absorb the daemonizing behavior implied by\ngc.autoDetach as a maintenance.autoDetach config option.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/gc.c              | 16 ++++++++++++++++\n t/t5514-fetch-multiple.sh |  2 +-\n t/t7900-maintenance.sh    |  2 +-\n 3 files changed, 18 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 67a8d405a1..709d13553b 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -755,9 +755,17 @@ static int maintenance_task_gc(struct maintenance_run_opts *opts)\n \n typedef int maintenance_task_fn(struct maintenance_run_opts *opts);\n \n+/*\n+ * An auto condition function returns 1 if the task should run\n+ * and 0 if the task should NOT run. See needs_to_gc() for an\n+ * example.\n+ */\n+typedef int maintenance_auto_fn(void);\n+\n struct maintenance_task {\n \tconst char *name;\n \tmaintenance_task_fn *fn;\n+\tmaintenance_auto_fn *auto_condition;\n \tunsigned enabled:1;\n \n \t/* -1 if not selected. */\n@@ -776,6 +784,7 @@ static struct maintenance_task tasks[] = {\n \t[TASK_GC] = {\n \t\t\"gc\",\n \t\tmaintenance_task_gc,\n+\t\tneed_to_gc,\n \t\t1,\n \t},\n \t[TASK_COMMIT_GRAPH] = {\n@@ -831,6 +840,11 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n \t\tif (!found_selected && !tasks[i].enabled)\n \t\t\tcontinue;\n \n+\t\tif (opts->auto_flag &&\n+\t\t    (!tasks[i].auto_condition ||\n+\t\t     !tasks[i].auto_condition()))\n+\t\t\tcontinue;\n+\n \t\tif (tasks[i].fn(opts)) {\n \t\t\terror(_(\"task '%s' failed\"), tasks[i].name);\n \t\t\tresult = 1;\n@@ -845,6 +859,8 @@ static void initialize_task_config(void)\n {\n \tint i;\n \tstruct strbuf config_name = STRBUF_INIT;\n+\tgc_config();\n+\n \tfor (i = 0; i < TASK__COUNT; i++) {\n \t\tint config_value;\n \ndiff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh\nindex de8e2f1531..bd202ec6f3 100755\n--- a/t/t5514-fetch-multiple.sh\n+++ b/t/t5514-fetch-multiple.sh\n@@ -108,7 +108,7 @@ test_expect_success 'git fetch --multiple (two remotes)' '\n \t GIT_TRACE=1 git fetch --multiple one two 2>trace &&\n \t git branch -r > output &&\n \t test_cmp ../expect output &&\n-\t grep \"built-in: git gc\" trace >gc &&\n+\t grep \"built-in: git maintenance\" trace >gc &&\n \t test_line_count = 1 gc\n \t)\n '\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex 290abb7832..4f6a04ddb1 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -21,7 +21,7 @@ test_expect_success 'run [--auto|--quiet]' '\n \tGIT_TRACE2_EVENT=\"$(pwd)/run-no-quiet.txt\" \\\n \t\tgit maintenance run --no-quiet 2>/dev/null &&\n \ttest_subcommand git gc --quiet <run-no-auto.txt &&\n-\ttest_subcommand git gc --auto --quiet <run-auto.txt &&\n+\ttest_subcommand ! git gc --auto --quiet <run-auto.txt &&\n \ttest_subcommand git gc --no-quiet <run-no-quiet.txt\n '\n \n-- \ngitgitgadget\n\n"},{"id":"404558","messageId":"20200826230205.1444446-1-jonathantanmy@google.com","threadId":"53997","inReplyTo":"3d513acdd8885d90393cbfa847c38e802ffddac9.1598380427.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 07/11] maintenance: take a lock on the objects directory","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-08-26T23:02:05Z","receivedAt":"2020-08-26T23:02:20Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> If the lock file already exists, then fail with a warning. If '--auto'\n> is specified, then instead no warning is shown and no tasks are attempted.\n\nMaybe just delete these 2 lines - you're not failing, and you're\nrefraining from attempting all tasks regardless.\n\nWith or without this change, patches 1-7 look good.\n"},{"id":"404559","messageId":"20200826230252.1445841-1-jonathantanmy@google.com","threadId":"53997","inReplyTo":"4c3115fe3522bee47ba1f8f5e847e99ad7e56d40.1598380427.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 10/11] maintenance: add auto condition for commit-graph task","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-08-26T23:02:52Z","receivedAt":"2020-08-26T23:02:58Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> From: Derrick Stolee <dstolee@microsoft.com>\n> \n> Instead of writing a new commit-graph in every 'git maintenance run\n> --auto' process (when maintenance.commit-graph.enalbed is configured to\n> be true), only write when there are \"enough\" commits not in a\n> commit-graph file.\n> \n> This count is controlled by the maintenance.commit-graph.auto config\n> option.\n> \n> To compute the count, use a depth-first search starting at each ref, and\n> leaving markers using the PARENT1 flag. If this count reaches the limit,\n\nPARENT1 -> SEEN\n\nOther than that, all the 11 patches look good.\n"},{"id":"404561","messageId":"xmqqblixt0zu.fsf@gitster.c.googlers.com","threadId":"53997","inReplyTo":"20200826230252.1445841-1-jonathantanmy@google.com","subject":"Re: [PATCH v3 10/11] maintenance: add auto condition for commit-graph task","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-08-26T23:56:37Z","receivedAt":"2020-08-26T23:56:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n>> From: Derrick Stolee <dstolee@microsoft.com>\n>> \n>> Instead of writing a new commit-graph in every 'git maintenance run\n>> --auto' process (when maintenance.commit-graph.enalbed is configured to\n>> be true), only write when there are \"enough\" commits not in a\n>> commit-graph file.\n>> \n>> This count is controlled by the maintenance.commit-graph.auto config\n>> option.\n>> \n>> To compute the count, use a depth-first search starting at each ref, and\n>> leaving markers using the PARENT1 flag. If this count reaches the limit,\n>\n> PARENT1 -> SEEN\n>\n> Other than that, all the 11 patches look good.\n\nThanks.\n"},{"id":"405024","messageId":"82326c1c38ca6d7ec71bddb50d33c1e5bc3421fb.1599224956.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v4.git.1599224956.gitgitgadget@gmail.com","subject":"[PATCH v4 04/11] maintenance: initialize task array","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-04T13:09:08Z","receivedAt":"2020-09-04T13:09:53Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nIn anticipation of implementing multiple maintenance tasks inside the\n'maintenance' builtin, use a list of structs to describe the work to be\ndone.\n\nThe struct maintenance_task stores the name of the task (as given by a\nfuture command-line argument) along with a function pointer to its\nimplementation and a boolean for whether the step is enabled.\n\nA list these structs are initialized with the full list of implemented\ntasks along with a default order. For now, this list only contains the\n\"gc\" task. This task is also the only task enabled by default.\n\nThe run subcommand will return a nonzero exit code if any task fails.\nHowever, it will attempt all tasks in its loop before returning with the\nfailure. Also each failed task will print an error message.\n\nHelped-by: Taylor Blau <me@ttaylorr.com>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/gc.c | 43 ++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 42 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex da11f1edcd..81643515e5 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -728,6 +728,47 @@ static int maintenance_task_gc(struct maintenance_run_opts *opts)\n \treturn run_command(&child);\n }\n \n+typedef int maintenance_task_fn(struct maintenance_run_opts *opts);\n+\n+struct maintenance_task {\n+\tconst char *name;\n+\tmaintenance_task_fn *fn;\n+\tunsigned enabled:1;\n+};\n+\n+enum maintenance_task_label {\n+\tTASK_GC,\n+\n+\t/* Leave as final value */\n+\tTASK__COUNT\n+};\n+\n+static struct maintenance_task tasks[] = {\n+\t[TASK_GC] = {\n+\t\t\"gc\",\n+\t\tmaintenance_task_gc,\n+\t\t1,\n+\t},\n+};\n+\n+static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n+{\n+\tint i;\n+\tint result = 0;\n+\n+\tfor (i = 0; i < TASK__COUNT; i++) {\n+\t\tif (!tasks[i].enabled)\n+\t\t\tcontinue;\n+\n+\t\tif (tasks[i].fn(opts)) {\n+\t\t\terror(_(\"task '%s' failed\"), tasks[i].name);\n+\t\t\tresult = 1;\n+\t\t}\n+\t}\n+\n+\treturn result;\n+}\n+\n static int maintenance_run(int argc, const char **argv, const char *prefix)\n {\n \tstruct maintenance_run_opts opts;\n@@ -750,7 +791,7 @@ static int maintenance_run(int argc, const char **argv, const char *prefix)\n \tif (argc != 0)\n \t\tusage_with_options(builtin_maintenance_run_usage,\n \t\t\t\t   builtin_maintenance_run_options);\n-\treturn maintenance_task_gc(&opts);\n+\treturn maintenance_run_tasks(&opts);\n }\n \n static const char builtin_maintenance_usage[] = N_(\"git maintenance run [<options>]\");\n-- \ngitgitgadget\n\n"},{"id":"405025","messageId":"06984a42bfc892b82f7b39a1aa366158fcf3fd20.1599224956.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v4.git.1599224956.gitgitgadget@gmail.com","subject":"[PATCH v4 05/11] maintenance: add commit-graph task","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-04T13:09:09Z","receivedAt":"2020-09-04T13:09:57Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe first new task in the 'git maintenance' builtin is the\n'commit-graph' task. This updates the commit-graph file\nincrementally with the command\n\n\tgit commit-graph write --reachable --split\n\nBy writing an incremental commit-graph file using the \"--split\"\noption we minimize the disruption from this operation. The default\nbehavior is to merge layers until the new \"top\" layer is less than\nhalf the size of the layer below. This provides quick writes most\nof the time, with the longer writes following a power law\ndistribution.\n\nMost importantly, concurrent Git processes only look at the\ncommit-graph-chain file for a very short amount of time, so they\nwill verly likely not be holding a handle to the file when we try\nto replace it. (This only matters on Windows.)\n\nIf a concurrent process reads the old commit-graph-chain file, but\nour job expires some of the .graph files before they can be read,\nthen those processes will see a warning message (but not fail).\nThis could be avoided by a future update to use the --expire-time\nargument when writing the commit-graph.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-maintenance.txt |  8 ++++++++\n builtin/gc.c                      | 30 ++++++++++++++++++++++++++++++\n commit-graph.c                    |  8 ++++----\n commit-graph.h                    |  1 +\n t/t7900-maintenance.sh            |  2 ++\n 5 files changed, 45 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nindex 04fa0fe329..fc5dbcf0b9 100644\n--- a/Documentation/git-maintenance.txt\n+++ b/Documentation/git-maintenance.txt\n@@ -35,6 +35,14 @@ run::\n TASKS\n -----\n \n+commit-graph::\n+\tThe `commit-graph` job updates the `commit-graph` files incrementally,\n+\tthen verifies that the written data is correct. The incremental\n+\twrite is safe to run alongside concurrent Git processes since it\n+\twill not expire `.graph` files that were in the previous\n+\t`commit-graph-chain` file. They will be deleted by a later run based\n+\ton the expiration delay.\n+\n gc::\n \tClean up unnecessary files and optimize the local repository. \"GC\"\n \tstands for \"garbage collection,\" but this task performs many\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 81643515e5..16e567992e 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -710,6 +710,31 @@ struct maintenance_run_opts {\n \tint quiet;\n };\n \n+static int run_write_commit_graph(struct maintenance_run_opts *opts)\n+{\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\n+\tchild.git_cmd = 1;\n+\tstrvec_pushl(&child.args, \"commit-graph\", \"write\",\n+\t\t     \"--split\", \"--reachable\", NULL);\n+\n+\tif (opts->quiet)\n+\t\tstrvec_push(&child.args, \"--no-progress\");\n+\n+\treturn !!run_command(&child);\n+}\n+\n+static int maintenance_task_commit_graph(struct maintenance_run_opts *opts)\n+{\n+\tclose_object_store(the_repository->objects);\n+\tif (run_write_commit_graph(opts)) {\n+\t\terror(_(\"failed to write commit-graph\"));\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n static int maintenance_task_gc(struct maintenance_run_opts *opts)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n@@ -738,6 +763,7 @@ struct maintenance_task {\n \n enum maintenance_task_label {\n \tTASK_GC,\n+\tTASK_COMMIT_GRAPH,\n \n \t/* Leave as final value */\n \tTASK__COUNT\n@@ -749,6 +775,10 @@ static struct maintenance_task tasks[] = {\n \t\tmaintenance_task_gc,\n \t\t1,\n \t},\n+\t[TASK_COMMIT_GRAPH] = {\n+\t\t\"commit-graph\",\n+\t\tmaintenance_task_commit_graph,\n+\t},\n };\n \n static int maintenance_run_tasks(struct maintenance_run_opts *opts)\ndiff --git a/commit-graph.c b/commit-graph.c\nindex e51c91dd5b..a9b0db7d4a 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -172,7 +172,7 @@ static char *get_split_graph_filename(struct object_directory *odb,\n \t\t       oid_hex);\n }\n \n-static char *get_chain_filename(struct object_directory *odb)\n+char *get_commit_graph_chain_filename(struct object_directory *odb)\n {\n \treturn xstrfmt(\"%s/info/commit-graphs/commit-graph-chain\", odb->path);\n }\n@@ -516,7 +516,7 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r,\n \tstruct stat st;\n \tstruct object_id *oids;\n \tint i = 0, valid = 1, count;\n-\tchar *chain_name = get_chain_filename(odb);\n+\tchar *chain_name = get_commit_graph_chain_filename(odb);\n \tFILE *fp;\n \tint stat_res;\n \n@@ -1668,7 +1668,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)\n \t}\n \n \tif (ctx->split) {\n-\t\tchar *lock_name = get_chain_filename(ctx->odb);\n+\t\tchar *lock_name = get_commit_graph_chain_filename(ctx->odb);\n \n \t\thold_lock_file_for_update_mode(&lk, lock_name,\n \t\t\t\t\t       LOCK_DIE_ON_ERROR, 0444);\n@@ -2038,7 +2038,7 @@ static void expire_commit_graphs(struct write_commit_graph_context *ctx)\n \tif (ctx->split_opts && ctx->split_opts->expire_time)\n \t\texpire_time = ctx->split_opts->expire_time;\n \tif (!ctx->split) {\n-\t\tchar *chain_file_name = get_chain_filename(ctx->odb);\n+\t\tchar *chain_file_name = get_commit_graph_chain_filename(ctx->odb);\n \t\tunlink(chain_file_name);\n \t\tfree(chain_file_name);\n \t\tctx->num_commit_graphs_after = 0;\ndiff --git a/commit-graph.h b/commit-graph.h\nindex 09a97030dc..765221cdcc 100644\n--- a/commit-graph.h\n+++ b/commit-graph.h\n@@ -25,6 +25,7 @@ struct raw_object_store;\n struct string_list;\n \n char *get_commit_graph_filename(struct object_directory *odb);\n+char *get_commit_graph_chain_filename(struct object_directory *odb);\n int open_commit_graph(const char *graph_file, int *fd, struct stat *st);\n \n /*\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex 47d512464c..c0c4e6846e 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -4,6 +4,8 @@ test_description='git maintenance builtin'\n \n . ./test-lib.sh\n \n+GIT_TEST_COMMIT_GRAPH=0\n+\n test_expect_success 'help text' '\n \ttest_expect_code 129 git maintenance -h 2>err &&\n \ttest_i18ngrep \"usage: git maintenance run\" err &&\n-- \ngitgitgadget\n\n"},{"id":"405026","messageId":"e28b332df428d7f929715eea37e923ce6505313c.1599224956.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v4.git.1599224956.gitgitgadget@gmail.com","subject":"[PATCH v4 03/11] maintenance: replace run_auto_gc()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-04T13:09:07Z","receivedAt":"2020-09-04T13:10:00Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe run_auto_gc() method is used in several places to trigger a check\nfor repo maintenance after some Git commands, such as 'git commit' or\n'git fetch'.\n\nTo allow for extra customization of this maintenance activity, replace\nthe 'git gc --auto [--quiet]' call with one to 'git maintenance run\n--auto [--quiet]'. As we extend the maintenance builtin with other\nsteps, users will be able to select different maintenance activities.\n\nRename run_auto_gc() to run_auto_maintenance() to be clearer what is\nhappening on this call, and to expose all callers in the current diff.\nRewrite the method to use a struct child_process to simplify the calls\nslightly.\n\nSince 'git fetch' already allows disabling the 'git gc --auto'\nsubprocess, add an equivalent option with a different name to be more\ndescriptive of the new behavior: '--[no-]maintenance'. Update the\ndocumentation to include these options at the same time.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/fetch-options.txt |  6 ++++--\n Documentation/git-clone.txt     |  6 +++---\n builtin/am.c                    |  2 +-\n builtin/commit.c                |  2 +-\n builtin/fetch.c                 |  6 ++++--\n builtin/merge.c                 |  2 +-\n builtin/rebase.c                |  4 ++--\n run-command.c                   | 16 +++++++---------\n run-command.h                   |  2 +-\n t/t5510-fetch.sh                |  2 +-\n 10 files changed, 25 insertions(+), 23 deletions(-)\n\ndiff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt\nindex 07d06ff445..b65a758661 100644\n--- a/Documentation/fetch-options.txt\n+++ b/Documentation/fetch-options.txt\n@@ -95,9 +95,11 @@ ifndef::git-pull[]\n \tAllow several <repository> and <group> arguments to be\n \tspecified. No <refspec>s may be specified.\n \n+--[no-]auto-maintenance::\n --[no-]auto-gc::\n-\tRun `git gc --auto` at the end to perform garbage collection\n-\tif needed. This is enabled by default.\n+\tRun `git maintenance run --auto` at the end to perform automatic\n+\trepository maintenance if needed. (`--[no-]auto-gc` is a synonym.)\n+\tThis is enabled by default.\n \n --[no-]write-commit-graph::\n \tWrite a commit-graph after fetching. This overrides the config\ndiff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt\nindex c898310099..097e6a86c5 100644\n--- a/Documentation/git-clone.txt\n+++ b/Documentation/git-clone.txt\n@@ -78,9 +78,9 @@ repository using this option and then delete branches (or use any\n other Git command that makes any existing commit unreferenced) in the\n source repository, some objects may become unreferenced (or dangling).\n These objects may be removed by normal Git operations (such as `git commit`)\n-which automatically call `git gc --auto`. (See linkgit:git-gc[1].)\n-If these objects are removed and were referenced by the cloned repository,\n-then the cloned repository will become corrupt.\n+which automatically call `git maintenance run --auto`. (See\n+linkgit:git-maintenance[1].) If these objects are removed and were referenced\n+by the cloned repository, then the cloned repository will become corrupt.\n +\n Note that running `git repack` without the `--local` option in a repository\n cloned with `--shared` will copy objects from the source repository into a pack\ndiff --git a/builtin/am.c b/builtin/am.c\nindex dd4e6c2d9b..68e9d17379 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1795,7 +1795,7 @@ static void am_run(struct am_state *state, int resume)\n \tif (!state->rebasing) {\n \t\tam_destroy(state);\n \t\tclose_object_store(the_repository->objects);\n-\t\trun_auto_gc(state->quiet);\n+\t\trun_auto_maintenance(state->quiet);\n \t}\n }\n \ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 69ac78d5e5..f9b0a0c05d 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1702,7 +1702,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \tgit_test_write_commit_graph_or_die();\n \n \trepo_rerere(the_repository, 0);\n-\trun_auto_gc(quiet);\n+\trun_auto_maintenance(quiet);\n \trun_commit_hook(use_editor, get_index_file(), \"post-commit\", NULL);\n \tif (amend && !no_post_rewrite) {\n \t\tcommit_post_rewrite(the_repository, current_head, &oid);\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 2eb8d6a5a5..cb38e6f5ec 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -199,8 +199,10 @@ static struct option builtin_fetch_options[] = {\n \tOPT_STRING_LIST(0, \"negotiation-tip\", &negotiation_tip, N_(\"revision\"),\n \t\t\tN_(\"report that we have only objects reachable from this object\")),\n \tOPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),\n+\tOPT_BOOL(0, \"auto-maintenance\", &enable_auto_gc,\n+\t\t N_(\"run 'maintenance --auto' after fetching\")),\n \tOPT_BOOL(0, \"auto-gc\", &enable_auto_gc,\n-\t\t N_(\"run 'gc --auto' after fetching\")),\n+\t\t N_(\"run 'maintenance --auto' after fetching\")),\n \tOPT_BOOL(0, \"show-forced-updates\", &fetch_show_forced_updates,\n \t\t N_(\"check for forced-updates on all updated branches\")),\n \tOPT_BOOL(0, \"write-commit-graph\", &fetch_write_commit_graph,\n@@ -1891,7 +1893,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)\n \tclose_object_store(the_repository->objects);\n \n \tif (enable_auto_gc)\n-\t\trun_auto_gc(verbosity < 0);\n+\t\trun_auto_maintenance(verbosity < 0);\n \n \treturn result;\n }\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 74829a838e..8f31bfab56 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -456,7 +456,7 @@ static void finish(struct commit *head_commit,\n \t\t\t * user should see them.\n \t\t\t */\n \t\t\tclose_object_store(the_repository->objects);\n-\t\t\trun_auto_gc(verbosity < 0);\n+\t\t\trun_auto_maintenance(verbosity < 0);\n \t\t}\n \t}\n \tif (new_head && show_diffstat) {\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex dadb52fa92..9ffba9009d 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -728,10 +728,10 @@ static int finish_rebase(struct rebase_options *opts)\n \tapply_autostash(state_dir_path(\"autostash\", opts));\n \tclose_object_store(the_repository->objects);\n \t/*\n-\t * We ignore errors in 'gc --auto', since the\n+\t * We ignore errors in 'git maintenance run --auto', since the\n \t * user should see them.\n \t */\n-\trun_auto_gc(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE)));\n+\trun_auto_maintenance(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE)));\n \tif (opts->type == REBASE_MERGE) {\n \t\tstruct replay_opts replay = REPLAY_OPTS_INIT;\n \ndiff --git a/run-command.c b/run-command.c\nindex cc9c3296ba..2ee59acdc8 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -1866,15 +1866,13 @@ int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,\n \treturn result;\n }\n \n-int run_auto_gc(int quiet)\n+int run_auto_maintenance(int quiet)\n {\n-\tstruct strvec argv_gc_auto = STRVEC_INIT;\n-\tint status;\n+\tstruct child_process maint = CHILD_PROCESS_INIT;\n \n-\tstrvec_pushl(&argv_gc_auto, \"gc\", \"--auto\", NULL);\n-\tif (quiet)\n-\t\tstrvec_push(&argv_gc_auto, \"--quiet\");\n-\tstatus = run_command_v_opt(argv_gc_auto.v, RUN_GIT_CMD);\n-\tstrvec_clear(&argv_gc_auto);\n-\treturn status;\n+\tmaint.git_cmd = 1;\n+\tstrvec_pushl(&maint.args, \"maintenance\", \"run\", \"--auto\", NULL);\n+\tstrvec_push(&maint.args, quiet ? \"--quiet\" : \"--no-quiet\");\n+\n+\treturn run_command(&maint);\n }\ndiff --git a/run-command.h b/run-command.h\nindex 8b9bfaef16..6472b38bde 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -221,7 +221,7 @@ int run_hook_ve(const char *const *env, const char *name, va_list args);\n /*\n  * Trigger an auto-gc\n  */\n-int run_auto_gc(int quiet);\n+int run_auto_maintenance(int quiet);\n \n #define RUN_COMMAND_NO_STDIN 1\n #define RUN_GIT_CMD\t     2\t/*If this is to be git sub-command */\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 2a1abe91f0..a9682c5669 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -934,7 +934,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '\n \t\tgit config fetch.unpackLimit 1 &&\n \t\tgit config gc.autoPackLimit 1 &&\n \t\tgit config gc.autoDetach false &&\n-\t\tGIT_ASK_YESNO=\"$D/askyesno\" git fetch >fetch.out 2>&1 &&\n+\t\tGIT_ASK_YESNO=\"$D/askyesno\" git fetch --verbose >fetch.out 2>&1 &&\n \t\ttest_i18ngrep \"Auto packing the repository\" fetch.out &&\n \t\t! grep \"Should I try again\" fetch.out\n \t)\n-- \ngitgitgadget\n\n"},{"id":"405027","messageId":"c57998a1c8844fc39cc95877a85790566e8f3331.1599224956.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v4.git.1599224956.gitgitgadget@gmail.com","subject":"[PATCH v4 07/11] maintenance: take a lock on the objects directory","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-04T13:09:11Z","receivedAt":"2020-09-04T13:10:12Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nPerforming maintenance on a Git repository involves writing data to the\n.git directory, which is not safe to do with multiple writers attempting\nthe same operation. Ensure that only one 'git maintenance' process is\nrunning at a time by holding a file-based lock. Simply the presence of\nthe .git/maintenance.lock file will prevent future maintenance. This\nlock is never committed, since it does not represent meaningful data.\nInstead, it is only a placeholder.\n\nIf the lock file already exists, then no maintenance tasks are\nattempted. This will become very important later when we implement the\n'prefetch' task, as this is our stop-gap from creating a recursive process\nloop between 'git fetch' and 'git maintenance run --auto'.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/gc.c | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex e94f263c77..1cebb7282d 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -798,6 +798,25 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n {\n \tint i, found_selected = 0;\n \tint result = 0;\n+\tstruct lock_file lk;\n+\tstruct repository *r = the_repository;\n+\tchar *lock_path = xstrfmt(\"%s/maintenance\", r->objects->odb->path);\n+\n+\tif (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {\n+\t\t/*\n+\t\t * Another maintenance command is running.\n+\t\t *\n+\t\t * If --auto was provided, then it is likely due to a\n+\t\t * recursive process stack. Do not report an error in\n+\t\t * that case.\n+\t\t */\n+\t\tif (!opts->auto_flag && !opts->quiet)\n+\t\t\twarning(_(\"lock file '%s' exists, skipping maintenance\"),\n+\t\t\t\tlock_path);\n+\t\tfree(lock_path);\n+\t\treturn 0;\n+\t}\n+\tfree(lock_path);\n \n \tfor (i = 0; !found_selected && i < TASK__COUNT; i++)\n \t\tfound_selected = tasks[i].selected_order >= 0;\n@@ -818,6 +837,7 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n \t\t}\n \t}\n \n+\trollback_lock_file(&lk);\n \treturn result;\n }\n \n-- \ngitgitgadget\n\n"},{"id":"405028","messageId":"pull.695.v4.git.1599224956.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v3.git.1598380426.gitgitgadget@gmail.com","subject":"[PATCH v4 00/11] Maintenance I: Command, gc and commit-graph tasks","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-04T13:09:04Z","receivedAt":"2020-09-04T13:10:29Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"This series is based on jc/no-update-fetch-head.\n\nThis patch series contains 11patches that were going to be part of v4 of\nds/maintenance [1], but the discussion has gotten really long. To help focus\nthe conversation, I'm splitting out the portions that create and test the\n'maintenance' builtin from the additional tasks (prefetch, loose-objects,\nincremental-repack) that can be brought in later.\n\n[1] \nhttps://lore.kernel.org/git/pull.671.git.1594131695.gitgitgadget@gmail.com/\n\nAs mentioned before, git gc already plays the role of maintaining Git\nrepositories. It has accumulated several smaller pieces in its long history,\nincluding:\n\n 1. Repacking all reachable objects into one pack-file (and deleting\n    unreachable objects).\n 2. Packing refs.\n 3. Expiring reflogs.\n 4. Clearing rerere logs.\n 5. Updating the commit-graph file.\n 6. Pruning worktrees.\n\nWhile expiring reflogs, clearing rererelogs, and deleting unreachable\nobjects are suitable under the guise of \"garbage collection\", packing refs\nand updating the commit-graph file are not as obviously fitting. Further,\nthese operations are \"all or nothing\" in that they rewrite almost all\nrepository data, which does not perform well at extremely large scales.\nThese operations can also be disruptive to foreground Git commands when git\ngc --auto triggers during routine use.\n\nThis series does not intend to change what git gc does, but instead create\nnew choices for automatic maintenance activities, of which git gc remains\nthe only one enabled by default.\n\nThe new maintenance tasks are:\n\n * 'commit-graph' : write and verify a single layer of an incremental\n   commit-graph.\n * 'loose-objects' : prune packed loose objects, then create a new pack from\n   a batch of loose objects.\n * 'pack-files' : expire redundant packs from the multi-pack-index, then\n   repack using the multi-pack-index's incremental repack strategy.\n * 'prefetch' : fetch from each remote, storing the refs in 'refs/prefetch/\n   /'.\n\nThe only included tasks are the 'gc' and 'commit-graph' tasks. The rest will\nfollow in a follow-up series. Including the 'commit-graph' task here allows\nus to build and test features like config settings and the --task= \ncommand-line argument.\n\nThese tasks are all disabled by default, but can be enabled with config\noptions or run explicitly using \"git maintenance run --task=\". There are\nadditional config options to allow customizing the conditions for which the\ntasks run during the '--auto' option.\n\n Because 'gc' is implemented as a maintenance task, the most dramatic change\nof this series is to convert the 'git gc --auto' calls into 'git maintenance\nrun --auto' calls at the end of some Git commands. By default, the only\nchange is that 'git gc --auto' will be run below an additional 'git\nmaintenance' process.\n\nThe 'git maintenance' builtin has a 'run' subcommand so it can be extended\nlater with subcommands that manage background maintenance, such as 'start'\nor 'stop'. These are not the subject of this series, as it is important to\nfocus on the maintenance activities themselves.\n\nUpdates since v3\n================\n\n * Two commit-message typos are fixed.\n\nThanks for all of the review!\n\nUpdates since v2\n================\n\n * Based on jc/no-update-fetch-head instead of jk/strvec.\n   \n   \n * I realized that the other maintenance subcommands should not accept the\n   options for the 'run' subcommand, so I reorganized the option parsing\n   into that subcommand. This makes the range-diff noisier than it would\n   have been otherwise.\n   \n   \n * While updating the parsing, I also updated the usage strings.\n   \n   \n * The \"verify, then delete and rewrite on failure\" logic is removed. I'll\n   consider bringing this back with a way to test the behavior in a later\n   patch series.\n   \n   \n * Other feedback from Jonathan Tan is applied.\n   \n   \n\nUpdates since v1 (of this series)\n=================================\n\n * Documentation fixes.\n   \n   \n * The builtin code had some slight tweaks in PATCH 1.\n   \n   \n\nUPDATES since v3 of [1]\n=======================\n\n * The biggest change here is the use of \"test_subcommand\", based on\n   Jonathan Nieder's approach. This requires having the exact command-line\n   figured out, which now requires spelling out all --no- [quiet%7Cprogress] \n   options. I also added a bunch of \"2>/dev/null\" checks because of the\n   isatty(2) calls. Without that, the behavior will change depending on\n   whether the test is run with -x/-v or without.\n   \n   \n * The option parsing has changed to use a local struct and pass that struct\n   to the helper methods. This is instead of having a global singleton.\n   \n   \n\nThanks, -Stolee\n\nCc: sandals@crustytoothpaste.net [sandals@crustytoothpaste.net], \nsteadmon@google.com [steadmon@google.com], jrnieder@gmail.com\n[jrnieder@gmail.com], peff@peff.net [peff@peff.net], congdanhqx@gmail.com\n[congdanhqx@gmail.com], phillip.wood123@gmail.com\n[phillip.wood123@gmail.com], emilyshaffer@google.com\n[emilyshaffer@google.com], sluongng@gmail.com [sluongng@gmail.com], \njonathantanmy@google.com [jonathantanmy@google.com]\n\nDerrick Stolee (11):\n  maintenance: create basic maintenance runner\n  maintenance: add --quiet option\n  maintenance: replace run_auto_gc()\n  maintenance: initialize task array\n  maintenance: add commit-graph task\n  maintenance: add --task option\n  maintenance: take a lock on the objects directory\n  maintenance: create maintenance.<task>.enabled config\n  maintenance: use pointers to check --auto\n  maintenance: add auto condition for commit-graph task\n  maintenance: add trace2 regions for task execution\n\n .gitignore                           |   1 +\n Documentation/config.txt             |   2 +\n Documentation/config/maintenance.txt |  16 ++\n Documentation/fetch-options.txt      |   6 +-\n Documentation/git-clone.txt          |   6 +-\n Documentation/git-maintenance.txt    |  79 +++++++\n builtin.h                            |   1 +\n builtin/am.c                         |   2 +-\n builtin/commit.c                     |   2 +-\n builtin/fetch.c                      |   6 +-\n builtin/gc.c                         | 336 +++++++++++++++++++++++++++\n builtin/merge.c                      |   2 +-\n builtin/rebase.c                     |   4 +-\n command-list.txt                     |   1 +\n commit-graph.c                       |   8 +-\n commit-graph.h                       |   1 +\n git.c                                |   1 +\n object.h                             |   1 +\n run-command.c                        |  16 +-\n run-command.h                        |   2 +-\n t/t5510-fetch.sh                     |   2 +-\n t/t5514-fetch-multiple.sh            |   2 +-\n t/t7900-maintenance.sh               |  63 +++++\n t/test-lib-functions.sh              |  33 +++\n 24 files changed, 565 insertions(+), 28 deletions(-)\n create mode 100644 Documentation/config/maintenance.txt\n create mode 100644 Documentation/git-maintenance.txt\n create mode 100755 t/t7900-maintenance.sh\n\n\nbase-commit: 887952b8c680626f4721cb5fa57704478801aca4\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-695%2Fderrickstolee%2Fmaintenance%2Fbuiltin-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-695/derrickstolee/maintenance/builtin-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/695\n\nRange-diff vs v3:\n\n  1:  aa961af387 =  1:  aa961af387 maintenance: create basic maintenance runner\n  2:  5386d8a628 =  2:  5386d8a628 maintenance: add --quiet option\n  3:  e28b332df4 =  3:  e28b332df4 maintenance: replace run_auto_gc()\n  4:  82326c1c38 =  4:  82326c1c38 maintenance: initialize task array\n  5:  06984a42bf =  5:  06984a42bf maintenance: add commit-graph task\n  6:  69298aee24 =  6:  69298aee24 maintenance: add --task option\n  7:  3d513acdd8 !  7:  c57998a1c8 maintenance: take a lock on the objects directory\n     @@ Commit message\n          lock is never committed, since it does not represent meaningful data.\n          Instead, it is only a placeholder.\n      \n     -    If the lock file already exists, then fail with a warning. If '--auto'\n     -    is specified, then instead no warning is shown and no tasks are attempted.\n     -    This will become very important later when we implement the 'prefetch'\n     -    task, as this is our stop-gap from creating a recursive process loop\n     -    between 'git fetch' and 'git maintenance run --auto'.\n     +    If the lock file already exists, then no maintenance tasks are\n     +    attempted. This will become very important later when we implement the\n     +    'prefetch' task, as this is our stop-gap from creating a recursive process\n     +    loop between 'git fetch' and 'git maintenance run --auto'.\n      \n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n      \n  8:  712f5f2d8e =  8:  dc2a092366 maintenance: create maintenance.<task>.enabled config\n  9:  69d3b48fd4 =  9:  f5f1e85b03 maintenance: use pointers to check --auto\n 10:  4c3115fe35 ! 10:  8f84b03c46 maintenance: add auto condition for commit-graph task\n     @@ Commit message\n          option.\n      \n          To compute the count, use a depth-first search starting at each ref, and\n     -    leaving markers using the PARENT1 flag. If this count reaches the limit,\n     +    leaving markers using the SEEN flag. If this count reaches the limit,\n          then terminate early and start the task. Otherwise, this operation will\n          peel every ref and parse the commit it points to. If these are all in\n          the commit-graph, then this is typically a very fast operation. Users\n 11:  652a8eac57 = 11:  2d6414d89b maintenance: add trace2 regions for task execution\n\n-- \ngitgitgadget\n"},{"id":"405029","messageId":"2d6414d89ba447d6b2ad652f56038eb24004f408.1599224956.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v4.git.1599224956.gitgitgadget@gmail.com","subject":"[PATCH v4 11/11] maintenance: add trace2 regions for task execution","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-04T13:09:15Z","receivedAt":"2020-09-04T13:10:30Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/gc.c | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 8c4edf19ba..c3bcdc1167 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -927,10 +927,12 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n \t\t     !tasks[i].auto_condition()))\n \t\t\tcontinue;\n \n+\t\ttrace2_region_enter(\"maintenance\", tasks[i].name, r);\n \t\tif (tasks[i].fn(opts)) {\n \t\t\terror(_(\"task '%s' failed\"), tasks[i].name);\n \t\t\tresult = 1;\n \t\t}\n+\t\ttrace2_region_leave(\"maintenance\", tasks[i].name, r);\n \t}\n \n \trollback_lock_file(&lk);\n-- \ngitgitgadget\n"},{"id":"405030","messageId":"f5f1e85b030a0ed5774bc115f90fbd8209bdf17d.1599224956.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v4.git.1599224956.gitgitgadget@gmail.com","subject":"[PATCH v4 09/11] maintenance: use pointers to check --auto","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-04T13:09:13Z","receivedAt":"2020-09-04T13:10:34Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe 'git maintenance run' command has an '--auto' option. This is used\nby other Git commands such as 'git commit' or 'git fetch' to check if\nmaintenance should be run after adding data to the repository.\n\nPreviously, this --auto option was only used to add the argument to the\n'git gc' command as part of the 'gc' task. We will be expanding the\nother tasks to perform a check to see if they should do work as part of\nthe --auto flag, when they are enabled by config.\n\nFirst, update the 'gc' task to perform the auto check inside the\nmaintenance process. This prevents running an extra 'git gc --auto'\ncommand when not needed. It also shows a model for other tasks.\n\nSecond, use the 'auto_condition' function pointer as a signal for\nwhether we enable the maintenance task under '--auto'. For instance, we\ndo not want to enable the 'fetch' task in '--auto' mode, so that\nfunction pointer will remain NULL.\n\nNow that we are not automatically calling 'git gc', a test in\nt5514-fetch-multiple.sh must be changed to watch for 'git maintenance'\ninstead.\n\nWe continue to pass the '--auto' option to the 'git gc' command when\nnecessary, because of the gc.autoDetach config option changes behavior.\nLikely, we will want to absorb the daemonizing behavior implied by\ngc.autoDetach as a maintenance.autoDetach config option.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/gc.c              | 16 ++++++++++++++++\n t/t5514-fetch-multiple.sh |  2 +-\n t/t7900-maintenance.sh    |  2 +-\n 3 files changed, 18 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 67a8d405a1..709d13553b 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -755,9 +755,17 @@ static int maintenance_task_gc(struct maintenance_run_opts *opts)\n \n typedef int maintenance_task_fn(struct maintenance_run_opts *opts);\n \n+/*\n+ * An auto condition function returns 1 if the task should run\n+ * and 0 if the task should NOT run. See needs_to_gc() for an\n+ * example.\n+ */\n+typedef int maintenance_auto_fn(void);\n+\n struct maintenance_task {\n \tconst char *name;\n \tmaintenance_task_fn *fn;\n+\tmaintenance_auto_fn *auto_condition;\n \tunsigned enabled:1;\n \n \t/* -1 if not selected. */\n@@ -776,6 +784,7 @@ static struct maintenance_task tasks[] = {\n \t[TASK_GC] = {\n \t\t\"gc\",\n \t\tmaintenance_task_gc,\n+\t\tneed_to_gc,\n \t\t1,\n \t},\n \t[TASK_COMMIT_GRAPH] = {\n@@ -831,6 +840,11 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n \t\tif (!found_selected && !tasks[i].enabled)\n \t\t\tcontinue;\n \n+\t\tif (opts->auto_flag &&\n+\t\t    (!tasks[i].auto_condition ||\n+\t\t     !tasks[i].auto_condition()))\n+\t\t\tcontinue;\n+\n \t\tif (tasks[i].fn(opts)) {\n \t\t\terror(_(\"task '%s' failed\"), tasks[i].name);\n \t\t\tresult = 1;\n@@ -845,6 +859,8 @@ static void initialize_task_config(void)\n {\n \tint i;\n \tstruct strbuf config_name = STRBUF_INIT;\n+\tgc_config();\n+\n \tfor (i = 0; i < TASK__COUNT; i++) {\n \t\tint config_value;\n \ndiff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh\nindex de8e2f1531..bd202ec6f3 100755\n--- a/t/t5514-fetch-multiple.sh\n+++ b/t/t5514-fetch-multiple.sh\n@@ -108,7 +108,7 @@ test_expect_success 'git fetch --multiple (two remotes)' '\n \t GIT_TRACE=1 git fetch --multiple one two 2>trace &&\n \t git branch -r > output &&\n \t test_cmp ../expect output &&\n-\t grep \"built-in: git gc\" trace >gc &&\n+\t grep \"built-in: git maintenance\" trace >gc &&\n \t test_line_count = 1 gc\n \t)\n '\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex 290abb7832..4f6a04ddb1 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -21,7 +21,7 @@ test_expect_success 'run [--auto|--quiet]' '\n \tGIT_TRACE2_EVENT=\"$(pwd)/run-no-quiet.txt\" \\\n \t\tgit maintenance run --no-quiet 2>/dev/null &&\n \ttest_subcommand git gc --quiet <run-no-auto.txt &&\n-\ttest_subcommand git gc --auto --quiet <run-auto.txt &&\n+\ttest_subcommand ! git gc --auto --quiet <run-auto.txt &&\n \ttest_subcommand git gc --no-quiet <run-no-quiet.txt\n '\n \n-- \ngitgitgadget\n\n"},{"id":"405031","messageId":"8f84b03c4651681af1eb3512bd7c062c3a4c6d95.1599224956.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v4.git.1599224956.gitgitgadget@gmail.com","subject":"[PATCH v4 10/11] maintenance: add auto condition for commit-graph task","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-04T13:09:14Z","receivedAt":"2020-09-04T13:11:08Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nInstead of writing a new commit-graph in every 'git maintenance run\n--auto' process (when maintenance.commit-graph.enalbed is configured to\nbe true), only write when there are \"enough\" commits not in a\ncommit-graph file.\n\nThis count is controlled by the maintenance.commit-graph.auto config\noption.\n\nTo compute the count, use a depth-first search starting at each ref, and\nleaving markers using the SEEN flag. If this count reaches the limit,\nthen terminate early and start the task. Otherwise, this operation will\npeel every ref and parse the commit it points to. If these are all in\nthe commit-graph, then this is typically a very fast operation. Users\nwith many refs might feel a slow-down, and hence could consider updating\ntheir limit to be very small. A negative value will force the step to\nrun every time.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config/maintenance.txt | 10 ++++\n builtin/gc.c                         | 82 ++++++++++++++++++++++++++++\n object.h                             |  1 +\n 3 files changed, 93 insertions(+)\n\ndiff --git a/Documentation/config/maintenance.txt b/Documentation/config/maintenance.txt\nindex 4402b8b49f..7cc6700d57 100644\n--- a/Documentation/config/maintenance.txt\n+++ b/Documentation/config/maintenance.txt\n@@ -4,3 +4,13 @@ maintenance.<task>.enabled::\n \t`git maintenance run`. These config values are ignored if a\n \t`--task` option exists. By default, only `maintenance.gc.enabled`\n \tis true.\n+\n+maintenance.commit-graph.auto::\n+\tThis integer config option controls how often the `commit-graph` task\n+\tshould be run as part of `git maintenance run --auto`. If zero, then\n+\tthe `commit-graph` task will not run with the `--auto` option. A\n+\tnegative value will force the task to run every time. Otherwise, a\n+\tpositive value implies the command should run when the number of\n+\treachable commits that are not in the commit-graph file is at least\n+\tthe value of `maintenance.commit-graph.auto`. The default value is\n+\t100.\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 709d13553b..8c4edf19ba 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -28,6 +28,7 @@\n #include \"blob.h\"\n #include \"tree.h\"\n #include \"promisor-remote.h\"\n+#include \"refs.h\"\n \n #define FAILED_RUN \"failed to run %s\"\n \n@@ -710,6 +711,86 @@ struct maintenance_run_opts {\n \tint quiet;\n };\n \n+/* Remember to update object flag allocation in object.h */\n+#define SEEN\t\t(1u<<0)\n+\n+struct cg_auto_data {\n+\tint num_not_in_graph;\n+\tint limit;\n+};\n+\n+static int dfs_on_ref(const char *refname,\n+\t\t      const struct object_id *oid, int flags,\n+\t\t      void *cb_data)\n+{\n+\tstruct cg_auto_data *data = (struct cg_auto_data *)cb_data;\n+\tint result = 0;\n+\tstruct object_id peeled;\n+\tstruct commit_list *stack = NULL;\n+\tstruct commit *commit;\n+\n+\tif (!peel_ref(refname, &peeled))\n+\t\toid = &peeled;\n+\tif (oid_object_info(the_repository, oid, NULL) != OBJ_COMMIT)\n+\t\treturn 0;\n+\n+\tcommit = lookup_commit(the_repository, oid);\n+\tif (!commit)\n+\t\treturn 0;\n+\tif (parse_commit(commit))\n+\t\treturn 0;\n+\n+\tcommit_list_append(commit, &stack);\n+\n+\twhile (!result && stack) {\n+\t\tstruct commit_list *parent;\n+\n+\t\tcommit = pop_commit(&stack);\n+\n+\t\tfor (parent = commit->parents; parent; parent = parent->next) {\n+\t\t\tif (parse_commit(parent->item) ||\n+\t\t\t    commit_graph_position(parent->item) != COMMIT_NOT_FROM_GRAPH ||\n+\t\t\t    parent->item->object.flags & SEEN)\n+\t\t\t\tcontinue;\n+\n+\t\t\tparent->item->object.flags |= SEEN;\n+\t\t\tdata->num_not_in_graph++;\n+\n+\t\t\tif (data->num_not_in_graph >= data->limit) {\n+\t\t\t\tresult = 1;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tcommit_list_append(parent->item, &stack);\n+\t\t}\n+\t}\n+\n+\tfree_commit_list(stack);\n+\treturn result;\n+}\n+\n+static int should_write_commit_graph(void)\n+{\n+\tint result;\n+\tstruct cg_auto_data data;\n+\n+\tdata.num_not_in_graph = 0;\n+\tdata.limit = 100;\n+\tgit_config_get_int(\"maintenance.commit-graph.auto\",\n+\t\t\t   &data.limit);\n+\n+\tif (!data.limit)\n+\t\treturn 0;\n+\tif (data.limit < 0)\n+\t\treturn 1;\n+\n+\tresult = for_each_ref(dfs_on_ref, &data);\n+\n+\tclear_commit_marks_all(SEEN);\n+\n+\treturn result;\n+}\n+\n static int run_write_commit_graph(struct maintenance_run_opts *opts)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n@@ -790,6 +871,7 @@ static struct maintenance_task tasks[] = {\n \t[TASK_COMMIT_GRAPH] = {\n \t\t\"commit-graph\",\n \t\tmaintenance_task_commit_graph,\n+\t\tshould_write_commit_graph,\n \t},\n };\n \ndiff --git a/object.h b/object.h\nindex 96a2105859..20b18805f0 100644\n--- a/object.h\n+++ b/object.h\n@@ -73,6 +73,7 @@ struct object_array {\n  * sha1-name.c:                                              20\n  * list-objects-filter.c:                                      21\n  * builtin/fsck.c:           0--3\n+ * builtin/gc.c:             0\n  * builtin/index-pack.c:                                     2021\n  * builtin/pack-objects.c:                                   20\n  * builtin/reflog.c:                   10--12\n-- \ngitgitgadget\n\n"},{"id":"405032","messageId":"69298aee24f317b03c39eef341f4d6ebebd88bc7.1599224956.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v4.git.1599224956.gitgitgadget@gmail.com","subject":"[PATCH v4 06/11] maintenance: add --task option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-04T13:09:10Z","receivedAt":"2020-09-04T13:11:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nA user may want to only run certain maintenance tasks in a certain\norder. Add the --task=<task> option, which allows a user to specify an\nordered list of tasks to run. These cannot be run multiple times,\nhowever.\n\nHere is where our array of maintenance_task pointers becomes critical.\nWe can sort the array of pointers based on the task order, but we do not\nwant to move the struct data itself in order to preserve the hashmap\nreferences. We use the hashmap to match the --task=<task> arguments into\nthe task struct data.\n\nKeep in mind that the 'enabled' member of the maintenance_task struct is\na placeholder for a future 'maintenance.<task>.enabled' config option.\nThus, we use the 'enabled' member to specify which tasks are run when\nthe user does not specify any --task=<task> arguments. The 'enabled'\nmember should be ignored if --task=<task> appears.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-maintenance.txt |  9 ++++-\n builtin/gc.c                      | 66 +++++++++++++++++++++++++++++--\n t/t7900-maintenance.sh            | 27 +++++++++++++\n 3 files changed, 98 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nindex fc5dbcf0b9..819ca41ab6 100644\n--- a/Documentation/git-maintenance.txt\n+++ b/Documentation/git-maintenance.txt\n@@ -30,7 +30,9 @@ SUBCOMMANDS\n -----------\n \n run::\n-\tRun one or more maintenance tasks.\n+\tRun one or more maintenance tasks. If one or more `--task=<task>`\n+\toptions are specified, then those tasks are run in the provided\n+\torder. Otherwise, only the `gc` task is run.\n \n TASKS\n -----\n@@ -63,6 +65,11 @@ OPTIONS\n --quiet::\n \tDo not report progress or other information over `stderr`.\n \n+--task=<task>::\n+\tIf this option is specified one or more times, then only run the\n+\tspecified tasks in the specified order. See the 'TASKS' section\n+\tfor the list of accepted `<task>` values.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 16e567992e..e94f263c77 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -701,7 +701,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n }\n \n static const char * const builtin_maintenance_run_usage[] = {\n-\tN_(\"git maintenance run [--auto] [--[no-]quiet]\"),\n+\tN_(\"git maintenance run [--auto] [--[no-]quiet] [--task=<task>]\"),\n \tNULL\n };\n \n@@ -759,6 +759,9 @@ struct maintenance_task {\n \tconst char *name;\n \tmaintenance_task_fn *fn;\n \tunsigned enabled:1;\n+\n+\t/* -1 if not selected. */\n+\tint selected_order;\n };\n \n enum maintenance_task_label {\n@@ -781,13 +784,32 @@ static struct maintenance_task tasks[] = {\n \t},\n };\n \n+static int compare_tasks_by_selection(const void *a_, const void *b_)\n+{\n+\tconst struct maintenance_task *a, *b;\n+\n+\ta = (const struct maintenance_task *)&a_;\n+\tb = (const struct maintenance_task *)&b_;\n+\n+\treturn b->selected_order - a->selected_order;\n+}\n+\n static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n {\n-\tint i;\n+\tint i, found_selected = 0;\n \tint result = 0;\n \n+\tfor (i = 0; !found_selected && i < TASK__COUNT; i++)\n+\t\tfound_selected = tasks[i].selected_order >= 0;\n+\n+\tif (found_selected)\n+\t\tQSORT(tasks, TASK__COUNT, compare_tasks_by_selection);\n+\n \tfor (i = 0; i < TASK__COUNT; i++) {\n-\t\tif (!tasks[i].enabled)\n+\t\tif (found_selected && tasks[i].selected_order < 0)\n+\t\t\tcontinue;\n+\n+\t\tif (!found_selected && !tasks[i].enabled)\n \t\t\tcontinue;\n \n \t\tif (tasks[i].fn(opts)) {\n@@ -799,20 +821,58 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n \treturn result;\n }\n \n+static int task_option_parse(const struct option *opt,\n+\t\t\t     const char *arg, int unset)\n+{\n+\tint i, num_selected = 0;\n+\tstruct maintenance_task *task = NULL;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tfor (i = 0; i < TASK__COUNT; i++) {\n+\t\tif (tasks[i].selected_order >= 0)\n+\t\t\tnum_selected++;\n+\t\tif (!strcasecmp(tasks[i].name, arg)) {\n+\t\t\ttask = &tasks[i];\n+\t\t}\n+\t}\n+\n+\tif (!task) {\n+\t\terror(_(\"'%s' is not a valid task\"), arg);\n+\t\treturn 1;\n+\t}\n+\n+\tif (task->selected_order >= 0) {\n+\t\terror(_(\"task '%s' cannot be selected multiple times\"), arg);\n+\t\treturn 1;\n+\t}\n+\n+\ttask->selected_order = num_selected + 1;\n+\n+\treturn 0;\n+}\n+\n static int maintenance_run(int argc, const char **argv, const char *prefix)\n {\n+\tint i;\n \tstruct maintenance_run_opts opts;\n \tstruct option builtin_maintenance_run_options[] = {\n \t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n \t\t\t N_(\"run tasks based on the state of the repository\")),\n \t\tOPT_BOOL(0, \"quiet\", &opts.quiet,\n \t\t\t N_(\"do not report progress or other information over stderr\")),\n+\t\tOPT_CALLBACK_F(0, \"task\", NULL, N_(\"task\"),\n+\t\t\tN_(\"run a specific task\"),\n+\t\t\tPARSE_OPT_NONEG, task_option_parse),\n \t\tOPT_END()\n \t};\n \tmemset(&opts, 0, sizeof(opts));\n \n \topts.quiet = !isatty(2);\n \n+\tfor (i = 0; i < TASK__COUNT; i++)\n+\t\ttasks[i].selected_order = -1;\n+\n \targc = parse_options(argc, argv, prefix,\n \t\t\t     builtin_maintenance_run_options,\n \t\t\t     builtin_maintenance_run_usage,\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex c0c4e6846e..792765aff7 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -25,4 +25,31 @@ test_expect_success 'run [--auto|--quiet]' '\n \ttest_subcommand git gc --no-quiet <run-no-quiet.txt\n '\n \n+test_expect_success 'run --task=<task>' '\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-commit-graph.txt\" \\\n+\t\tgit maintenance run --task=commit-graph 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-gc.txt\" \\\n+\t\tgit maintenance run --task=gc 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-commit-graph.txt\" \\\n+\t\tgit maintenance run --task=commit-graph 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-both.txt\" \\\n+\t\tgit maintenance run --task=commit-graph --task=gc 2>/dev/null &&\n+\ttest_subcommand ! git gc --quiet <run-commit-graph.txt &&\n+\ttest_subcommand git gc --quiet <run-gc.txt &&\n+\ttest_subcommand git gc --quiet <run-both.txt &&\n+\ttest_subcommand git commit-graph write --split --reachable --no-progress <run-commit-graph.txt &&\n+\ttest_subcommand ! git commit-graph write --split --reachable --no-progress <run-gc.txt &&\n+\ttest_subcommand git commit-graph write --split --reachable --no-progress <run-both.txt\n+'\n+\n+test_expect_success 'run --task=bogus' '\n+\ttest_must_fail git maintenance run --task=bogus 2>err &&\n+\ttest_i18ngrep \"is not a valid task\" err\n+'\n+\n+test_expect_success 'run --task duplicate' '\n+\ttest_must_fail git maintenance run --task=gc --task=gc 2>err &&\n+\ttest_i18ngrep \"cannot be selected multiple times\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"405033","messageId":"dc2a092366d49ae4dc4756e1f4e10b783bbc44a8.1599224956.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v4.git.1599224956.gitgitgadget@gmail.com","subject":"[PATCH v4 08/11] maintenance: create maintenance.<task>.enabled config","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-04T13:09:12Z","receivedAt":"2020-09-04T13:11:21Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nCurrently, a normal run of \"git maintenance run\" will only run the 'gc'\ntask, as it is the only one enabled. This is mostly for backwards-\ncompatible reasons since \"git maintenance run --auto\" commands replaced\nprevious \"git gc --auto\" commands after some Git processes. Users could\nmanually run specific maintenance tasks by calling \"git maintenance run\n--task=<task>\" directly.\n\nAllow users to customize which steps are run automatically using config.\nThe 'maintenance.<task>.enabled' option then can turn on these other\ntasks (or turn off the 'gc' task).\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/config.txt             |  2 ++\n Documentation/config/maintenance.txt |  6 ++++++\n Documentation/git-maintenance.txt    | 14 +++++++++-----\n builtin/gc.c                         | 19 +++++++++++++++++++\n t/t7900-maintenance.sh               |  8 ++++++++\n 5 files changed, 44 insertions(+), 5 deletions(-)\n create mode 100644 Documentation/config/maintenance.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 3042d80978..f93b6837e4 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -398,6 +398,8 @@ include::config/mailinfo.txt[]\n \n include::config/mailmap.txt[]\n \n+include::config/maintenance.txt[]\n+\n include::config/man.txt[]\n \n include::config/merge.txt[]\ndiff --git a/Documentation/config/maintenance.txt b/Documentation/config/maintenance.txt\nnew file mode 100644\nindex 0000000000..4402b8b49f\n--- /dev/null\n+++ b/Documentation/config/maintenance.txt\n@@ -0,0 +1,6 @@\n+maintenance.<task>.enabled::\n+\tThis boolean config option controls whether the maintenance task\n+\twith name `<task>` is run when no `--task` option is specified to\n+\t`git maintenance run`. These config values are ignored if a\n+\t`--task` option exists. By default, only `maintenance.gc.enabled`\n+\tis true.\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nindex 819ca41ab6..6abcb8255a 100644\n--- a/Documentation/git-maintenance.txt\n+++ b/Documentation/git-maintenance.txt\n@@ -30,9 +30,11 @@ SUBCOMMANDS\n -----------\n \n run::\n-\tRun one or more maintenance tasks. If one or more `--task=<task>`\n-\toptions are specified, then those tasks are run in the provided\n-\torder. Otherwise, only the `gc` task is run.\n+\tRun one or more maintenance tasks. If one or more `--task` options\n+\tare specified, then those tasks are run in that order. Otherwise,\n+\tthe tasks are determined by which `maintenance.<task>.enabled`\n+\tconfig options are true. By default, only `maintenance.gc.enabled`\n+\tis true.\n \n TASKS\n -----\n@@ -67,8 +69,10 @@ OPTIONS\n \n --task=<task>::\n \tIf this option is specified one or more times, then only run the\n-\tspecified tasks in the specified order. See the 'TASKS' section\n-\tfor the list of accepted `<task>` values.\n+\tspecified tasks in the specified order. If no `--task=<task>`\n+\targuments are specified, then only the tasks with\n+\t`maintenance.<task>.enabled` configured as `true` are considered.\n+\tSee the 'TASKS' section for the list of accepted `<task>` values.\n \n GIT\n ---\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 1cebb7282d..67a8d405a1 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -841,6 +841,24 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n \treturn result;\n }\n \n+static void initialize_task_config(void)\n+{\n+\tint i;\n+\tstruct strbuf config_name = STRBUF_INIT;\n+\tfor (i = 0; i < TASK__COUNT; i++) {\n+\t\tint config_value;\n+\n+\t\tstrbuf_setlen(&config_name, 0);\n+\t\tstrbuf_addf(&config_name, \"maintenance.%s.enabled\",\n+\t\t\t    tasks[i].name);\n+\n+\t\tif (!git_config_get_bool(config_name.buf, &config_value))\n+\t\t\ttasks[i].enabled = config_value;\n+\t}\n+\n+\tstrbuf_release(&config_name);\n+}\n+\n static int task_option_parse(const struct option *opt,\n \t\t\t     const char *arg, int unset)\n {\n@@ -889,6 +907,7 @@ static int maintenance_run(int argc, const char **argv, const char *prefix)\n \tmemset(&opts, 0, sizeof(opts));\n \n \topts.quiet = !isatty(2);\n+\tinitialize_task_config();\n \n \tfor (i = 0; i < TASK__COUNT; i++)\n \t\ttasks[i].selected_order = -1;\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex 792765aff7..290abb7832 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -25,6 +25,14 @@ test_expect_success 'run [--auto|--quiet]' '\n \ttest_subcommand git gc --no-quiet <run-no-quiet.txt\n '\n \n+test_expect_success 'maintenance.<task>.enabled' '\n+\tgit config maintenance.gc.enabled false &&\n+\tgit config maintenance.commit-graph.enabled true &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-config.txt\" git maintenance run 2>err &&\n+\ttest_subcommand ! git gc --quiet <run-config.txt &&\n+\ttest_subcommand git commit-graph write --split --reachable --no-progress <run-config.txt\n+'\n+\n test_expect_success 'run --task=<task>' '\n \tGIT_TRACE2_EVENT=\"$(pwd)/run-commit-graph.txt\" \\\n \t\tgit maintenance run --task=commit-graph 2>/dev/null &&\n-- \ngitgitgadget\n\n"},{"id":"405034","messageId":"aa961af387b7f458f75ad60b9a2a45da4bb43794.1599224956.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v4.git.1599224956.gitgitgadget@gmail.com","subject":"[PATCH v4 01/11] maintenance: create basic maintenance runner","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-04T13:09:05Z","receivedAt":"2020-09-04T13:13:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe 'gc' builtin is our current entrypoint for automatically maintaining\na repository. This one tool does many operations, such as repacking the\nrepository, packing refs, and rewriting the commit-graph file. The name\nimplies it performs \"garbage collection\" which means several different\nthings, and some users may not want to use this operation that rewrites\nthe entire object database.\n\nCreate a new 'maintenance' builtin that will become a more general-\npurpose command. To start, it will only support the 'run' subcommand,\nbut will later expand to add subcommands for scheduling maintenance in\nthe background.\n\nFor now, the 'maintenance' builtin is a thin shim over the 'gc' builtin.\nIn fact, the only option is the '--auto' toggle, which is handed\ndirectly to the 'gc' builtin. The current change is isolated to this\nsimple operation to prevent more interesting logic from being lost in\nall of the boilerplate of adding a new builtin.\n\nUse existing builtin/gc.c file because we want to share code between the\ntwo builtins. It is possible that we will have 'maintenance' replace the\n'gc' builtin entirely at some point, leaving 'git gc' as an alias for\nsome specific arguments to 'git maintenance run'.\n\nCreate a new test_subcommand helper that allows us to test if a certain\nsubcommand was run. It requires storing the GIT_TRACE2_EVENT logs in a\nfile. A negation mode is available that will be used in later tests.\n\nHelped-by: Jonathan Nieder <jrnieder@gmail.com>\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n .gitignore                        |  1 +\n Documentation/git-maintenance.txt | 57 +++++++++++++++++++++++++++++++\n builtin.h                         |  1 +\n builtin/gc.c                      | 57 +++++++++++++++++++++++++++++++\n command-list.txt                  |  1 +\n git.c                             |  1 +\n t/t7900-maintenance.sh            | 21 ++++++++++++\n t/test-lib-functions.sh           | 33 ++++++++++++++++++\n 8 files changed, 172 insertions(+)\n create mode 100644 Documentation/git-maintenance.txt\n create mode 100755 t/t7900-maintenance.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex ee509a2ad2..a5808fa30d 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -90,6 +90,7 @@\n /git-ls-tree\n /git-mailinfo\n /git-mailsplit\n+/git-maintenance\n /git-merge\n /git-merge-base\n /git-merge-index\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nnew file mode 100644\nindex 0000000000..ff47fb3641\n--- /dev/null\n+++ b/Documentation/git-maintenance.txt\n@@ -0,0 +1,57 @@\n+git-maintenance(1)\n+==================\n+\n+NAME\n+----\n+git-maintenance - Run tasks to optimize Git repository data\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git maintenance' run [<options>]\n+\n+\n+DESCRIPTION\n+-----------\n+Run tasks to optimize Git repository data, speeding up other Git commands\n+and reducing storage requirements for the repository.\n+\n+Git commands that add repository data, such as `git add` or `git fetch`,\n+are optimized for a responsive user experience. These commands do not take\n+time to optimize the Git data, since such optimizations scale with the full\n+size of the repository while these user commands each perform a relatively\n+small action.\n+\n+The `git maintenance` command provides flexibility for how to optimize the\n+Git repository.\n+\n+SUBCOMMANDS\n+-----------\n+\n+run::\n+\tRun one or more maintenance tasks.\n+\n+TASKS\n+-----\n+\n+gc::\n+\tClean up unnecessary files and optimize the local repository. \"GC\"\n+\tstands for \"garbage collection,\" but this task performs many\n+\tsmaller tasks. This task can be expensive for large repositories,\n+\tas it repacks all Git objects into a single pack-file. It can also\n+\tbe disruptive in some situations, as it deletes stale data. See\n+\tlinkgit:git-gc[1] for more details on garbage collection in Git.\n+\n+OPTIONS\n+-------\n+--auto::\n+\tWhen combined with the `run` subcommand, run maintenance tasks\n+\tonly if certain thresholds are met. For example, the `gc` task\n+\truns when the number of loose objects exceeds the number stored\n+\tin the `gc.auto` config setting, or when the number of pack-files\n+\texceeds the `gc.autoPackLimit` config setting.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/builtin.h b/builtin.h\nindex a5ae15bfe5..17c1c0ce49 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -167,6 +167,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix);\n int cmd_ls_remote(int argc, const char **argv, const char *prefix);\n int cmd_mailinfo(int argc, const char **argv, const char *prefix);\n int cmd_mailsplit(int argc, const char **argv, const char *prefix);\n+int cmd_maintenance(int argc, const char **argv, const char *prefix);\n int cmd_merge(int argc, const char **argv, const char *prefix);\n int cmd_merge_base(int argc, const char **argv, const char *prefix);\n int cmd_merge_index(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex aafa0946f5..7f2771e786 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -699,3 +699,60 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n \n \treturn 0;\n }\n+\n+static const char * const builtin_maintenance_run_usage[] = {\n+\tN_(\"git maintenance run [--auto]\"),\n+\tNULL\n+};\n+\n+struct maintenance_run_opts {\n+\tint auto_flag;\n+};\n+\n+static int maintenance_task_gc(struct maintenance_run_opts *opts)\n+{\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\n+\tchild.git_cmd = 1;\n+\tstrvec_push(&child.args, \"gc\");\n+\n+\tif (opts->auto_flag)\n+\t\tstrvec_push(&child.args, \"--auto\");\n+\n+\tclose_object_store(the_repository->objects);\n+\treturn run_command(&child);\n+}\n+\n+static int maintenance_run(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct maintenance_run_opts opts;\n+\tstruct option builtin_maintenance_run_options[] = {\n+\t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n+\t\t\t N_(\"run tasks based on the state of the repository\")),\n+\t\tOPT_END()\n+\t};\n+\tmemset(&opts, 0, sizeof(opts));\n+\n+\targc = parse_options(argc, argv, prefix,\n+\t\t\t     builtin_maintenance_run_options,\n+\t\t\t     builtin_maintenance_run_usage,\n+\t\t\t     PARSE_OPT_STOP_AT_NON_OPTION);\n+\n+\tif (argc != 0)\n+\t\tusage_with_options(builtin_maintenance_run_usage,\n+\t\t\t\t   builtin_maintenance_run_options);\n+\treturn maintenance_task_gc(&opts);\n+}\n+\n+static const char builtin_maintenance_usage[] = N_(\"git maintenance run [<options>]\");\n+\n+int cmd_maintenance(int argc, const char **argv, const char *prefix)\n+{\n+\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n+\t\tusage(builtin_maintenance_usage);\n+\n+\tif (!strcmp(argv[1], \"run\"))\n+\t\treturn maintenance_run(argc - 1, argv + 1, prefix);\n+\n+\tdie(_(\"invalid subcommand: %s\"), argv[1]);\n+}\ndiff --git a/command-list.txt b/command-list.txt\nindex e5901f2213..0e3204e7d1 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -117,6 +117,7 @@ git-ls-remote                           plumbinginterrogators\n git-ls-tree                             plumbinginterrogators\n git-mailinfo                            purehelpers\n git-mailsplit                           purehelpers\n+git-maintenance                         mainporcelain\n git-merge                               mainporcelain           history\n git-merge-base                          plumbinginterrogators\n git-merge-file                          plumbingmanipulators\ndiff --git a/git.c b/git.c\nindex 8bd1d7551d..24f250d29a 100644\n--- a/git.c\n+++ b/git.c\n@@ -529,6 +529,7 @@ static struct cmd_struct commands[] = {\n \t{ \"ls-tree\", cmd_ls_tree, RUN_SETUP },\n \t{ \"mailinfo\", cmd_mailinfo, RUN_SETUP_GENTLY | NO_PARSEOPT },\n \t{ \"mailsplit\", cmd_mailsplit, NO_PARSEOPT },\n+\t{ \"maintenance\", cmd_maintenance, RUN_SETUP_GENTLY | NO_PARSEOPT },\n \t{ \"merge\", cmd_merge, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"merge-base\", cmd_merge_base, RUN_SETUP },\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nnew file mode 100755\nindex 0000000000..14aa691f19\n--- /dev/null\n+++ b/t/t7900-maintenance.sh\n@@ -0,0 +1,21 @@\n+#!/bin/sh\n+\n+test_description='git maintenance builtin'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'help text' '\n+\ttest_expect_code 129 git maintenance -h 2>err &&\n+\ttest_i18ngrep \"usage: git maintenance run\" err &&\n+\ttest_expect_code 128 git maintenance barf 2>err &&\n+\ttest_i18ngrep \"invalid subcommand: barf\" err\n+'\n+\n+test_expect_success 'run [--auto]' '\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-auto.txt\" git maintenance run &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" git maintenance run --auto &&\n+\ttest_subcommand git gc <run-no-auto.txt &&\n+\ttest_subcommand git gc --auto <run-auto.txt\n+'\n+\n+test_done\ndiff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh\nindex 6a8e194a99..d805e73f45 100644\n--- a/t/test-lib-functions.sh\n+++ b/t/test-lib-functions.sh\n@@ -1628,3 +1628,36 @@ test_path_is_hidden () {\n \tcase \"$(\"$SYSTEMROOT\"/system32/attrib \"$1\")\" in *H*?:*) return 0;; esac\n \treturn 1\n }\n+\n+# Check that the given command was invoked as part of the\n+# trace2-format trace on stdin.\n+#\n+#\ttest_subcommand [!] <command> <args>... < <trace>\n+#\n+# For example, to look for an invocation of \"git upload-pack\n+# /path/to/repo\"\n+#\n+#\tGIT_TRACE2_EVENT=event.log git fetch ... &&\n+#\ttest_subcommand git upload-pack \"$PATH\" <event.log\n+#\n+# If the first parameter passed is !, this instead checks that\n+# the given command was not called.\n+#\n+test_subcommand () {\n+\tlocal negate=\n+\tif test \"$1\" = \"!\"\n+\tthen\n+\t\tnegate=t\n+\t\tshift\n+\tfi\n+\n+\tlocal expr=$(printf '\"%s\",' \"$@\")\n+\texpr=\"${expr%,}\"\n+\n+\tif test -n \"$negate\"\n+\tthen\n+\t\t! grep \"\\[$expr\\]\"\n+\telse\n+\t\tgrep \"\\[$expr\\]\"\n+\tfi\n+}\n-- \ngitgitgadget\n\n"},{"id":"405035","messageId":"5386d8a6283d814c0d0d2b7a180409f064cea709.1599224956.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v4.git.1599224956.gitgitgadget@gmail.com","subject":"[PATCH v4 02/11] maintenance: add --quiet option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-04T13:09:06Z","receivedAt":"2020-09-04T13:14:00Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nMaintenance activities are commonly used as steps in larger scripts.\nProviding a '--quiet' option allows those scripts to be less noisy when\nrun on a terminal window. Turn this mode on by default when stderr is\nnot a terminal.\n\nPipe the option to the 'git gc' child process.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-maintenance.txt |  3 +++\n builtin/gc.c                      | 11 ++++++++++-\n t/t7900-maintenance.sh            | 15 ++++++++++-----\n 3 files changed, 23 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nindex ff47fb3641..04fa0fe329 100644\n--- a/Documentation/git-maintenance.txt\n+++ b/Documentation/git-maintenance.txt\n@@ -52,6 +52,9 @@ OPTIONS\n \tin the `gc.auto` config setting, or when the number of pack-files\n \texceeds the `gc.autoPackLimit` config setting.\n \n+--quiet::\n+\tDo not report progress or other information over `stderr`.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 7f2771e786..da11f1edcd 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -701,12 +701,13 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n }\n \n static const char * const builtin_maintenance_run_usage[] = {\n-\tN_(\"git maintenance run [--auto]\"),\n+\tN_(\"git maintenance run [--auto] [--[no-]quiet]\"),\n \tNULL\n };\n \n struct maintenance_run_opts {\n \tint auto_flag;\n+\tint quiet;\n };\n \n static int maintenance_task_gc(struct maintenance_run_opts *opts)\n@@ -718,6 +719,10 @@ static int maintenance_task_gc(struct maintenance_run_opts *opts)\n \n \tif (opts->auto_flag)\n \t\tstrvec_push(&child.args, \"--auto\");\n+\tif (opts->quiet)\n+\t\tstrvec_push(&child.args, \"--quiet\");\n+\telse\n+\t\tstrvec_push(&child.args, \"--no-quiet\");\n \n \tclose_object_store(the_repository->objects);\n \treturn run_command(&child);\n@@ -729,10 +734,14 @@ static int maintenance_run(int argc, const char **argv, const char *prefix)\n \tstruct option builtin_maintenance_run_options[] = {\n \t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n \t\t\t N_(\"run tasks based on the state of the repository\")),\n+\t\tOPT_BOOL(0, \"quiet\", &opts.quiet,\n+\t\t\t N_(\"do not report progress or other information over stderr\")),\n \t\tOPT_END()\n \t};\n \tmemset(&opts, 0, sizeof(opts));\n \n+\topts.quiet = !isatty(2);\n+\n \targc = parse_options(argc, argv, prefix,\n \t\t\t     builtin_maintenance_run_options,\n \t\t\t     builtin_maintenance_run_usage,\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex 14aa691f19..47d512464c 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -11,11 +11,16 @@ test_expect_success 'help text' '\n \ttest_i18ngrep \"invalid subcommand: barf\" err\n '\n \n-test_expect_success 'run [--auto]' '\n-\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-auto.txt\" git maintenance run &&\n-\tGIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" git maintenance run --auto &&\n-\ttest_subcommand git gc <run-no-auto.txt &&\n-\ttest_subcommand git gc --auto <run-auto.txt\n+test_expect_success 'run [--auto|--quiet]' '\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-auto.txt\" \\\n+\t\tgit maintenance run 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" \\\n+\t\tgit maintenance run --auto 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-quiet.txt\" \\\n+\t\tgit maintenance run --no-quiet 2>/dev/null &&\n+\ttest_subcommand git gc --quiet <run-no-auto.txt &&\n+\ttest_subcommand git gc --auto --quiet <run-auto.txt &&\n+\ttest_subcommand git gc --no-quiet <run-no-quiet.txt\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"405776","messageId":"1d2f2731bd3c206dc53fa2610d901b95e53b6295.1600366313.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v5.git.1600366313.gitgitgadget@gmail.com","subject":"[PATCH v5 04/11] maintenance: initialize task array","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-17T18:11:45Z","receivedAt":"2020-09-17T18:13:25Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nIn anticipation of implementing multiple maintenance tasks inside the\n'maintenance' builtin, use a list of structs to describe the work to be\ndone.\n\nThe struct maintenance_task stores the name of the task (as given by a\nfuture command-line argument) along with a function pointer to its\nimplementation and a boolean for whether the step is enabled.\n\nA list these structs are initialized with the full list of implemented\ntasks along with a default order. For now, this list only contains the\n\"gc\" task. This task is also the only task enabled by default.\n\nThe run subcommand will return a nonzero exit code if any task fails.\nHowever, it will attempt all tasks in its loop before returning with the\nfailure. Also each failed task will print an error message.\n\nHelped-by: Taylor Blau <me@ttaylorr.com>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/gc.c | 43 ++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 42 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex bacce0c747..904fb2d9aa 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -728,6 +728,47 @@ static int maintenance_task_gc(struct maintenance_run_opts *opts)\n \treturn run_command(&child);\n }\n \n+typedef int maintenance_task_fn(struct maintenance_run_opts *opts);\n+\n+struct maintenance_task {\n+\tconst char *name;\n+\tmaintenance_task_fn *fn;\n+\tunsigned enabled:1;\n+};\n+\n+enum maintenance_task_label {\n+\tTASK_GC,\n+\n+\t/* Leave as final value */\n+\tTASK__COUNT\n+};\n+\n+static struct maintenance_task tasks[] = {\n+\t[TASK_GC] = {\n+\t\t\"gc\",\n+\t\tmaintenance_task_gc,\n+\t\t1,\n+\t},\n+};\n+\n+static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n+{\n+\tint i;\n+\tint result = 0;\n+\n+\tfor (i = 0; i < TASK__COUNT; i++) {\n+\t\tif (!tasks[i].enabled)\n+\t\t\tcontinue;\n+\n+\t\tif (tasks[i].fn(opts)) {\n+\t\t\terror(_(\"task '%s' failed\"), tasks[i].name);\n+\t\t\tresult = 1;\n+\t\t}\n+\t}\n+\n+\treturn result;\n+}\n+\n static int maintenance_run(int argc, const char **argv, const char *prefix)\n {\n \tstruct maintenance_run_opts opts;\n@@ -750,7 +791,7 @@ static int maintenance_run(int argc, const char **argv, const char *prefix)\n \tif (argc != 0)\n \t\tusage_with_options(builtin_maintenance_run_usage,\n \t\t\t\t   builtin_maintenance_run_options);\n-\treturn maintenance_task_gc(&opts);\n+\treturn maintenance_run_tasks(&opts);\n }\n \n static const char builtin_maintenance_usage[] = N_(\"git maintenance run [<options>]\");\n-- \ngitgitgadget\n\n"},{"id":"405777","messageId":"8204ebbf8312b30ee8da38b73f0877cbe1865bcf.1600366313.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v5.git.1600366313.gitgitgadget@gmail.com","subject":"[PATCH v5 05/11] maintenance: add commit-graph task","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-17T18:11:46Z","receivedAt":"2020-09-17T18:14:08Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe first new task in the 'git maintenance' builtin is the\n'commit-graph' task. This updates the commit-graph file\nincrementally with the command\n\n\tgit commit-graph write --reachable --split\n\nBy writing an incremental commit-graph file using the \"--split\"\noption we minimize the disruption from this operation. The default\nbehavior is to merge layers until the new \"top\" layer is less than\nhalf the size of the layer below. This provides quick writes most\nof the time, with the longer writes following a power law\ndistribution.\n\nMost importantly, concurrent Git processes only look at the\ncommit-graph-chain file for a very short amount of time, so they\nwill verly likely not be holding a handle to the file when we try\nto replace it. (This only matters on Windows.)\n\nIf a concurrent process reads the old commit-graph-chain file, but\nour job expires some of the .graph files before they can be read,\nthen those processes will see a warning message (but not fail).\nThis could be avoided by a future update to use the --expire-time\nargument when writing the commit-graph.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/git-maintenance.txt |  8 ++++++++\n builtin/gc.c                      | 30 ++++++++++++++++++++++++++++++\n commit-graph.c                    |  8 ++++----\n commit-graph.h                    |  1 +\n t/t7900-maintenance.sh            |  2 ++\n 5 files changed, 45 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nindex 04fa0fe329..fc5dbcf0b9 100644\n--- a/Documentation/git-maintenance.txt\n+++ b/Documentation/git-maintenance.txt\n@@ -35,6 +35,14 @@ run::\n TASKS\n -----\n \n+commit-graph::\n+\tThe `commit-graph` job updates the `commit-graph` files incrementally,\n+\tthen verifies that the written data is correct. The incremental\n+\twrite is safe to run alongside concurrent Git processes since it\n+\twill not expire `.graph` files that were in the previous\n+\t`commit-graph-chain` file. They will be deleted by a later run based\n+\ton the expiration delay.\n+\n gc::\n \tClean up unnecessary files and optimize the local repository. \"GC\"\n \tstands for \"garbage collection,\" but this task performs many\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 904fb2d9aa..86b807a008 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -710,6 +710,31 @@ struct maintenance_run_opts {\n \tint quiet;\n };\n \n+static int run_write_commit_graph(struct maintenance_run_opts *opts)\n+{\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\n+\tchild.git_cmd = 1;\n+\tstrvec_pushl(&child.args, \"commit-graph\", \"write\",\n+\t\t     \"--split\", \"--reachable\", NULL);\n+\n+\tif (opts->quiet)\n+\t\tstrvec_push(&child.args, \"--no-progress\");\n+\n+\treturn !!run_command(&child);\n+}\n+\n+static int maintenance_task_commit_graph(struct maintenance_run_opts *opts)\n+{\n+\tclose_object_store(the_repository->objects);\n+\tif (run_write_commit_graph(opts)) {\n+\t\terror(_(\"failed to write commit-graph\"));\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n static int maintenance_task_gc(struct maintenance_run_opts *opts)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n@@ -738,6 +763,7 @@ struct maintenance_task {\n \n enum maintenance_task_label {\n \tTASK_GC,\n+\tTASK_COMMIT_GRAPH,\n \n \t/* Leave as final value */\n \tTASK__COUNT\n@@ -749,6 +775,10 @@ static struct maintenance_task tasks[] = {\n \t\tmaintenance_task_gc,\n \t\t1,\n \t},\n+\t[TASK_COMMIT_GRAPH] = {\n+\t\t\"commit-graph\",\n+\t\tmaintenance_task_commit_graph,\n+\t},\n };\n \n static int maintenance_run_tasks(struct maintenance_run_opts *opts)\ndiff --git a/commit-graph.c b/commit-graph.c\nindex e51c91dd5b..a9b0db7d4a 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -172,7 +172,7 @@ static char *get_split_graph_filename(struct object_directory *odb,\n \t\t       oid_hex);\n }\n \n-static char *get_chain_filename(struct object_directory *odb)\n+char *get_commit_graph_chain_filename(struct object_directory *odb)\n {\n \treturn xstrfmt(\"%s/info/commit-graphs/commit-graph-chain\", odb->path);\n }\n@@ -516,7 +516,7 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r,\n \tstruct stat st;\n \tstruct object_id *oids;\n \tint i = 0, valid = 1, count;\n-\tchar *chain_name = get_chain_filename(odb);\n+\tchar *chain_name = get_commit_graph_chain_filename(odb);\n \tFILE *fp;\n \tint stat_res;\n \n@@ -1668,7 +1668,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)\n \t}\n \n \tif (ctx->split) {\n-\t\tchar *lock_name = get_chain_filename(ctx->odb);\n+\t\tchar *lock_name = get_commit_graph_chain_filename(ctx->odb);\n \n \t\thold_lock_file_for_update_mode(&lk, lock_name,\n \t\t\t\t\t       LOCK_DIE_ON_ERROR, 0444);\n@@ -2038,7 +2038,7 @@ static void expire_commit_graphs(struct write_commit_graph_context *ctx)\n \tif (ctx->split_opts && ctx->split_opts->expire_time)\n \t\texpire_time = ctx->split_opts->expire_time;\n \tif (!ctx->split) {\n-\t\tchar *chain_file_name = get_chain_filename(ctx->odb);\n+\t\tchar *chain_file_name = get_commit_graph_chain_filename(ctx->odb);\n \t\tunlink(chain_file_name);\n \t\tfree(chain_file_name);\n \t\tctx->num_commit_graphs_after = 0;\ndiff --git a/commit-graph.h b/commit-graph.h\nindex 09a97030dc..765221cdcc 100644\n--- a/commit-graph.h\n+++ b/commit-graph.h\n@@ -25,6 +25,7 @@ struct raw_object_store;\n struct string_list;\n \n char *get_commit_graph_filename(struct object_directory *odb);\n+char *get_commit_graph_chain_filename(struct object_directory *odb);\n int open_commit_graph(const char *graph_file, int *fd, struct stat *st);\n \n /*\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex 5637053bf6..505a1b4d60 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -4,6 +4,8 @@ test_description='git maintenance builtin'\n \n . ./test-lib.sh\n \n+GIT_TEST_COMMIT_GRAPH=0\n+\n test_expect_success 'help text' '\n \ttest_expect_code 129 git maintenance -h 2>err &&\n \ttest_i18ngrep \"usage: git maintenance run\" err &&\n-- \ngitgitgadget\n\n"},{"id":"405778","messageId":"91b8555c9ec9f84690c7bcea01f0c546ada2de1b.1600366313.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v5.git.1600366313.gitgitgadget@gmail.com","subject":"[PATCH v5 06/11] maintenance: add --task option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-17T18:11:47Z","receivedAt":"2020-09-17T18:14:25Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nA user may want to only run certain maintenance tasks in a certain\norder. Add the --task=<task> option, which allows a user to specify an\nordered list of tasks to run. These cannot be run multiple times,\nhowever.\n\nHere is where our array of maintenance_task pointers becomes critical.\nWe can sort the array of pointers based on the task order, but we do not\nwant to move the struct data itself in order to preserve the hashmap\nreferences. We use the hashmap to match the --task=<task> arguments into\nthe task struct data.\n\nKeep in mind that the 'enabled' member of the maintenance_task struct is\na placeholder for a future 'maintenance.<task>.enabled' config option.\nThus, we use the 'enabled' member to specify which tasks are run when\nthe user does not specify any --task=<task> arguments. The 'enabled'\nmember should be ignored if --task=<task> appears.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/git-maintenance.txt |  9 ++++-\n builtin/gc.c                      | 66 +++++++++++++++++++++++++++++--\n t/t7900-maintenance.sh            | 27 +++++++++++++\n 3 files changed, 98 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nindex fc5dbcf0b9..819ca41ab6 100644\n--- a/Documentation/git-maintenance.txt\n+++ b/Documentation/git-maintenance.txt\n@@ -30,7 +30,9 @@ SUBCOMMANDS\n -----------\n \n run::\n-\tRun one or more maintenance tasks.\n+\tRun one or more maintenance tasks. If one or more `--task=<task>`\n+\toptions are specified, then those tasks are run in the provided\n+\torder. Otherwise, only the `gc` task is run.\n \n TASKS\n -----\n@@ -63,6 +65,11 @@ OPTIONS\n --quiet::\n \tDo not report progress or other information over `stderr`.\n \n+--task=<task>::\n+\tIf this option is specified one or more times, then only run the\n+\tspecified tasks in the specified order. See the 'TASKS' section\n+\tfor the list of accepted `<task>` values.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 86b807a008..00fff59bdb 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -701,7 +701,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n }\n \n static const char * const builtin_maintenance_run_usage[] = {\n-\tN_(\"git maintenance run [--auto] [--[no-]quiet]\"),\n+\tN_(\"git maintenance run [--auto] [--[no-]quiet] [--task=<task>]\"),\n \tNULL\n };\n \n@@ -759,6 +759,9 @@ struct maintenance_task {\n \tconst char *name;\n \tmaintenance_task_fn *fn;\n \tunsigned enabled:1;\n+\n+\t/* -1 if not selected. */\n+\tint selected_order;\n };\n \n enum maintenance_task_label {\n@@ -781,13 +784,32 @@ static struct maintenance_task tasks[] = {\n \t},\n };\n \n+static int compare_tasks_by_selection(const void *a_, const void *b_)\n+{\n+\tconst struct maintenance_task *a, *b;\n+\n+\ta = (const struct maintenance_task *)&a_;\n+\tb = (const struct maintenance_task *)&b_;\n+\n+\treturn b->selected_order - a->selected_order;\n+}\n+\n static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n {\n-\tint i;\n+\tint i, found_selected = 0;\n \tint result = 0;\n \n+\tfor (i = 0; !found_selected && i < TASK__COUNT; i++)\n+\t\tfound_selected = tasks[i].selected_order >= 0;\n+\n+\tif (found_selected)\n+\t\tQSORT(tasks, TASK__COUNT, compare_tasks_by_selection);\n+\n \tfor (i = 0; i < TASK__COUNT; i++) {\n-\t\tif (!tasks[i].enabled)\n+\t\tif (found_selected && tasks[i].selected_order < 0)\n+\t\t\tcontinue;\n+\n+\t\tif (!found_selected && !tasks[i].enabled)\n \t\t\tcontinue;\n \n \t\tif (tasks[i].fn(opts)) {\n@@ -799,20 +821,58 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n \treturn result;\n }\n \n+static int task_option_parse(const struct option *opt,\n+\t\t\t     const char *arg, int unset)\n+{\n+\tint i, num_selected = 0;\n+\tstruct maintenance_task *task = NULL;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tfor (i = 0; i < TASK__COUNT; i++) {\n+\t\tif (tasks[i].selected_order >= 0)\n+\t\t\tnum_selected++;\n+\t\tif (!strcasecmp(tasks[i].name, arg)) {\n+\t\t\ttask = &tasks[i];\n+\t\t}\n+\t}\n+\n+\tif (!task) {\n+\t\terror(_(\"'%s' is not a valid task\"), arg);\n+\t\treturn 1;\n+\t}\n+\n+\tif (task->selected_order >= 0) {\n+\t\terror(_(\"task '%s' cannot be selected multiple times\"), arg);\n+\t\treturn 1;\n+\t}\n+\n+\ttask->selected_order = num_selected + 1;\n+\n+\treturn 0;\n+}\n+\n static int maintenance_run(int argc, const char **argv, const char *prefix)\n {\n+\tint i;\n \tstruct maintenance_run_opts opts;\n \tstruct option builtin_maintenance_run_options[] = {\n \t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n \t\t\t N_(\"run tasks based on the state of the repository\")),\n \t\tOPT_BOOL(0, \"quiet\", &opts.quiet,\n \t\t\t N_(\"do not report progress or other information over stderr\")),\n+\t\tOPT_CALLBACK_F(0, \"task\", NULL, N_(\"task\"),\n+\t\t\tN_(\"run a specific task\"),\n+\t\t\tPARSE_OPT_NONEG, task_option_parse),\n \t\tOPT_END()\n \t};\n \tmemset(&opts, 0, sizeof(opts));\n \n \topts.quiet = !isatty(2);\n \n+\tfor (i = 0; i < TASK__COUNT; i++)\n+\t\ttasks[i].selected_order = -1;\n+\n \targc = parse_options(argc, argv, prefix,\n \t\t\t     builtin_maintenance_run_options,\n \t\t\t     builtin_maintenance_run_usage,\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex 505a1b4d60..fb4cadd30c 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -27,4 +27,31 @@ test_expect_success 'run [--auto|--quiet]' '\n \ttest_subcommand git gc --no-quiet <run-no-quiet.txt\n '\n \n+test_expect_success 'run --task=<task>' '\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-commit-graph.txt\" \\\n+\t\tgit maintenance run --task=commit-graph 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-gc.txt\" \\\n+\t\tgit maintenance run --task=gc 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-commit-graph.txt\" \\\n+\t\tgit maintenance run --task=commit-graph 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-both.txt\" \\\n+\t\tgit maintenance run --task=commit-graph --task=gc 2>/dev/null &&\n+\ttest_subcommand ! git gc --quiet <run-commit-graph.txt &&\n+\ttest_subcommand git gc --quiet <run-gc.txt &&\n+\ttest_subcommand git gc --quiet <run-both.txt &&\n+\ttest_subcommand git commit-graph write --split --reachable --no-progress <run-commit-graph.txt &&\n+\ttest_subcommand ! git commit-graph write --split --reachable --no-progress <run-gc.txt &&\n+\ttest_subcommand git commit-graph write --split --reachable --no-progress <run-both.txt\n+'\n+\n+test_expect_success 'run --task=bogus' '\n+\ttest_must_fail git maintenance run --task=bogus 2>err &&\n+\ttest_i18ngrep \"is not a valid task\" err\n+'\n+\n+test_expect_success 'run --task duplicate' '\n+\ttest_must_fail git maintenance run --task=gc --task=gc 2>err &&\n+\ttest_i18ngrep \"cannot be selected multiple times\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"405779","messageId":"00a0d63508be1010df547a0ac56c73ec5df116b9.1600366313.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v5.git.1600366313.gitgitgadget@gmail.com","subject":"[PATCH v5 01/11] maintenance: create basic maintenance runner","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-17T18:11:42Z","receivedAt":"2020-09-17T18:14:49Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe 'gc' builtin is our current entrypoint for automatically maintaining\na repository. This one tool does many operations, such as repacking the\nrepository, packing refs, and rewriting the commit-graph file. The name\nimplies it performs \"garbage collection\" which means several different\nthings, and some users may not want to use this operation that rewrites\nthe entire object database.\n\nCreate a new 'maintenance' builtin that will become a more general-\npurpose command. To start, it will only support the 'run' subcommand,\nbut will later expand to add subcommands for scheduling maintenance in\nthe background.\n\nFor now, the 'maintenance' builtin is a thin shim over the 'gc' builtin.\nIn fact, the only option is the '--auto' toggle, which is handed\ndirectly to the 'gc' builtin. The current change is isolated to this\nsimple operation to prevent more interesting logic from being lost in\nall of the boilerplate of adding a new builtin.\n\nUse existing builtin/gc.c file because we want to share code between the\ntwo builtins. It is possible that we will have 'maintenance' replace the\n'gc' builtin entirely at some point, leaving 'git gc' as an alias for\nsome specific arguments to 'git maintenance run'.\n\nCreate a new test_subcommand helper that allows us to test if a certain\nsubcommand was run. It requires storing the GIT_TRACE2_EVENT logs in a\nfile. A negation mode is available that will be used in later tests.\n\nHelped-by: Jonathan Nieder <jrnieder@gmail.com>\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n .gitignore                        |  1 +\n Documentation/git-maintenance.txt | 57 ++++++++++++++++++++++++++++++\n builtin.h                         |  1 +\n builtin/gc.c                      | 58 +++++++++++++++++++++++++++++++\n command-list.txt                  |  1 +\n git.c                             |  1 +\n t/t7900-maintenance.sh            | 23 ++++++++++++\n t/test-lib-functions.sh           | 33 ++++++++++++++++++\n 8 files changed, 175 insertions(+)\n create mode 100644 Documentation/git-maintenance.txt\n create mode 100755 t/t7900-maintenance.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex ee509a2ad2..a5808fa30d 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -90,6 +90,7 @@\n /git-ls-tree\n /git-mailinfo\n /git-mailsplit\n+/git-maintenance\n /git-merge\n /git-merge-base\n /git-merge-index\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nnew file mode 100644\nindex 0000000000..ff47fb3641\n--- /dev/null\n+++ b/Documentation/git-maintenance.txt\n@@ -0,0 +1,57 @@\n+git-maintenance(1)\n+==================\n+\n+NAME\n+----\n+git-maintenance - Run tasks to optimize Git repository data\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git maintenance' run [<options>]\n+\n+\n+DESCRIPTION\n+-----------\n+Run tasks to optimize Git repository data, speeding up other Git commands\n+and reducing storage requirements for the repository.\n+\n+Git commands that add repository data, such as `git add` or `git fetch`,\n+are optimized for a responsive user experience. These commands do not take\n+time to optimize the Git data, since such optimizations scale with the full\n+size of the repository while these user commands each perform a relatively\n+small action.\n+\n+The `git maintenance` command provides flexibility for how to optimize the\n+Git repository.\n+\n+SUBCOMMANDS\n+-----------\n+\n+run::\n+\tRun one or more maintenance tasks.\n+\n+TASKS\n+-----\n+\n+gc::\n+\tClean up unnecessary files and optimize the local repository. \"GC\"\n+\tstands for \"garbage collection,\" but this task performs many\n+\tsmaller tasks. This task can be expensive for large repositories,\n+\tas it repacks all Git objects into a single pack-file. It can also\n+\tbe disruptive in some situations, as it deletes stale data. See\n+\tlinkgit:git-gc[1] for more details on garbage collection in Git.\n+\n+OPTIONS\n+-------\n+--auto::\n+\tWhen combined with the `run` subcommand, run maintenance tasks\n+\tonly if certain thresholds are met. For example, the `gc` task\n+\truns when the number of loose objects exceeds the number stored\n+\tin the `gc.auto` config setting, or when the number of pack-files\n+\texceeds the `gc.autoPackLimit` config setting.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/builtin.h b/builtin.h\nindex a5ae15bfe5..17c1c0ce49 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -167,6 +167,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix);\n int cmd_ls_remote(int argc, const char **argv, const char *prefix);\n int cmd_mailinfo(int argc, const char **argv, const char *prefix);\n int cmd_mailsplit(int argc, const char **argv, const char *prefix);\n+int cmd_maintenance(int argc, const char **argv, const char *prefix);\n int cmd_merge(int argc, const char **argv, const char *prefix);\n int cmd_merge_base(int argc, const char **argv, const char *prefix);\n int cmd_merge_index(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex aafa0946f5..ec064e8686 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -699,3 +699,61 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n \n \treturn 0;\n }\n+\n+static const char * const builtin_maintenance_run_usage[] = {\n+\tN_(\"git maintenance run [--auto]\"),\n+\tNULL\n+};\n+\n+struct maintenance_run_opts {\n+\tint auto_flag;\n+};\n+\n+static int maintenance_task_gc(struct maintenance_run_opts *opts)\n+{\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\n+\tchild.git_cmd = 1;\n+\tstrvec_push(&child.args, \"gc\");\n+\n+\tif (opts->auto_flag)\n+\t\tstrvec_push(&child.args, \"--auto\");\n+\n+\tclose_object_store(the_repository->objects);\n+\treturn run_command(&child);\n+}\n+\n+static int maintenance_run(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct maintenance_run_opts opts;\n+\tstruct option builtin_maintenance_run_options[] = {\n+\t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n+\t\t\t N_(\"run tasks based on the state of the repository\")),\n+\t\tOPT_END()\n+\t};\n+\tmemset(&opts, 0, sizeof(opts));\n+\n+\targc = parse_options(argc, argv, prefix,\n+\t\t\t     builtin_maintenance_run_options,\n+\t\t\t     builtin_maintenance_run_usage,\n+\t\t\t     PARSE_OPT_STOP_AT_NON_OPTION);\n+\n+\tif (argc != 0)\n+\t\tusage_with_options(builtin_maintenance_run_usage,\n+\t\t\t\t   builtin_maintenance_run_options);\n+\treturn maintenance_task_gc(&opts);\n+}\n+\n+static const char builtin_maintenance_usage[] = N_(\"git maintenance run [<options>]\");\n+\n+int cmd_maintenance(int argc, const char **argv, const char *prefix)\n+{\n+\tif (argc < 2 ||\n+\t    (argc == 2 && !strcmp(argv[1], \"-h\")))\n+\t\tusage(builtin_maintenance_usage);\n+\n+\tif (!strcmp(argv[1], \"run\"))\n+\t\treturn maintenance_run(argc - 1, argv + 1, prefix);\n+\n+\tdie(_(\"invalid subcommand: %s\"), argv[1]);\n+}\ndiff --git a/command-list.txt b/command-list.txt\nindex e5901f2213..0e3204e7d1 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -117,6 +117,7 @@ git-ls-remote                           plumbinginterrogators\n git-ls-tree                             plumbinginterrogators\n git-mailinfo                            purehelpers\n git-mailsplit                           purehelpers\n+git-maintenance                         mainporcelain\n git-merge                               mainporcelain           history\n git-merge-base                          plumbinginterrogators\n git-merge-file                          plumbingmanipulators\ndiff --git a/git.c b/git.c\nindex 8bd1d7551d..24f250d29a 100644\n--- a/git.c\n+++ b/git.c\n@@ -529,6 +529,7 @@ static struct cmd_struct commands[] = {\n \t{ \"ls-tree\", cmd_ls_tree, RUN_SETUP },\n \t{ \"mailinfo\", cmd_mailinfo, RUN_SETUP_GENTLY | NO_PARSEOPT },\n \t{ \"mailsplit\", cmd_mailsplit, NO_PARSEOPT },\n+\t{ \"maintenance\", cmd_maintenance, RUN_SETUP_GENTLY | NO_PARSEOPT },\n \t{ \"merge\", cmd_merge, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"merge-base\", cmd_merge_base, RUN_SETUP },\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nnew file mode 100755\nindex 0000000000..c2f0b1d0c0\n--- /dev/null\n+++ b/t/t7900-maintenance.sh\n@@ -0,0 +1,23 @@\n+#!/bin/sh\n+\n+test_description='git maintenance builtin'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'help text' '\n+\ttest_expect_code 129 git maintenance -h 2>err &&\n+\ttest_i18ngrep \"usage: git maintenance run\" err &&\n+\ttest_expect_code 128 git maintenance barf 2>err &&\n+\ttest_i18ngrep \"invalid subcommand: barf\" err &&\n+\ttest_expect_code 129 git maintenance 2>err &&\n+\ttest_i18ngrep \"usage: git maintenance\" err\n+'\n+\n+test_expect_success 'run [--auto]' '\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-auto.txt\" git maintenance run &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" git maintenance run --auto &&\n+\ttest_subcommand git gc <run-no-auto.txt &&\n+\ttest_subcommand git gc --auto <run-auto.txt\n+'\n+\n+test_done\ndiff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh\nindex 6a8e194a99..d805e73f45 100644\n--- a/t/test-lib-functions.sh\n+++ b/t/test-lib-functions.sh\n@@ -1628,3 +1628,36 @@ test_path_is_hidden () {\n \tcase \"$(\"$SYSTEMROOT\"/system32/attrib \"$1\")\" in *H*?:*) return 0;; esac\n \treturn 1\n }\n+\n+# Check that the given command was invoked as part of the\n+# trace2-format trace on stdin.\n+#\n+#\ttest_subcommand [!] <command> <args>... < <trace>\n+#\n+# For example, to look for an invocation of \"git upload-pack\n+# /path/to/repo\"\n+#\n+#\tGIT_TRACE2_EVENT=event.log git fetch ... &&\n+#\ttest_subcommand git upload-pack \"$PATH\" <event.log\n+#\n+# If the first parameter passed is !, this instead checks that\n+# the given command was not called.\n+#\n+test_subcommand () {\n+\tlocal negate=\n+\tif test \"$1\" = \"!\"\n+\tthen\n+\t\tnegate=t\n+\t\tshift\n+\tfi\n+\n+\tlocal expr=$(printf '\"%s\",' \"$@\")\n+\texpr=\"${expr%,}\"\n+\n+\tif test -n \"$negate\"\n+\tthen\n+\t\t! grep \"\\[$expr\\]\"\n+\telse\n+\t\tgrep \"\\[$expr\\]\"\n+\tfi\n+}\n-- \ngitgitgadget\n\n"},{"id":"405780","messageId":"7a2a4e1e5278c7c94e0a6cfe1a1917724f826b0e.1600366313.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v5.git.1600366313.gitgitgadget@gmail.com","subject":"[PATCH v5 10/11] maintenance: add auto condition for commit-graph task","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-17T18:11:51Z","receivedAt":"2020-09-17T18:15:09Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nInstead of writing a new commit-graph in every 'git maintenance run\n--auto' process (when maintenance.commit-graph.enalbed is configured to\nbe true), only write when there are \"enough\" commits not in a\ncommit-graph file.\n\nThis count is controlled by the maintenance.commit-graph.auto config\noption.\n\nTo compute the count, use a depth-first search starting at each ref, and\nleaving markers using the SEEN flag. If this count reaches the limit,\nthen terminate early and start the task. Otherwise, this operation will\npeel every ref and parse the commit it points to. If these are all in\nthe commit-graph, then this is typically a very fast operation. Users\nwith many refs might feel a slow-down, and hence could consider updating\ntheir limit to be very small. A negative value will force the step to\nrun every time.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/config/maintenance.txt | 10 ++++\n builtin/gc.c                         | 82 ++++++++++++++++++++++++++++\n object.h                             |  1 +\n 3 files changed, 93 insertions(+)\n\ndiff --git a/Documentation/config/maintenance.txt b/Documentation/config/maintenance.txt\nindex 4402b8b49f..7cc6700d57 100644\n--- a/Documentation/config/maintenance.txt\n+++ b/Documentation/config/maintenance.txt\n@@ -4,3 +4,13 @@ maintenance.<task>.enabled::\n \t`git maintenance run`. These config values are ignored if a\n \t`--task` option exists. By default, only `maintenance.gc.enabled`\n \tis true.\n+\n+maintenance.commit-graph.auto::\n+\tThis integer config option controls how often the `commit-graph` task\n+\tshould be run as part of `git maintenance run --auto`. If zero, then\n+\tthe `commit-graph` task will not run with the `--auto` option. A\n+\tnegative value will force the task to run every time. Otherwise, a\n+\tpositive value implies the command should run when the number of\n+\treachable commits that are not in the commit-graph file is at least\n+\tthe value of `maintenance.commit-graph.auto`. The default value is\n+\t100.\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 13c24bca7d..4db853c561 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -28,6 +28,7 @@\n #include \"blob.h\"\n #include \"tree.h\"\n #include \"promisor-remote.h\"\n+#include \"refs.h\"\n \n #define FAILED_RUN \"failed to run %s\"\n \n@@ -710,6 +711,86 @@ struct maintenance_run_opts {\n \tint quiet;\n };\n \n+/* Remember to update object flag allocation in object.h */\n+#define SEEN\t\t(1u<<0)\n+\n+struct cg_auto_data {\n+\tint num_not_in_graph;\n+\tint limit;\n+};\n+\n+static int dfs_on_ref(const char *refname,\n+\t\t      const struct object_id *oid, int flags,\n+\t\t      void *cb_data)\n+{\n+\tstruct cg_auto_data *data = (struct cg_auto_data *)cb_data;\n+\tint result = 0;\n+\tstruct object_id peeled;\n+\tstruct commit_list *stack = NULL;\n+\tstruct commit *commit;\n+\n+\tif (!peel_ref(refname, &peeled))\n+\t\toid = &peeled;\n+\tif (oid_object_info(the_repository, oid, NULL) != OBJ_COMMIT)\n+\t\treturn 0;\n+\n+\tcommit = lookup_commit(the_repository, oid);\n+\tif (!commit)\n+\t\treturn 0;\n+\tif (parse_commit(commit))\n+\t\treturn 0;\n+\n+\tcommit_list_append(commit, &stack);\n+\n+\twhile (!result && stack) {\n+\t\tstruct commit_list *parent;\n+\n+\t\tcommit = pop_commit(&stack);\n+\n+\t\tfor (parent = commit->parents; parent; parent = parent->next) {\n+\t\t\tif (parse_commit(parent->item) ||\n+\t\t\t    commit_graph_position(parent->item) != COMMIT_NOT_FROM_GRAPH ||\n+\t\t\t    parent->item->object.flags & SEEN)\n+\t\t\t\tcontinue;\n+\n+\t\t\tparent->item->object.flags |= SEEN;\n+\t\t\tdata->num_not_in_graph++;\n+\n+\t\t\tif (data->num_not_in_graph >= data->limit) {\n+\t\t\t\tresult = 1;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tcommit_list_append(parent->item, &stack);\n+\t\t}\n+\t}\n+\n+\tfree_commit_list(stack);\n+\treturn result;\n+}\n+\n+static int should_write_commit_graph(void)\n+{\n+\tint result;\n+\tstruct cg_auto_data data;\n+\n+\tdata.num_not_in_graph = 0;\n+\tdata.limit = 100;\n+\tgit_config_get_int(\"maintenance.commit-graph.auto\",\n+\t\t\t   &data.limit);\n+\n+\tif (!data.limit)\n+\t\treturn 0;\n+\tif (data.limit < 0)\n+\t\treturn 1;\n+\n+\tresult = for_each_ref(dfs_on_ref, &data);\n+\n+\tclear_commit_marks_all(SEEN);\n+\n+\treturn result;\n+}\n+\n static int run_write_commit_graph(struct maintenance_run_opts *opts)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n@@ -790,6 +871,7 @@ static struct maintenance_task tasks[] = {\n \t[TASK_COMMIT_GRAPH] = {\n \t\t\"commit-graph\",\n \t\tmaintenance_task_commit_graph,\n+\t\tshould_write_commit_graph,\n \t},\n };\n \ndiff --git a/object.h b/object.h\nindex 96a2105859..20b18805f0 100644\n--- a/object.h\n+++ b/object.h\n@@ -73,6 +73,7 @@ struct object_array {\n  * sha1-name.c:                                              20\n  * list-objects-filter.c:                                      21\n  * builtin/fsck.c:           0--3\n+ * builtin/gc.c:             0\n  * builtin/index-pack.c:                                     2021\n  * builtin/pack-objects.c:                                   20\n  * builtin/reflog.c:                   10--12\n-- \ngitgitgadget\n\n"},{"id":"405781","messageId":"20a74abd96795d66acc19ea780864cd5f4ea6a2b.1600366313.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v5.git.1600366313.gitgitgadget@gmail.com","subject":"[PATCH v5 11/11] maintenance: add trace2 regions for task execution","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-17T18:11:52Z","receivedAt":"2020-09-17T18:15:31Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/gc.c | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 4db853c561..090959350e 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -927,10 +927,12 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n \t\t     !tasks[i].auto_condition()))\n \t\t\tcontinue;\n \n+\t\ttrace2_region_enter(\"maintenance\", tasks[i].name, r);\n \t\tif (tasks[i].fn(opts)) {\n \t\t\terror(_(\"task '%s' failed\"), tasks[i].name);\n \t\t\tresult = 1;\n \t\t}\n+\t\ttrace2_region_leave(\"maintenance\", tasks[i].name, r);\n \t}\n \n \trollback_lock_file(&lk);\n-- \ngitgitgadget\n"},{"id":"405783","messageId":"3cbdeeafb56d0cddabbcef7d9ac301704e2616e6.1600366313.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v5.git.1600366313.gitgitgadget@gmail.com","subject":"[PATCH v5 03/11] maintenance: replace run_auto_gc()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-17T18:11:44Z","receivedAt":"2020-09-17T18:17:12Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe run_auto_gc() method is used in several places to trigger a check\nfor repo maintenance after some Git commands, such as 'git commit' or\n'git fetch'.\n\nTo allow for extra customization of this maintenance activity, replace\nthe 'git gc --auto [--quiet]' call with one to 'git maintenance run\n--auto [--quiet]'. As we extend the maintenance builtin with other\nsteps, users will be able to select different maintenance activities.\n\nRename run_auto_gc() to run_auto_maintenance() to be clearer what is\nhappening on this call, and to expose all callers in the current diff.\nRewrite the method to use a struct child_process to simplify the calls\nslightly.\n\nSince 'git fetch' already allows disabling the 'git gc --auto'\nsubprocess, add an equivalent option with a different name to be more\ndescriptive of the new behavior: '--[no-]maintenance'. Update the\ndocumentation to include these options at the same time.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/fetch-options.txt |  6 ++++--\n Documentation/git-clone.txt     |  6 +++---\n builtin/am.c                    |  2 +-\n builtin/commit.c                |  2 +-\n builtin/fetch.c                 |  6 ++++--\n builtin/merge.c                 |  2 +-\n builtin/rebase.c                |  4 ++--\n run-command.c                   | 16 +++++++---------\n run-command.h                   |  2 +-\n t/t5510-fetch.sh                |  2 +-\n 10 files changed, 25 insertions(+), 23 deletions(-)\n\ndiff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt\nindex 07d06ff445..b65a758661 100644\n--- a/Documentation/fetch-options.txt\n+++ b/Documentation/fetch-options.txt\n@@ -95,9 +95,11 @@ ifndef::git-pull[]\n \tAllow several <repository> and <group> arguments to be\n \tspecified. No <refspec>s may be specified.\n \n+--[no-]auto-maintenance::\n --[no-]auto-gc::\n-\tRun `git gc --auto` at the end to perform garbage collection\n-\tif needed. This is enabled by default.\n+\tRun `git maintenance run --auto` at the end to perform automatic\n+\trepository maintenance if needed. (`--[no-]auto-gc` is a synonym.)\n+\tThis is enabled by default.\n \n --[no-]write-commit-graph::\n \tWrite a commit-graph after fetching. This overrides the config\ndiff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt\nindex c898310099..097e6a86c5 100644\n--- a/Documentation/git-clone.txt\n+++ b/Documentation/git-clone.txt\n@@ -78,9 +78,9 @@ repository using this option and then delete branches (or use any\n other Git command that makes any existing commit unreferenced) in the\n source repository, some objects may become unreferenced (or dangling).\n These objects may be removed by normal Git operations (such as `git commit`)\n-which automatically call `git gc --auto`. (See linkgit:git-gc[1].)\n-If these objects are removed and were referenced by the cloned repository,\n-then the cloned repository will become corrupt.\n+which automatically call `git maintenance run --auto`. (See\n+linkgit:git-maintenance[1].) If these objects are removed and were referenced\n+by the cloned repository, then the cloned repository will become corrupt.\n +\n Note that running `git repack` without the `--local` option in a repository\n cloned with `--shared` will copy objects from the source repository into a pack\ndiff --git a/builtin/am.c b/builtin/am.c\nindex dd4e6c2d9b..68e9d17379 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1795,7 +1795,7 @@ static void am_run(struct am_state *state, int resume)\n \tif (!state->rebasing) {\n \t\tam_destroy(state);\n \t\tclose_object_store(the_repository->objects);\n-\t\trun_auto_gc(state->quiet);\n+\t\trun_auto_maintenance(state->quiet);\n \t}\n }\n \ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 69ac78d5e5..f9b0a0c05d 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1702,7 +1702,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \tgit_test_write_commit_graph_or_die();\n \n \trepo_rerere(the_repository, 0);\n-\trun_auto_gc(quiet);\n+\trun_auto_maintenance(quiet);\n \trun_commit_hook(use_editor, get_index_file(), \"post-commit\", NULL);\n \tif (amend && !no_post_rewrite) {\n \t\tcommit_post_rewrite(the_repository, current_head, &oid);\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 2eb8d6a5a5..cb38e6f5ec 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -199,8 +199,10 @@ static struct option builtin_fetch_options[] = {\n \tOPT_STRING_LIST(0, \"negotiation-tip\", &negotiation_tip, N_(\"revision\"),\n \t\t\tN_(\"report that we have only objects reachable from this object\")),\n \tOPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),\n+\tOPT_BOOL(0, \"auto-maintenance\", &enable_auto_gc,\n+\t\t N_(\"run 'maintenance --auto' after fetching\")),\n \tOPT_BOOL(0, \"auto-gc\", &enable_auto_gc,\n-\t\t N_(\"run 'gc --auto' after fetching\")),\n+\t\t N_(\"run 'maintenance --auto' after fetching\")),\n \tOPT_BOOL(0, \"show-forced-updates\", &fetch_show_forced_updates,\n \t\t N_(\"check for forced-updates on all updated branches\")),\n \tOPT_BOOL(0, \"write-commit-graph\", &fetch_write_commit_graph,\n@@ -1891,7 +1893,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)\n \tclose_object_store(the_repository->objects);\n \n \tif (enable_auto_gc)\n-\t\trun_auto_gc(verbosity < 0);\n+\t\trun_auto_maintenance(verbosity < 0);\n \n \treturn result;\n }\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 74829a838e..8f31bfab56 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -456,7 +456,7 @@ static void finish(struct commit *head_commit,\n \t\t\t * user should see them.\n \t\t\t */\n \t\t\tclose_object_store(the_repository->objects);\n-\t\t\trun_auto_gc(verbosity < 0);\n+\t\t\trun_auto_maintenance(verbosity < 0);\n \t\t}\n \t}\n \tif (new_head && show_diffstat) {\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex dadb52fa92..9ffba9009d 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -728,10 +728,10 @@ static int finish_rebase(struct rebase_options *opts)\n \tapply_autostash(state_dir_path(\"autostash\", opts));\n \tclose_object_store(the_repository->objects);\n \t/*\n-\t * We ignore errors in 'gc --auto', since the\n+\t * We ignore errors in 'git maintenance run --auto', since the\n \t * user should see them.\n \t */\n-\trun_auto_gc(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE)));\n+\trun_auto_maintenance(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE)));\n \tif (opts->type == REBASE_MERGE) {\n \t\tstruct replay_opts replay = REPLAY_OPTS_INIT;\n \ndiff --git a/run-command.c b/run-command.c\nindex cc9c3296ba..2ee59acdc8 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -1866,15 +1866,13 @@ int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,\n \treturn result;\n }\n \n-int run_auto_gc(int quiet)\n+int run_auto_maintenance(int quiet)\n {\n-\tstruct strvec argv_gc_auto = STRVEC_INIT;\n-\tint status;\n+\tstruct child_process maint = CHILD_PROCESS_INIT;\n \n-\tstrvec_pushl(&argv_gc_auto, \"gc\", \"--auto\", NULL);\n-\tif (quiet)\n-\t\tstrvec_push(&argv_gc_auto, \"--quiet\");\n-\tstatus = run_command_v_opt(argv_gc_auto.v, RUN_GIT_CMD);\n-\tstrvec_clear(&argv_gc_auto);\n-\treturn status;\n+\tmaint.git_cmd = 1;\n+\tstrvec_pushl(&maint.args, \"maintenance\", \"run\", \"--auto\", NULL);\n+\tstrvec_push(&maint.args, quiet ? \"--quiet\" : \"--no-quiet\");\n+\n+\treturn run_command(&maint);\n }\ndiff --git a/run-command.h b/run-command.h\nindex 8b9bfaef16..6472b38bde 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -221,7 +221,7 @@ int run_hook_ve(const char *const *env, const char *name, va_list args);\n /*\n  * Trigger an auto-gc\n  */\n-int run_auto_gc(int quiet);\n+int run_auto_maintenance(int quiet);\n \n #define RUN_COMMAND_NO_STDIN 1\n #define RUN_GIT_CMD\t     2\t/*If this is to be git sub-command */\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 2a1abe91f0..a9682c5669 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -934,7 +934,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '\n \t\tgit config fetch.unpackLimit 1 &&\n \t\tgit config gc.autoPackLimit 1 &&\n \t\tgit config gc.autoDetach false &&\n-\t\tGIT_ASK_YESNO=\"$D/askyesno\" git fetch >fetch.out 2>&1 &&\n+\t\tGIT_ASK_YESNO=\"$D/askyesno\" git fetch --verbose >fetch.out 2>&1 &&\n \t\ttest_i18ngrep \"Auto packing the repository\" fetch.out &&\n \t\t! grep \"Should I try again\" fetch.out\n \t)\n-- \ngitgitgadget\n\n"},{"id":"405784","messageId":"713207b4a188e16e952b8587f062488f97d403f8.1600366313.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v5.git.1600366313.gitgitgadget@gmail.com","subject":"[PATCH v5 08/11] maintenance: create maintenance.<task>.enabled config","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-17T18:11:49Z","receivedAt":"2020-09-17T18:18:01Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nCurrently, a normal run of \"git maintenance run\" will only run the 'gc'\ntask, as it is the only one enabled. This is mostly for backwards-\ncompatible reasons since \"git maintenance run --auto\" commands replaced\nprevious \"git gc --auto\" commands after some Git processes. Users could\nmanually run specific maintenance tasks by calling \"git maintenance run\n--task=<task>\" directly.\n\nAllow users to customize which steps are run automatically using config.\nThe 'maintenance.<task>.enabled' option then can turn on these other\ntasks (or turn off the 'gc' task).\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/config.txt             |  2 ++\n Documentation/config/maintenance.txt |  6 ++++++\n Documentation/git-maintenance.txt    | 14 +++++++++-----\n builtin/gc.c                         | 19 +++++++++++++++++++\n t/t7900-maintenance.sh               |  8 ++++++++\n 5 files changed, 44 insertions(+), 5 deletions(-)\n create mode 100644 Documentation/config/maintenance.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 3042d80978..f93b6837e4 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -398,6 +398,8 @@ include::config/mailinfo.txt[]\n \n include::config/mailmap.txt[]\n \n+include::config/maintenance.txt[]\n+\n include::config/man.txt[]\n \n include::config/merge.txt[]\ndiff --git a/Documentation/config/maintenance.txt b/Documentation/config/maintenance.txt\nnew file mode 100644\nindex 0000000000..4402b8b49f\n--- /dev/null\n+++ b/Documentation/config/maintenance.txt\n@@ -0,0 +1,6 @@\n+maintenance.<task>.enabled::\n+\tThis boolean config option controls whether the maintenance task\n+\twith name `<task>` is run when no `--task` option is specified to\n+\t`git maintenance run`. These config values are ignored if a\n+\t`--task` option exists. By default, only `maintenance.gc.enabled`\n+\tis true.\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nindex 819ca41ab6..6abcb8255a 100644\n--- a/Documentation/git-maintenance.txt\n+++ b/Documentation/git-maintenance.txt\n@@ -30,9 +30,11 @@ SUBCOMMANDS\n -----------\n \n run::\n-\tRun one or more maintenance tasks. If one or more `--task=<task>`\n-\toptions are specified, then those tasks are run in the provided\n-\torder. Otherwise, only the `gc` task is run.\n+\tRun one or more maintenance tasks. If one or more `--task` options\n+\tare specified, then those tasks are run in that order. Otherwise,\n+\tthe tasks are determined by which `maintenance.<task>.enabled`\n+\tconfig options are true. By default, only `maintenance.gc.enabled`\n+\tis true.\n \n TASKS\n -----\n@@ -67,8 +69,10 @@ OPTIONS\n \n --task=<task>::\n \tIf this option is specified one or more times, then only run the\n-\tspecified tasks in the specified order. See the 'TASKS' section\n-\tfor the list of accepted `<task>` values.\n+\tspecified tasks in the specified order. If no `--task=<task>`\n+\targuments are specified, then only the tasks with\n+\t`maintenance.<task>.enabled` configured as `true` are considered.\n+\tSee the 'TASKS' section for the list of accepted `<task>` values.\n \n GIT\n ---\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 7ba9c6f7c9..55a3d836f0 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -841,6 +841,24 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n \treturn result;\n }\n \n+static void initialize_task_config(void)\n+{\n+\tint i;\n+\tstruct strbuf config_name = STRBUF_INIT;\n+\tfor (i = 0; i < TASK__COUNT; i++) {\n+\t\tint config_value;\n+\n+\t\tstrbuf_setlen(&config_name, 0);\n+\t\tstrbuf_addf(&config_name, \"maintenance.%s.enabled\",\n+\t\t\t    tasks[i].name);\n+\n+\t\tif (!git_config_get_bool(config_name.buf, &config_value))\n+\t\t\ttasks[i].enabled = config_value;\n+\t}\n+\n+\tstrbuf_release(&config_name);\n+}\n+\n static int task_option_parse(const struct option *opt,\n \t\t\t     const char *arg, int unset)\n {\n@@ -889,6 +907,7 @@ static int maintenance_run(int argc, const char **argv, const char *prefix)\n \tmemset(&opts, 0, sizeof(opts));\n \n \topts.quiet = !isatty(2);\n+\tinitialize_task_config();\n \n \tfor (i = 0; i < TASK__COUNT; i++)\n \t\ttasks[i].selected_order = -1;\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex fb4cadd30c..8a162a18ba 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -27,6 +27,14 @@ test_expect_success 'run [--auto|--quiet]' '\n \ttest_subcommand git gc --no-quiet <run-no-quiet.txt\n '\n \n+test_expect_success 'maintenance.<task>.enabled' '\n+\tgit config maintenance.gc.enabled false &&\n+\tgit config maintenance.commit-graph.enabled true &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-config.txt\" git maintenance run 2>err &&\n+\ttest_subcommand ! git gc --quiet <run-config.txt &&\n+\ttest_subcommand git commit-graph write --split --reachable --no-progress <run-config.txt\n+'\n+\n test_expect_success 'run --task=<task>' '\n \tGIT_TRACE2_EVENT=\"$(pwd)/run-commit-graph.txt\" \\\n \t\tgit maintenance run --task=commit-graph 2>/dev/null &&\n-- \ngitgitgadget\n\n"},{"id":"405785","messageId":"d424cda058470e7419e587c9551f7eaef4a6a12b.1600366313.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v5.git.1600366313.gitgitgadget@gmail.com","subject":"[PATCH v5 09/11] maintenance: use pointers to check --auto","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-17T18:11:50Z","receivedAt":"2020-09-17T18:18:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nThe 'git maintenance run' command has an '--auto' option. This is used\nby other Git commands such as 'git commit' or 'git fetch' to check if\nmaintenance should be run after adding data to the repository.\n\nPreviously, this --auto option was only used to add the argument to the\n'git gc' command as part of the 'gc' task. We will be expanding the\nother tasks to perform a check to see if they should do work as part of\nthe --auto flag, when they are enabled by config.\n\nFirst, update the 'gc' task to perform the auto check inside the\nmaintenance process. This prevents running an extra 'git gc --auto'\ncommand when not needed. It also shows a model for other tasks.\n\nSecond, use the 'auto_condition' function pointer as a signal for\nwhether we enable the maintenance task under '--auto'. For instance, we\ndo not want to enable the 'fetch' task in '--auto' mode, so that\nfunction pointer will remain NULL.\n\nNow that we are not automatically calling 'git gc', a test in\nt5514-fetch-multiple.sh must be changed to watch for 'git maintenance'\ninstead.\n\nWe continue to pass the '--auto' option to the 'git gc' command when\nnecessary, because of the gc.autoDetach config option changes behavior.\nLikely, we will want to absorb the daemonizing behavior implied by\ngc.autoDetach as a maintenance.autoDetach config option.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/gc.c              | 16 ++++++++++++++++\n t/t5514-fetch-multiple.sh |  2 +-\n t/t7900-maintenance.sh    |  2 +-\n 3 files changed, 18 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 55a3d836f0..13c24bca7d 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -755,9 +755,17 @@ static int maintenance_task_gc(struct maintenance_run_opts *opts)\n \n typedef int maintenance_task_fn(struct maintenance_run_opts *opts);\n \n+/*\n+ * An auto condition function returns 1 if the task should run\n+ * and 0 if the task should NOT run. See needs_to_gc() for an\n+ * example.\n+ */\n+typedef int maintenance_auto_fn(void);\n+\n struct maintenance_task {\n \tconst char *name;\n \tmaintenance_task_fn *fn;\n+\tmaintenance_auto_fn *auto_condition;\n \tunsigned enabled:1;\n \n \t/* -1 if not selected. */\n@@ -776,6 +784,7 @@ static struct maintenance_task tasks[] = {\n \t[TASK_GC] = {\n \t\t\"gc\",\n \t\tmaintenance_task_gc,\n+\t\tneed_to_gc,\n \t\t1,\n \t},\n \t[TASK_COMMIT_GRAPH] = {\n@@ -831,6 +840,11 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n \t\tif (!found_selected && !tasks[i].enabled)\n \t\t\tcontinue;\n \n+\t\tif (opts->auto_flag &&\n+\t\t    (!tasks[i].auto_condition ||\n+\t\t     !tasks[i].auto_condition()))\n+\t\t\tcontinue;\n+\n \t\tif (tasks[i].fn(opts)) {\n \t\t\terror(_(\"task '%s' failed\"), tasks[i].name);\n \t\t\tresult = 1;\n@@ -845,6 +859,8 @@ static void initialize_task_config(void)\n {\n \tint i;\n \tstruct strbuf config_name = STRBUF_INIT;\n+\tgc_config();\n+\n \tfor (i = 0; i < TASK__COUNT; i++) {\n \t\tint config_value;\n \ndiff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh\nindex de8e2f1531..bd202ec6f3 100755\n--- a/t/t5514-fetch-multiple.sh\n+++ b/t/t5514-fetch-multiple.sh\n@@ -108,7 +108,7 @@ test_expect_success 'git fetch --multiple (two remotes)' '\n \t GIT_TRACE=1 git fetch --multiple one two 2>trace &&\n \t git branch -r > output &&\n \t test_cmp ../expect output &&\n-\t grep \"built-in: git gc\" trace >gc &&\n+\t grep \"built-in: git maintenance\" trace >gc &&\n \t test_line_count = 1 gc\n \t)\n '\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex 8a162a18ba..53c883531e 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -23,7 +23,7 @@ test_expect_success 'run [--auto|--quiet]' '\n \tGIT_TRACE2_EVENT=\"$(pwd)/run-no-quiet.txt\" \\\n \t\tgit maintenance run --no-quiet 2>/dev/null &&\n \ttest_subcommand git gc --quiet <run-no-auto.txt &&\n-\ttest_subcommand git gc --auto --quiet <run-auto.txt &&\n+\ttest_subcommand ! git gc --auto --quiet <run-auto.txt &&\n \ttest_subcommand git gc --no-quiet <run-no-quiet.txt\n '\n \n-- \ngitgitgadget\n\n"},{"id":"405786","messageId":"1a0a3eebb825ac3eabfdd86f82ed7ef6abb454c5.1600366313.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v5.git.1600366313.gitgitgadget@gmail.com","subject":"[PATCH v5 07/11] maintenance: take a lock on the objects directory","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-17T18:11:48Z","receivedAt":"2020-09-17T18:18:59Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nPerforming maintenance on a Git repository involves writing data to the\n.git directory, which is not safe to do with multiple writers attempting\nthe same operation. Ensure that only one 'git maintenance' process is\nrunning at a time by holding a file-based lock. Simply the presence of\nthe .git/maintenance.lock file will prevent future maintenance. This\nlock is never committed, since it does not represent meaningful data.\nInstead, it is only a placeholder.\n\nIf the lock file already exists, then no maintenance tasks are\nattempted. This will become very important later when we implement the\n'prefetch' task, as this is our stop-gap from creating a recursive process\nloop between 'git fetch' and 'git maintenance run --auto'.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/gc.c | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 00fff59bdb..7ba9c6f7c9 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -798,6 +798,25 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n {\n \tint i, found_selected = 0;\n \tint result = 0;\n+\tstruct lock_file lk;\n+\tstruct repository *r = the_repository;\n+\tchar *lock_path = xstrfmt(\"%s/maintenance\", r->objects->odb->path);\n+\n+\tif (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {\n+\t\t/*\n+\t\t * Another maintenance command is running.\n+\t\t *\n+\t\t * If --auto was provided, then it is likely due to a\n+\t\t * recursive process stack. Do not report an error in\n+\t\t * that case.\n+\t\t */\n+\t\tif (!opts->auto_flag && !opts->quiet)\n+\t\t\twarning(_(\"lock file '%s' exists, skipping maintenance\"),\n+\t\t\t\tlock_path);\n+\t\tfree(lock_path);\n+\t\treturn 0;\n+\t}\n+\tfree(lock_path);\n \n \tfor (i = 0; !found_selected && i < TASK__COUNT; i++)\n \t\tfound_selected = tasks[i].selected_order >= 0;\n@@ -818,6 +837,7 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n \t\t}\n \t}\n \n+\trollback_lock_file(&lk);\n \treturn result;\n }\n \n-- \ngitgitgadget\n\n"},{"id":"405787","messageId":"52eb937f49a79dfd985e9c6d237ec022b6290094.1600366313.git.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v5.git.1600366313.gitgitgadget@gmail.com","subject":"[PATCH v5 02/11] maintenance: add --quiet option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-17T18:11:43Z","receivedAt":"2020-09-17T18:19:23Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nMaintenance activities are commonly used as steps in larger scripts.\nProviding a '--quiet' option allows those scripts to be less noisy when\nrun on a terminal window. Turn this mode on by default when stderr is\nnot a terminal.\n\nPipe the option to the 'git gc' child process.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/git-maintenance.txt |  3 +++\n builtin/gc.c                      | 11 ++++++++++-\n t/t7900-maintenance.sh            | 15 ++++++++++-----\n 3 files changed, 23 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt\nindex ff47fb3641..04fa0fe329 100644\n--- a/Documentation/git-maintenance.txt\n+++ b/Documentation/git-maintenance.txt\n@@ -52,6 +52,9 @@ OPTIONS\n \tin the `gc.auto` config setting, or when the number of pack-files\n \texceeds the `gc.autoPackLimit` config setting.\n \n+--quiet::\n+\tDo not report progress or other information over `stderr`.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex ec064e8686..bacce0c747 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -701,12 +701,13 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n }\n \n static const char * const builtin_maintenance_run_usage[] = {\n-\tN_(\"git maintenance run [--auto]\"),\n+\tN_(\"git maintenance run [--auto] [--[no-]quiet]\"),\n \tNULL\n };\n \n struct maintenance_run_opts {\n \tint auto_flag;\n+\tint quiet;\n };\n \n static int maintenance_task_gc(struct maintenance_run_opts *opts)\n@@ -718,6 +719,10 @@ static int maintenance_task_gc(struct maintenance_run_opts *opts)\n \n \tif (opts->auto_flag)\n \t\tstrvec_push(&child.args, \"--auto\");\n+\tif (opts->quiet)\n+\t\tstrvec_push(&child.args, \"--quiet\");\n+\telse\n+\t\tstrvec_push(&child.args, \"--no-quiet\");\n \n \tclose_object_store(the_repository->objects);\n \treturn run_command(&child);\n@@ -729,10 +734,14 @@ static int maintenance_run(int argc, const char **argv, const char *prefix)\n \tstruct option builtin_maintenance_run_options[] = {\n \t\tOPT_BOOL(0, \"auto\", &opts.auto_flag,\n \t\t\t N_(\"run tasks based on the state of the repository\")),\n+\t\tOPT_BOOL(0, \"quiet\", &opts.quiet,\n+\t\t\t N_(\"do not report progress or other information over stderr\")),\n \t\tOPT_END()\n \t};\n \tmemset(&opts, 0, sizeof(opts));\n \n+\topts.quiet = !isatty(2);\n+\n \targc = parse_options(argc, argv, prefix,\n \t\t\t     builtin_maintenance_run_options,\n \t\t\t     builtin_maintenance_run_usage,\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex c2f0b1d0c0..5637053bf6 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -13,11 +13,16 @@ test_expect_success 'help text' '\n \ttest_i18ngrep \"usage: git maintenance\" err\n '\n \n-test_expect_success 'run [--auto]' '\n-\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-auto.txt\" git maintenance run &&\n-\tGIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" git maintenance run --auto &&\n-\ttest_subcommand git gc <run-no-auto.txt &&\n-\ttest_subcommand git gc --auto <run-auto.txt\n+test_expect_success 'run [--auto|--quiet]' '\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-auto.txt\" \\\n+\t\tgit maintenance run 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-auto.txt\" \\\n+\t\tgit maintenance run --auto 2>/dev/null &&\n+\tGIT_TRACE2_EVENT=\"$(pwd)/run-no-quiet.txt\" \\\n+\t\tgit maintenance run --no-quiet 2>/dev/null &&\n+\ttest_subcommand git gc --quiet <run-no-auto.txt &&\n+\ttest_subcommand git gc --auto --quiet <run-auto.txt &&\n+\ttest_subcommand git gc --no-quiet <run-no-quiet.txt\n '\n \n test_done\n-- \ngitgitgadget\n\n"},{"id":"405788","messageId":"pull.695.v5.git.1600366313.gitgitgadget@gmail.com","threadId":"53997","inReplyTo":"pull.695.v4.git.1599224956.gitgitgadget@gmail.com","subject":"[PATCH v5 00/11] Maintenance I: Command, gc and commit-graph tasks","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-09-17T18:11:41Z","receivedAt":"2020-09-17T18:20:04Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"This series is based on jc/no-update-fetch-head.\n\nThis patch series contains 11patches that were going to be part of v4 of\nds/maintenance [1], but the discussion has gotten really long. To help focus\nthe conversation, I'm splitting out the portions that create and test the\n'maintenance' builtin from the additional tasks (prefetch, loose-objects,\nincremental-repack) that can be brought in later.\n\n[1] \nhttps://lore.kernel.org/git/pull.671.git.1594131695.gitgitgadget@gmail.com/\n\nAs mentioned before, git gc already plays the role of maintaining Git\nrepositories. It has accumulated several smaller pieces in its long history,\nincluding:\n\n 1. Repacking all reachable objects into one pack-file (and deleting\n    unreachable objects).\n 2. Packing refs.\n 3. Expiring reflogs.\n 4. Clearing rerere logs.\n 5. Updating the commit-graph file.\n 6. Pruning worktrees.\n\nWhile expiring reflogs, clearing rererelogs, and deleting unreachable\nobjects are suitable under the guise of \"garbage collection\", packing refs\nand updating the commit-graph file are not as obviously fitting. Further,\nthese operations are \"all or nothing\" in that they rewrite almost all\nrepository data, which does not perform well at extremely large scales.\nThese operations can also be disruptive to foreground Git commands when git\ngc --auto triggers during routine use.\n\nThis series does not intend to change what git gc does, but instead create\nnew choices for automatic maintenance activities, of which git gc remains\nthe only one enabled by default.\n\nThe new maintenance tasks are:\n\n * 'commit-graph' : write and verify a single layer of an incremental\n   commit-graph.\n * 'loose-objects' : prune packed loose objects, then create a new pack from\n   a batch of loose objects.\n * 'pack-files' : expire redundant packs from the multi-pack-index, then\n   repack using the multi-pack-index's incremental repack strategy.\n * 'prefetch' : fetch from each remote, storing the refs in 'refs/prefetch/\n   /'.\n\nThe only included tasks are the 'gc' and 'commit-graph' tasks. The rest will\nfollow in a follow-up series. Including the 'commit-graph' task here allows\nus to build and test features like config settings and the --task= \ncommand-line argument.\n\nThese tasks are all disabled by default, but can be enabled with config\noptions or run explicitly using \"git maintenance run --task=\". There are\nadditional config options to allow customizing the conditions for which the\ntasks run during the '--auto' option.\n\n Because 'gc' is implemented as a maintenance task, the most dramatic change\nof this series is to convert the 'git gc --auto' calls into 'git maintenance\nrun --auto' calls at the end of some Git commands. By default, the only\nchange is that 'git gc --auto' will be run below an additional 'git\nmaintenance' process.\n\nThe 'git maintenance' builtin has a 'run' subcommand so it can be extended\nlater with subcommands that manage background maintenance, such as 'start'\nor 'stop'. These are not the subject of this series, as it is important to\nfocus on the maintenance activities themselves.\n\nUpdate in v4\n============\n\nA segfault when running just \"git maintenance\" is fixed.\n\nUpdates since v3\n================\n\n * Two commit-message typos are fixed.\n\nThanks for all of the review!\n\nUpdates since v2\n================\n\n * Based on jc/no-update-fetch-head instead of jk/strvec.\n   \n   \n * I realized that the other maintenance subcommands should not accept the\n   options for the 'run' subcommand, so I reorganized the option parsing\n   into that subcommand. This makes the range-diff noisier than it would\n   have been otherwise.\n   \n   \n * While updating the parsing, I also updated the usage strings.\n   \n   \n * The \"verify, then delete and rewrite on failure\" logic is removed. I'll\n   consider bringing this back with a way to test the behavior in a later\n   patch series.\n   \n   \n * Other feedback from Jonathan Tan is applied.\n   \n   \n\nUpdates since v1 (of this series)\n=================================\n\n * Documentation fixes.\n   \n   \n * The builtin code had some slight tweaks in PATCH 1.\n   \n   \n\nUPDATES since v3 of [1]\n=======================\n\n * The biggest change here is the use of \"test_subcommand\", based on\n   Jonathan Nieder's approach. This requires having the exact command-line\n   figured out, which now requires spelling out all --no- [quiet%7Cprogress] \n   options. I also added a bunch of \"2>/dev/null\" checks because of the\n   isatty(2) calls. Without that, the behavior will change depending on\n   whether the test is run with -x/-v or without.\n   \n   \n * The option parsing has changed to use a local struct and pass that struct\n   to the helper methods. This is instead of having a global singleton.\n   \n   \n\nThanks, -Stolee\n\nCc: sandals@crustytoothpaste.net [sandals@crustytoothpaste.net], \nsteadmon@google.com [steadmon@google.com], jrnieder@gmail.com\n[jrnieder@gmail.com], peff@peff.net [peff@peff.net], congdanhqx@gmail.com\n[congdanhqx@gmail.com], phillip.wood123@gmail.com\n[phillip.wood123@gmail.com], emilyshaffer@google.com\n[emilyshaffer@google.com], sluongng@gmail.com [sluongng@gmail.com], \njonathantanmy@google.com [jonathantanmy@google.com]\n\nDerrick Stolee (11):\n  maintenance: create basic maintenance runner\n  maintenance: add --quiet option\n  maintenance: replace run_auto_gc()\n  maintenance: initialize task array\n  maintenance: add commit-graph task\n  maintenance: add --task option\n  maintenance: take a lock on the objects directory\n  maintenance: create maintenance.<task>.enabled config\n  maintenance: use pointers to check --auto\n  maintenance: add auto condition for commit-graph task\n  maintenance: add trace2 regions for task execution\n\n .gitignore                           |   1 +\n Documentation/config.txt             |   2 +\n Documentation/config/maintenance.txt |  16 ++\n Documentation/fetch-options.txt      |   6 +-\n Documentation/git-clone.txt          |   6 +-\n Documentation/git-maintenance.txt    |  79 +++++++\n builtin.h                            |   1 +\n builtin/am.c                         |   2 +-\n builtin/commit.c                     |   2 +-\n builtin/fetch.c                      |   6 +-\n builtin/gc.c                         | 337 +++++++++++++++++++++++++++\n builtin/merge.c                      |   2 +-\n builtin/rebase.c                     |   4 +-\n command-list.txt                     |   1 +\n commit-graph.c                       |   8 +-\n commit-graph.h                       |   1 +\n git.c                                |   1 +\n object.h                             |   1 +\n run-command.c                        |  16 +-\n run-command.h                        |   2 +-\n t/t5510-fetch.sh                     |   2 +-\n t/t5514-fetch-multiple.sh            |   2 +-\n t/t7900-maintenance.sh               |  65 ++++++\n t/test-lib-functions.sh              |  33 +++\n 24 files changed, 568 insertions(+), 28 deletions(-)\n create mode 100644 Documentation/config/maintenance.txt\n create mode 100644 Documentation/git-maintenance.txt\n create mode 100755 t/t7900-maintenance.sh\n\n\nbase-commit: 887952b8c680626f4721cb5fa57704478801aca4\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-695%2Fderrickstolee%2Fmaintenance%2Fbuiltin-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-695/derrickstolee/maintenance/builtin-v5\nPull-Request: https://github.com/gitgitgadget/git/pull/695\n\nRange-diff vs v4:\n\n  1:  aa961af387 !  1:  00a0d63508 maintenance: create basic maintenance runner\n     @@ Commit message\n      \n          Helped-by: Jonathan Nieder <jrnieder@gmail.com>\n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n     +    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n      \n       ## .gitignore ##\n      @@\n     @@ builtin/gc.c: int cmd_gc(int argc, const char **argv, const char *prefix)\n      +\n      +int cmd_maintenance(int argc, const char **argv, const char *prefix)\n      +{\n     -+\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n     ++\tif (argc < 2 ||\n     ++\t    (argc == 2 && !strcmp(argv[1], \"-h\")))\n      +\t\tusage(builtin_maintenance_usage);\n      +\n      +\tif (!strcmp(argv[1], \"run\"))\n     @@ t/t7900-maintenance.sh (new)\n      +\ttest_expect_code 129 git maintenance -h 2>err &&\n      +\ttest_i18ngrep \"usage: git maintenance run\" err &&\n      +\ttest_expect_code 128 git maintenance barf 2>err &&\n     -+\ttest_i18ngrep \"invalid subcommand: barf\" err\n     ++\ttest_i18ngrep \"invalid subcommand: barf\" err &&\n     ++\ttest_expect_code 129 git maintenance 2>err &&\n     ++\ttest_i18ngrep \"usage: git maintenance\" err\n      +'\n      +\n      +test_expect_success 'run [--auto]' '\n  2:  5386d8a628 !  2:  52eb937f49 maintenance: add --quiet option\n     @@ Commit message\n          Pipe the option to the 'git gc' child process.\n      \n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n     +    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n      \n       ## Documentation/git-maintenance.txt ##\n      @@ Documentation/git-maintenance.txt: OPTIONS\n     @@ builtin/gc.c: static int maintenance_run(int argc, const char **argv, const char\n      \n       ## t/t7900-maintenance.sh ##\n      @@ t/t7900-maintenance.sh: test_expect_success 'help text' '\n     - \ttest_i18ngrep \"invalid subcommand: barf\" err\n     + \ttest_i18ngrep \"usage: git maintenance\" err\n       '\n       \n      -test_expect_success 'run [--auto]' '\n  3:  e28b332df4 !  3:  3cbdeeafb5 maintenance: replace run_auto_gc()\n     @@ Commit message\n          documentation to include these options at the same time.\n      \n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n     +    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n      \n       ## Documentation/fetch-options.txt ##\n      @@ Documentation/fetch-options.txt: ifndef::git-pull[]\n  4:  82326c1c38 !  4:  1d2f2731bd maintenance: initialize task array\n     @@ Commit message\n          Helped-by: Taylor Blau <me@ttaylorr.com>\n          Helped-by: Junio C Hamano <gitster@pobox.com>\n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n     +    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n      \n       ## builtin/gc.c ##\n      @@ builtin/gc.c: static int maintenance_task_gc(struct maintenance_run_opts *opts)\n  5:  06984a42bf !  5:  8204ebbf83 maintenance: add commit-graph task\n     @@ Commit message\n          argument when writing the commit-graph.\n      \n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n     +    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n      \n       ## Documentation/git-maintenance.txt ##\n      @@ Documentation/git-maintenance.txt: run::\n  6:  69298aee24 !  6:  91b8555c9e maintenance: add --task option\n     @@ Commit message\n          member should be ignored if --task=<task> appears.\n      \n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n     +    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n      \n       ## Documentation/git-maintenance.txt ##\n      @@ Documentation/git-maintenance.txt: SUBCOMMANDS\n  7:  c57998a1c8 !  7:  1a0a3eebb8 maintenance: take a lock on the objects directory\n     @@ Commit message\n          loop between 'git fetch' and 'git maintenance run --auto'.\n      \n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n     +    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n      \n       ## builtin/gc.c ##\n      @@ builtin/gc.c: static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n  8:  dc2a092366 !  8:  713207b4a1 maintenance: create maintenance.<task>.enabled config\n     @@ Commit message\n          tasks (or turn off the 'gc' task).\n      \n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n     +    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n      \n       ## Documentation/config.txt ##\n      @@ Documentation/config.txt: include::config/mailinfo.txt[]\n  9:  f5f1e85b03 !  9:  d424cda058 maintenance: use pointers to check --auto\n     @@ Commit message\n          gc.autoDetach as a maintenance.autoDetach config option.\n      \n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n     +    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n      \n       ## builtin/gc.c ##\n      @@ builtin/gc.c: static int maintenance_task_gc(struct maintenance_run_opts *opts)\n 10:  8f84b03c46 ! 10:  7a2a4e1e52 maintenance: add auto condition for commit-graph task\n     @@ Commit message\n          run every time.\n      \n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n     +    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n      \n       ## Documentation/config/maintenance.txt ##\n      @@ Documentation/config/maintenance.txt: maintenance.<task>.enabled::\n 11:  2d6414d89b ! 11:  20a74abd96 maintenance: add trace2 regions for task execution\n     @@ Commit message\n          maintenance: add trace2 regions for task execution\n      \n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n     +    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n      \n       ## builtin/gc.c ##\n      @@ builtin/gc.c: static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n\n-- \ngitgitgadget\n"},{"id":"405791","messageId":"xmqqtuvwqmjg.fsf@gitster.c.googlers.com","threadId":"53997","inReplyTo":"pull.695.v5.git.1600366313.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 00/11] Maintenance I: Command, gc and commit-graph tasks","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-09-17T18:35:15Z","receivedAt":"2020-09-17T18:35:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Update in v4\n> ============\n>\n> A segfault when running just \"git maintenance\" is fixed.\n\nThe net change is just a single liner below, and is obviously\ncorrect.\n\nI propagated it through to part #2 and part #3 locally, and\nhopefully this makes part #1 ready to go.\n\nThanks.\n\n\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex c3bcdc1167..090959350e 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -1027,7 +1027,8 @@ static const char builtin_maintenance_usage[] = N_(\"git maintenance run [<option\n \n int cmd_maintenance(int argc, const char **argv, const char *prefix)\n {\n-\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n+\tif (argc < 2 ||\n+\t    (argc == 2 && !strcmp(argv[1], \"-h\")))\n \t\tusage(builtin_maintenance_usage);\n \n \tif (!strcmp(argv[1], \"run\"))\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex 4f6a04ddb1..53c883531e 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -10,7 +10,9 @@ test_expect_success 'help text' '\n \ttest_expect_code 129 git maintenance -h 2>err &&\n \ttest_i18ngrep \"usage: git maintenance run\" err &&\n \ttest_expect_code 128 git maintenance barf 2>err &&\n-\ttest_i18ngrep \"invalid subcommand: barf\" err\n+\ttest_i18ngrep \"invalid subcommand: barf\" err &&\n+\ttest_expect_code 129 git maintenance 2>err &&\n+\ttest_i18ngrep \"usage: git maintenance\" err\n '\n \n test_expect_success 'run [--auto|--quiet]' '\n"},{"id":"405855","messageId":"nycvar.QRO.7.76.6.2009181514000.5061@tvgsbejvaqbjf.bet","threadId":"53997","inReplyTo":"xmqqtuvwqmjg.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 00/11] Maintenance I: Command, gc and commit-graph tasks","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2020-09-18T13:14:34Z","receivedAt":"2020-09-18T13:19:49Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Thu, 17 Sep 2020, Junio C Hamano wrote:\n\n> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > Update in v4\n> > ============\n> >\n> > A segfault when running just \"git maintenance\" is fixed.\n>\n> The net change is just a single liner below, and is obviously\n> correct.\n>\n> I propagated it through to part #2 and part #3 locally, and\n> hopefully this makes part #1 ready to go.\n\nYay!\n\n> diff --git a/builtin/gc.c b/builtin/gc.c\n> index c3bcdc1167..090959350e 100644\n> --- a/builtin/gc.c\n> +++ b/builtin/gc.c\n> @@ -1027,7 +1027,8 @@ static const char builtin_maintenance_usage[] = N_(\"git maintenance run [<option\n>\n>  int cmd_maintenance(int argc, const char **argv, const char *prefix)\n>  {\n> -\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n> +\tif (argc < 2 ||\n> +\t    (argc == 2 && !strcmp(argv[1], \"-h\")))\n>  \t\tusage(builtin_maintenance_usage);\n>\n>  \tif (!strcmp(argv[1], \"run\"))\n> diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\n> index 4f6a04ddb1..53c883531e 100755\n> --- a/t/t7900-maintenance.sh\n> +++ b/t/t7900-maintenance.sh\n> @@ -10,7 +10,9 @@ test_expect_success 'help text' '\n>  \ttest_expect_code 129 git maintenance -h 2>err &&\n>  \ttest_i18ngrep \"usage: git maintenance run\" err &&\n>  \ttest_expect_code 128 git maintenance barf 2>err &&\n> -\ttest_i18ngrep \"invalid subcommand: barf\" err\n> +\ttest_i18ngrep \"invalid subcommand: barf\" err &&\n> +\ttest_expect_code 129 git maintenance 2>err &&\n> +\ttest_i18ngrep \"usage: git maintenance\" err\n>  '\n>\n>  test_expect_success 'run [--auto|--quiet]' '\n>\n\nYep, that's how I read the range-diff, too.\n\nCiao,\nDscho\n"},{"id":"406047","messageId":"87d02fi75p.fsf@evledraar.gmail.com","threadId":"53997","inReplyTo":"1a0a3eebb825ac3eabfdd86f82ed7ef6abb454c5.1600366313.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 07/11] maintenance: take a lock on the objects directory","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2020-09-21T13:36:02Z","receivedAt":"2020-09-21T13:36:07Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Sep 17 2020, Derrick Stolee via GitGitGadget wrote:\n\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> Performing maintenance on a Git repository involves writing data to the\n> .git directory, which is not safe to do with multiple writers attempting\n> the same operation. Ensure that only one 'git maintenance' process is\n> running at a time by holding a file-based lock. Simply the presence of\n> the .git/maintenance.lock file will prevent future maintenance. This\n> lock is never committed, since it does not represent meaningful data.\n> Instead, it is only a placeholder.\n>\n> If the lock file already exists, then no maintenance tasks are\n> attempted. This will become very important later when we implement the\n> 'prefetch' task, as this is our stop-gap from creating a recursive process\n> loop between 'git fetch' and 'git maintenance run --auto'.\n>\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> ---\n>  builtin/gc.c | 20 ++++++++++++++++++++\n>  1 file changed, 20 insertions(+)\n>\n> diff --git a/builtin/gc.c b/builtin/gc.c\n> index 00fff59bdb..7ba9c6f7c9 100644\n> --- a/builtin/gc.c\n> +++ b/builtin/gc.c\n> @@ -798,6 +798,25 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts)\n>  {\n>  \tint i, found_selected = 0;\n>  \tint result = 0;\n> +\tstruct lock_file lk;\n> +\tstruct repository *r = the_repository;\n> +\tchar *lock_path = xstrfmt(\"%s/maintenance\", r->objects->odb->path);\n> +\n> +\tif (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {\n> +\t\t/*\n> +\t\t * Another maintenance command is running.\n> +\t\t *\n> +\t\t * If --auto was provided, then it is likely due to a\n> +\t\t * recursive process stack. Do not report an error in\n> +\t\t * that case.\n> +\t\t */\n> +\t\tif (!opts->auto_flag && !opts->quiet)\n> +\t\t\twarning(_(\"lock file '%s' exists, skipping maintenance\"),\n> +\t\t\t\tlock_path);\n> +\t\tfree(lock_path);\n> +\t\treturn 0;\n> +\t}\n> +\tfree(lock_path);\n>  \n>  \tfor (i = 0; !found_selected && i < TASK__COUNT; i++)\n>  \t\tfound_selected = tasks[i].selected_order >= 0;\n\nThere's now two different lock strategies in builtin/gc.c, the existing\none introduced in 64a99eb476 (\"gc: reject if another gc is running,\nunless --force is given\", 2013-08-08) where we write the hostname to the\ngc.pid file, and then discard the lockfile depending on a heuristic of\nwhether or not it's the same etc., and this one.\n\nWith this as an entry point we'll entirely do away with the old one\nsince we don't use the \"gc --auto\" entry point.\n\nAll of that may or may not be desirable, but I think a description in\nthe docs & tests for how these lock files should interact would be\nhelpful. E.g. writing a different hostname in the gc lockfile and\nsetting the time on it with with \"test-tool chmtime\" will cause it to\nplow ahead, but doing the same for \"git maintenance\" will stop it in its\ntracks no matter the time or content.\n"},{"id":"406048","messageId":"368d7c1f-b8b2-8bed-ead4-047019125435@gmail.com","threadId":"53997","inReplyTo":"87d02fi75p.fsf@evledraar.gmail.com","subject":"Re: [PATCH v5 07/11] maintenance: take a lock on the objects directory","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2020-09-21T13:43:24Z","receivedAt":"2020-09-21T13:43:27Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 9/21/2020 9:36 AM, Ævar Arnfjörð Bjarmason wrote:\n> There's now two different lock strategies in builtin/gc.c, the existing\n> one introduced in 64a99eb476 (\"gc: reject if another gc is running,\n> unless --force is given\", 2013-08-08) where we write the hostname to the\n> gc.pid file, and then discard the lockfile depending on a heuristic of\n> whether or not it's the same etc., and this one.\n> \n> With this as an entry point we'll entirely do away with the old one\n> since we don't use the \"gc --auto\" entry point.\n\nUsers could still erroneously launch two `git gc` commands concurrently\nand that will not use the maintenance.lock file. The GC lock is worth\nkeeping around until we redirect the `gc` command to be an alias that\nruns `git maintenance run --task=gc [options]`.\n\n> All of that may or may not be desirable, but I think a description in\n> the docs & tests for how these lock files should interact would be\n> helpful. E.g. writing a different hostname in the gc lockfile and\n> setting the time on it with with \"test-tool chmtime\" will cause it to\n> plow ahead, but doing the same for \"git maintenance\" will stop it in its\n> tracks no matter the time or content.\n\nThanks for the pointers to create this test. I'll try to get to it\nsoon. For now, here is a tracking issue [1].\n\n[1] https://github.com/gitgitgadget/git/issues/737\n\nThanks,\n-Stolee\n\n"},{"id":"406067","messageId":"xmqqa6xjexn1.fsf@gitster.c.googlers.com","threadId":"53997","inReplyTo":"368d7c1f-b8b2-8bed-ead4-047019125435@gmail.com","subject":"Re: [PATCH v5 07/11] maintenance: take a lock on the objects directory","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-09-21T19:29:54Z","receivedAt":"2020-09-21T19:30:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Derrick Stolee <stolee@gmail.com> writes:\n\n>> With this as an entry point we'll entirely do away with the old one\n>> since we don't use the \"gc --auto\" entry point.\n>\n> Users could still erroneously launch two `git gc` commands concurrently\n> and that will not use the maintenance.lock file. The GC lock is worth\n> keeping around until we redirect the `gc` command to be an alias that\n> runs `git maintenance run --task=gc [options]`.\n\nYes, that is prudent.  And as long as the traditional gc lock stops\nthe maintenance command from running the gc task, everthing would\nwork consistently ;-)\n\nThanks.\n"}]}