@matheusbsbs / matheus-bagliano / commits / 8a6ac287b2
Fork 0

builtin/commit-graph.c: introduce split strategy 'replace'

When using split commit-graphs, it is sometimes useful to completely replace the commit-graph chain with a new base. For example, consider a scenario in which a repository builds a new commit-graph incremental for each push. Occasionally (say, after some fixed number of pushes), they may wish to rebuild the commit-graph chain with all reachable commits. They can do so with $ git commit-graph write --reachable but this removes the chain entirely and replaces it with a single commit-graph in 'objects/info/commit-graph'. Unfortunately, this means that the next push will have to move this commit-graph into the first layer of a new chain, and then write its new commits on top. Avoid such copying entirely by allowing the caller to specify that they wish to replace the entirety of their commit-graph chain, while also specifying that the new commit-graph should become the basis of a fresh, length-one chain. This addresses the above situation by making it possible for the caller to instead write: $ git commit-graph write --reachable --split=replace which writes a new length-one chain to 'objects/info/commit-graphs', making the commit-graph incremental generated by the subsequent push relatively cheap by avoiding the aforementioned copy. In order to do this, remove an assumption in 'write_commit_graph_file' that chains are always at least two incrementals long. Signed-off-by: Taylor Blau <me@ttaylorr.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Taylor Blau committed Apr 13, 2020 at 22:04 UTC 8a6ac287b2ba5f75bb2d9409dd97e9b501daf253
5 files changed +66 -18
Documentation/git-commit-graph.txt
+4 -3
@@ -64,9 +64,10 @@ strategy and other splitting options. The new commits not already in the
64 commit-graph are added in a new "tip" file. This file is merged with the
65 existing file if the following merge conditions are met:
66 * If `--split=no-merge` is specified, a merge is never performed, and
67 -the remaining options are ignored. A bare `--split` defers to the
68 -remaining options. (Note that merging a chain of commit graphs replaces
69 -the existing chain with a length-1 chain where the first and only
67 +the remaining options are ignored. `--split=replace` overwrites the
68 +existing chain with a new one. A bare `--split` defers to the remaining
69 +options. (Note that merging a chain of commit graphs replaces the
70 +existing chain with a length-1 chain where the first and only
71 incremental holds the entire graph).
72 +
73 * If `--size-multiple=<X>` is not specified, let `X` equal 2. If the new
builtin/commit-graph.c
+2
@@ -126,6 +126,8 @@ static int write_option_parse_split(const struct option *opt, const char *arg,
126
127 if (!strcmp(arg, "no-merge"))
128 *flags = COMMIT_GRAPH_SPLIT_MERGE_PROHIBITED;
129 + else if (!strcmp(arg, "replace"))
130 + *flags = COMMIT_GRAPH_SPLIT_REPLACE;
131 else
132 die(_("unrecognized --split argument, %s"), arg);
133
commit-graph.c
+39 -14
@@ -866,7 +866,7 @@ static void write_graph_chunk_data(struct hashfile *f, int hash_len,
866
867 if (edge_value >= 0)
868 edge_value += ctx->new_num_commits_in_base;
869 - else {
869 + else if (ctx->new_base_graph) {
870 uint32_t pos;
871 if (find_commit_in_graph(parent->item,
872 ctx->new_base_graph,
@@ -897,7 +897,7 @@ static void write_graph_chunk_data(struct hashfile *f, int hash_len,
897
898 if (edge_value >= 0)
899 edge_value += ctx->new_num_commits_in_base;
900 - else {
900 + else if (ctx->new_base_graph) {
901 uint32_t pos;
902 if (find_commit_in_graph(parent->item,
903 ctx->new_base_graph,
@@ -964,7 +964,7 @@ static void write_graph_chunk_extra_edges(struct hashfile *f,
964
965 if (edge_value >= 0)
966 edge_value += ctx->new_num_commits_in_base;
967 - else {
967 + else if (ctx->new_base_graph) {
968 uint32_t pos;
969 if (find_commit_in_graph(parent->item,
970 ctx->new_base_graph,
@@ -1037,6 +1037,8 @@ static void close_reachable(struct write_commit_graph_context *ctx)
1037 {
1038 int i;
1039 struct commit *commit;
1040 + enum commit_graph_split_flags flags = ctx->split_opts ?
1041 + ctx->split_opts->flags : COMMIT_GRAPH_SPLIT_UNSPECIFIED;
1042
1043 if (ctx->report_progress)
1044 ctx->progress = start_delayed_progress(
@@ -1066,8 +1068,9 @@ static void close_reachable(struct write_commit_graph_context *ctx)
1068 if (!commit)
1069 continue;
1070 if (ctx->split) {
1069 - if (!parse_commit(commit) &&
1070 - commit->graph_pos == COMMIT_NOT_FROM_GRAPH)
1071 + if ((!parse_commit(commit) &&
1072 + commit->graph_pos == COMMIT_NOT_FROM_GRAPH) ||
1073 + flags == COMMIT_GRAPH_SPLIT_REPLACE)
1074 add_missing_parents(ctx, commit);
1075 } else if (!parse_commit_no_graph(commit))
1076 add_missing_parents(ctx, commit);
@@ -1287,6 +1290,8 @@ static uint32_t count_distinct_commits(struct write_commit_graph_context *ctx)
1290 static void copy_oids_to_commits(struct write_commit_graph_context *ctx)
1291 {
1292 uint32_t i;
1293 + enum commit_graph_split_flags flags = ctx->split_opts ?
1294 + ctx->split_opts->flags : COMMIT_GRAPH_SPLIT_UNSPECIFIED;
1295
1296 ctx->num_extra_edges = 0;
1297 if (ctx->report_progress)
@@ -1303,11 +1308,14 @@ static void copy_oids_to_commits(struct write_commit_graph_context *ctx)
1308 ALLOC_GROW(ctx->commits.list, ctx->commits.nr + 1, ctx->commits.alloc);
1309 ctx->commits.list[ctx->commits.nr] = lookup_commit(ctx->r, &ctx->oids.list[i]);
1310
1306 - if (ctx->split &&
1311 + if (ctx->split && flags != COMMIT_GRAPH_SPLIT_REPLACE &&
1312 ctx->commits.list[ctx->commits.nr]->graph_pos != COMMIT_NOT_FROM_GRAPH)
1313 continue;
1314
1310 - parse_commit_no_graph(ctx->commits.list[ctx->commits.nr]);
1315 + if (ctx->split && flags == COMMIT_GRAPH_SPLIT_REPLACE)
1316 + parse_commit(ctx->commits.list[ctx->commits.nr]);
1317 + else
1318 + parse_commit_no_graph(ctx->commits.list[ctx->commits.nr]);
1319
1320 num_parents = commit_list_count(ctx->commits.list[ctx->commits.nr]->parents);
1321 if (num_parents > 2)
@@ -1488,8 +1496,12 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
1496 }
1497
1498 if (ctx->base_graph_name) {
1491 - const char *dest = ctx->commit_graph_filenames_after[
1492 - ctx->num_commit_graphs_after - 2];
1499 + const char *dest;
1500 + int idx = ctx->num_commit_graphs_after - 1;
1501 + if (ctx->num_commit_graphs_after > 1)
1502 + idx--;
1503 +
1504 + dest = ctx->commit_graph_filenames_after[idx];
1505
1506 if (strcmp(ctx->base_graph_name, dest)) {
1507 result = rename(ctx->base_graph_name, dest);
@@ -1546,9 +1558,13 @@ static void split_graph_merge_strategy(struct write_commit_graph_context *ctx)
1558
1559 g = ctx->r->objects->commit_graph;
1560 num_commits = ctx->commits.nr;
1549 - ctx->num_commit_graphs_after = ctx->num_commit_graphs_before + 1;
1561 + if (flags == COMMIT_GRAPH_SPLIT_REPLACE)
1562 + ctx->num_commit_graphs_after = 1;
1563 + else
1564 + ctx->num_commit_graphs_after = ctx->num_commit_graphs_before + 1;
1565
1551 - if (flags != COMMIT_GRAPH_SPLIT_MERGE_PROHIBITED) {
1566 + if (flags != COMMIT_GRAPH_SPLIT_MERGE_PROHIBITED &&
1567 + flags != COMMIT_GRAPH_SPLIT_REPLACE) {
1568 while (g && (g->num_commits <= size_mult * num_commits ||
1569 (max_commits && num_commits > max_commits))) {
1570 if (g->odb != ctx->odb)
@@ -1561,7 +1577,11 @@ static void split_graph_merge_strategy(struct write_commit_graph_context *ctx)
1577 }
1578 }
1579
1564 - ctx->new_base_graph = g;
1580 + if (flags != COMMIT_GRAPH_SPLIT_REPLACE)
1581 + ctx->new_base_graph = g;
1582 + else if (ctx->num_commit_graphs_after != 1)
1583 + BUG("split_graph_merge_strategy: num_commit_graphs_after "
1584 + "should be 1 with --split=replace");
1585
1586 if (ctx->num_commit_graphs_after == 2) {
1587 char *old_graph_name = get_commit_graph_filename(g->odb);
@@ -1772,6 +1792,7 @@ int write_commit_graph(struct object_directory *odb,
1792 struct write_commit_graph_context *ctx;
1793 uint32_t i, count_distinct = 0;
1794 int res = 0;
1795 + int replace = 0;
1796
1797 if (!commit_graph_compatible(the_repository))
1798 return 0;
@@ -1806,6 +1827,9 @@ int write_commit_graph(struct object_directory *odb,
1827 g = g->base_graph;
1828 }
1829 }
1830 +
1831 + if (ctx->split_opts)
1832 + replace = ctx->split_opts->flags & COMMIT_GRAPH_SPLIT_REPLACE;
1833 }
1834
1835 ctx->approx_nr_objects = approximate_object_count();
@@ -1866,13 +1890,14 @@ int write_commit_graph(struct object_directory *odb,
1890 goto cleanup;
1891 }
1892
1869 - if (!ctx->commits.nr)
1893 + if (!ctx->commits.nr && !replace)
1894 goto cleanup;
1895
1896 if (ctx->split) {
1897 split_graph_merge_strategy(ctx);
1898
1875 - merge_commit_graphs(ctx);
1899 + if (!replace)
1900 + merge_commit_graphs(ctx);
1901 } else
1902 ctx->num_commit_graphs_after = 1;
1903
commit-graph.h
+2 -1
@@ -84,7 +84,8 @@ enum commit_graph_write_flags {
84
85 enum commit_graph_split_flags {
86 COMMIT_GRAPH_SPLIT_UNSPECIFIED = 0,
87 - COMMIT_GRAPH_SPLIT_MERGE_PROHIBITED = 1
87 + COMMIT_GRAPH_SPLIT_MERGE_PROHIBITED = 1,
88 + COMMIT_GRAPH_SPLIT_REPLACE = 2
89 };
90
91 struct split_commit_graph_opts {
t/t5324-split-commit-graph.sh
+19
@@ -355,4 +355,23 @@ test_expect_success '--split=no-merge always writes an incremental' '
355 test_line_count = 2 $graphdir/commit-graph-chain
356 '
357
358 +test_expect_success '--split=replace replaces the chain' '
359 + rm -rf $graphdir $infodir/commit-graph &&
360 + git reset --hard commits/3 &&
361 + git rev-list -1 HEAD~2 >a &&
362 + git rev-list -1 HEAD~1 >b &&
363 + git rev-list -1 HEAD >c &&
364 + git commit-graph write --split=no-merge --stdin-commits <a &&
365 + git commit-graph write --split=no-merge --stdin-commits <b &&
366 + git commit-graph write --split=no-merge --stdin-commits <c &&
367 + test_line_count = 3 $graphdir/commit-graph-chain &&
368 + git commit-graph write --stdin-commits --split=replace <b &&
369 + test_path_is_missing $infodir/commit-graph &&
370 + test_path_is_file $graphdir/commit-graph-chain &&
371 + ls $graphdir/graph-*.graph >graph-files &&
372 + test_line_count = 1 graph-files &&
373 + verify_chain_files_exist $graphdir &&
374 + graph_read_expect 2
375 +'
376 +
377 test_done