@@ -35,6 +35,24 @@ run::
TASKS
-----
+commit-graph::
+ The `commit-graph` job updates the `commit-graph` files incrementally,
+ then verifies that the written data is correct. If the new layer has an
+ issue, then the chain file is removed and the `commit-graph` is
+ rewritten from scratch.
++
+The verification only checks the top layer of the `commit-graph` chain.
+If the incremental write merged the new commits with at least one
+existing layer, then there is potential for on-disk corruption being
+carried forward into the new file. This will be noticed and the new
+commit-graph file will be clean as Git reparses the commit data from
+the object database.
++
+The incremental write is safe to run alongside concurrent Git processes
+since it will not expire `.graph` files that were in the previous
+`commit-graph-chain` file. They will be deleted by a later run based on
+the expiration delay.
+
gc::
Cleanup unnecessary files and optimize the local repository. "GC"
stands for "garbage collection," but this task performs many
@@ -710,6 +710,64 @@ static struct maintenance_opts {
int quiet;
} opts;
+static int run_write_commit_graph(void)
+{
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ child.git_cmd = 1;
+ strvec_pushl(&child.args, "commit-graph", "write",
+ "--split", "--reachable", NULL);
+
+ if (opts.quiet)
+ strvec_push(&child.args, "--no-progress");
+
+ return !!run_command(&child);
+}
+
+static int run_verify_commit_graph(void)
+{
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ child.git_cmd = 1;
+ strvec_pushl(&child.args, "commit-graph", "verify",
+ "--shallow", NULL);
+
+ if (opts.quiet)
+ strvec_push(&child.args, "--no-progress");
+
+ return !!run_command(&child);
+}
+
+static int maintenance_task_commit_graph(void)
+{
+ struct repository *r = the_repository;
+ char *chain_path;
+
+ close_object_store(r->objects);
+ if (run_write_commit_graph()) {
+ error(_("failed to write commit-graph"));
+ return 1;
+ }
+
+ if (!run_verify_commit_graph())
+ return 0;
+
+ warning(_("commit-graph verify caught error, rewriting"));
+
+ chain_path = get_commit_graph_chain_filename(r->objects->odb);
+ if (unlink(chain_path)) {
+ UNLEAK(chain_path);
+ die(_("failed to remove commit-graph at %s"), chain_path);
+ }
+ free(chain_path);
+
+ if (!run_write_commit_graph())
+ return 0;
+
+ error(_("failed to rewrite commit-graph"));
+ return 1;
+}
+
static int maintenance_task_gc(void)
{
struct child_process child = CHILD_PROCESS_INIT;
@@ -736,6 +794,7 @@ struct maintenance_task {
enum maintenance_task_label {
TASK_GC,
+ TASK_COMMIT_GRAPH,
/* Leave as final value */
TASK__COUNT
@@ -747,6 +806,10 @@ static struct maintenance_task tasks[] = {
maintenance_task_gc,
1,
},
+ [TASK_COMMIT_GRAPH] = {
+ "commit-graph",
+ maintenance_task_commit_graph,
+ },
};
static int maintenance_run(void)
@@ -172,7 +172,7 @@ static char *get_split_graph_filename(struct object_directory *odb,
oid_hex);
}
-static char *get_chain_filename(struct object_directory *odb)
+char *get_commit_graph_chain_filename(struct object_directory *odb)
{
return xstrfmt("%s/info/commit-graphs/commit-graph-chain", odb->path);
}
@@ -521,7 +521,7 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r,
struct stat st;
struct object_id *oids;
int i = 0, valid = 1, count;
- char *chain_name = get_chain_filename(odb);
+ char *chain_name = get_commit_graph_chain_filename(odb);
FILE *fp;
int stat_res;
@@ -1619,7 +1619,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
}
if (ctx->split) {
- char *lock_name = get_chain_filename(ctx->odb);
+ char *lock_name = get_commit_graph_chain_filename(ctx->odb);
hold_lock_file_for_update_mode(&lk, lock_name,
LOCK_DIE_ON_ERROR, 0444);
@@ -1996,7 +1996,7 @@ static void expire_commit_graphs(struct write_commit_graph_context *ctx)
if (ctx->split_opts && ctx->split_opts->expire_time)
expire_time = ctx->split_opts->expire_time;
if (!ctx->split) {
- char *chain_file_name = get_chain_filename(ctx->odb);
+ char *chain_file_name = get_commit_graph_chain_filename(ctx->odb);
unlink(chain_file_name);
free(chain_file_name);
ctx->num_commit_graphs_after = 0;
@@ -25,6 +25,7 @@ struct commit;
struct bloom_filter_settings;
char *get_commit_graph_filename(struct object_directory *odb);
+char *get_commit_graph_chain_filename(struct object_directory *odb);
int open_commit_graph(const char *graph_file, int *fd, struct stat *st);
/*
@@ -4,6 +4,8 @@ test_description='git maintenance builtin'
. ./test-lib.sh
+GIT_TEST_COMMIT_GRAPH=0
+
test_expect_success 'help text' '
test_expect_code 129 git maintenance -h 2>err &&
test_i18ngrep "usage: git maintenance run" err