diff --git a/Documentation/git-commit-graph.txt b/Documentation/git-commit-graph.txt
index 28d1fee505306b..b210cef52f06bd 100644
--- a/Documentation/git-commit-graph.txt
+++ b/Documentation/git-commit-graph.txt
@@ -39,29 +39,42 @@ COMMANDS
--------
'write'::
-Write a commit-graph file based on the commits found in packfiles.
+Write a commit-graph file based on the specified sources of input:
+
-With the `--stdin-packs` option, generate the new commit graph by
+With the `--input=stdin-packs` option, generate the new commit graph by
walking objects only in the specified pack-indexes. (Cannot be combined
-with `--stdin-commits` or `--reachable`.)
+with `--input=stdin-commits` or `--input=reachable`.)
+
-With the `--stdin-commits` option, generate the new commit graph by
-walking commits starting at the commits specified in stdin as a list
+With the `--input=stdin-commits` option, generate the new commit graph
+by walking commits starting at the commits specified in stdin as a list
of OIDs in hex, one OID per line. (Cannot be combined with
-`--stdin-packs` or `--reachable`.)
+`--input=stdin-packs` or `--input=reachable`.)
+
-With the `--reachable` option, generate the new commit graph by walking
-commits starting at all refs. (Cannot be combined with `--stdin-commits`
-or `--stdin-packs`.)
+With the `--input=reachable` option, generate the new commit graph by
+walking commits starting at all refs. (Cannot be combined with
+`--input=stdin-commits` or `--input=stdin-packs`.)
+
-With the `--append` option, include all commits that are present in the
-existing commit-graph file.
+With the `--input=append` option, include all commits that are present
+in the existing commit-graph file.
+
-With the `--split` option, write the commit-graph as a chain of multiple
-commit-graph files stored in `
/info/commit-graphs`. The new commits
-not already in the commit-graph are added in a new "tip" file. This file
-is merged with the existing file if the following merge conditions are
-met:
+With the `--input=none` option, behave as if `--input=append` were
+given, but do not walk other packs to find additional commits.
++
+If none of the above options are given, then generate the new
+commit-graph by walking over all pack-indexes.
++
+With the `--split[=]` option, write the commit-graph as a
+chain of multiple commit-graph files stored in
+`/info/commit-graphs`. Commit-graph layers are merged based on the
+strategy and other splitting options. The new commits not already in the
+commit-graph are added in a new "tip" file. This file is merged with the
+existing file if the following merge conditions are met:
++
+* If `--split=merge-always` is specified, then a merge is always
+conducted, and the remaining options are ignored. Conversely, if
+`--split=no-merge` is specified, a merge is never performed, and the
+remaining options are ignored. A bare `--split` defers to the remaining
+options.
+
* If `--size-multiple=` is not specified, let `X` equal 2. If the new
tip file would have `N` commits and the previous tip has `M` commits and
@@ -99,20 +112,20 @@ $ git commit-graph write
using commits in ``.
+
------------------------------------------------
-$ echo | git commit-graph write --stdin-packs
+$ echo | git commit-graph write --input=stdin-packs
------------------------------------------------
* Write a commit-graph file containing all reachable commits.
+
------------------------------------------------
-$ git show-ref -s | git commit-graph write --stdin-commits
+$ git show-ref -s | git commit-graph write --input=stdin-commits
------------------------------------------------
* Write a commit-graph file containing all commits in the current
commit-graph file along with those reachable from `HEAD`.
+
------------------------------------------------
-$ git rev-parse HEAD | git commit-graph write --stdin-commits --append
+$ git rev-parse HEAD | git commit-graph write --input=stdin-commits --input=append
------------------------------------------------
diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c
index 4a70b33fb5f135..a71af88815576e 100644
--- a/builtin/commit-graph.c
+++ b/builtin/commit-graph.c
@@ -9,7 +9,10 @@
static char const * const builtin_commit_graph_usage[] = {
N_("git commit-graph verify [--object-dir ] [--shallow] [--[no-]progress]"),
- N_("git commit-graph write [--object-dir ] [--append|--split] [--reachable|--stdin-packs|--stdin-commits] [--[no-]progress] "),
+ N_("git commit-graph write [--object-dir ] "
+ "[--split[=]] "
+ "[--input=] "
+ "[--[no-]progress] "),
NULL
};
@@ -19,16 +22,24 @@ static const char * const builtin_commit_graph_verify_usage[] = {
};
static const char * const builtin_commit_graph_write_usage[] = {
- N_("git commit-graph write [--object-dir ] [--append|--split] [--reachable|--stdin-packs|--stdin-commits] [--[no-]progress] "),
+ N_("git commit-graph write [--object-dir ] "
+ "[--split[=]] "
+ "[--input=] "
+ "[--[no-]progress] "),
NULL
};
+enum commit_graph_input {
+ COMMIT_GRAPH_INPUT_REACHABLE = (1 << 1),
+ COMMIT_GRAPH_INPUT_STDIN_PACKS = (1 << 2),
+ COMMIT_GRAPH_INPUT_STDIN_COMMITS = (1 << 3),
+ COMMIT_GRAPH_INPUT_APPEND = (1 << 4),
+ COMMIT_GRAPH_INPUT_NONE = (1 << 5)
+};
+
static struct opts_commit_graph {
const char *obj_dir;
- int reachable;
- int stdin_packs;
- int stdin_commits;
- int append;
+ enum commit_graph_input input;
int split;
int shallow;
int progress;
@@ -53,6 +64,30 @@ static struct object_directory *find_odb(struct repository *r,
return odb;
}
+static int option_parse_input(const struct option *opt, const char *arg,
+ int unset)
+{
+ enum commit_graph_input *to = opt->value;
+ if (unset || !strcmp(arg, "packs")) {
+ *to = 0;
+ return 0;
+ }
+
+ if (!strcmp(arg, "reachable"))
+ *to |= COMMIT_GRAPH_INPUT_REACHABLE;
+ else if (!strcmp(arg, "stdin-packs"))
+ *to |= COMMIT_GRAPH_INPUT_STDIN_PACKS;
+ else if (!strcmp(arg, "stdin-commits"))
+ *to |= COMMIT_GRAPH_INPUT_STDIN_COMMITS;
+ else if (!strcmp(arg, "append"))
+ *to |= COMMIT_GRAPH_INPUT_APPEND;
+ else if (!strcmp(arg, "none"))
+ *to |= (COMMIT_GRAPH_INPUT_APPEND | COMMIT_GRAPH_INPUT_NONE);
+ else
+ die(_("unrecognized --input source, %s"), arg);
+ return 0;
+}
+
static int graph_verify(int argc, const char **argv)
{
struct commit_graph *graph = NULL;
@@ -111,6 +146,27 @@ static int graph_verify(int argc, const char **argv)
extern int read_replace_refs;
static struct split_commit_graph_opts split_opts;
+static int write_option_parse_split(const struct option *opt, const char *arg,
+ int unset)
+{
+ enum commit_graph_split_flags *flags = opt->value;
+
+ opts.split = 1;
+ if (!arg) {
+ *flags = COMMIT_GRAPH_SPLIT_MERGE_AUTO;
+ return 0;
+ }
+
+ if (!strcmp(arg, "merge-all"))
+ *flags = COMMIT_GRAPH_SPLIT_MERGE_REQUIRED;
+ else if (!strcmp(arg, "no-merge"))
+ *flags = COMMIT_GRAPH_SPLIT_MERGE_PROHIBITED;
+ else
+ die(_("unrecognized --split argument, %s"), arg);
+
+ return 0;
+}
+
static int graph_write(int argc, const char **argv)
{
struct string_list *pack_indexes = NULL;
@@ -124,17 +180,26 @@ static int graph_write(int argc, const char **argv)
OPT_STRING(0, "object-dir", &opts.obj_dir,
N_("dir"),
N_("The object directory to store the graph")),
- OPT_BOOL(0, "reachable", &opts.reachable,
- N_("start walk at all refs")),
- OPT_BOOL(0, "stdin-packs", &opts.stdin_packs,
- N_("scan pack-indexes listed by stdin for commits")),
- OPT_BOOL(0, "stdin-commits", &opts.stdin_commits,
- N_("start walk at commits listed by stdin")),
- OPT_BOOL(0, "append", &opts.append,
- N_("include all commits already in the commit-graph file")),
+ OPT_CALLBACK(0, "input", &opts.input, NULL,
+ N_("include commits from this source in the graph"),
+ option_parse_input),
+ OPT_BIT(0, "reachable", &opts.input,
+ N_("start walk at all refs"),
+ COMMIT_GRAPH_INPUT_REACHABLE),
+ OPT_BIT(0, "stdin-packs", &opts.input,
+ N_("scan pack-indexes listed by stdin for commits"),
+ COMMIT_GRAPH_INPUT_STDIN_PACKS),
+ OPT_BIT(0, "stdin-commits", &opts.input,
+ N_("start walk at commits listed by stdin"),
+ COMMIT_GRAPH_INPUT_STDIN_COMMITS),
+ OPT_BIT(0, "append", &opts.input,
+ N_("include all commits already in the commit-graph file"),
+ COMMIT_GRAPH_INPUT_APPEND),
OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")),
- OPT_BOOL(0, "split", &opts.split,
- N_("allow writing an incremental commit-graph file")),
+ OPT_CALLBACK_F(0, "split", &split_opts.flags, NULL,
+ N_("allow writing an incremental commit-graph file"),
+ PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
+ write_option_parse_split),
OPT_INTEGER(0, "max-commits", &split_opts.max_commits,
N_("maximum number of commits in a non-base split commit-graph")),
OPT_INTEGER(0, "size-multiple", &split_opts.size_multiple,
@@ -155,12 +220,16 @@ static int graph_write(int argc, const char **argv)
builtin_commit_graph_write_options,
builtin_commit_graph_write_usage, 0);
- if (opts.reachable + opts.stdin_packs + opts.stdin_commits > 1)
- die(_("use at most one of --reachable, --stdin-commits, or --stdin-packs"));
+ if ((!!(opts.input & COMMIT_GRAPH_INPUT_REACHABLE) +
+ !!(opts.input & COMMIT_GRAPH_INPUT_STDIN_PACKS) +
+ !!(opts.input & COMMIT_GRAPH_INPUT_STDIN_COMMITS)) > 1)
+ die(_("use at most one of --input=reachable, --input=stdin-commits, or --input=stdin-packs"));
if (!opts.obj_dir)
opts.obj_dir = get_object_directory();
- if (opts.append)
+ if (opts.input & COMMIT_GRAPH_INPUT_APPEND)
flags |= COMMIT_GRAPH_WRITE_APPEND;
+ if (opts.input & COMMIT_GRAPH_INPUT_NONE)
+ flags |= COMMIT_GRAPH_WRITE_NO_INPUT;
if (opts.split)
flags |= COMMIT_GRAPH_WRITE_SPLIT;
if (opts.progress)
@@ -169,22 +238,22 @@ static int graph_write(int argc, const char **argv)
read_replace_refs = 0;
odb = find_odb(the_repository, opts.obj_dir);
- if (opts.reachable) {
+ if (opts.input & COMMIT_GRAPH_INPUT_REACHABLE) {
if (write_commit_graph_reachable(odb, flags, &split_opts))
return 1;
return 0;
}
string_list_init(&lines, 0);
- if (opts.stdin_packs || opts.stdin_commits) {
+ if (opts.input & (COMMIT_GRAPH_INPUT_STDIN_PACKS | COMMIT_GRAPH_INPUT_STDIN_COMMITS)) {
struct strbuf buf = STRBUF_INIT;
while (strbuf_getline(&buf, stdin) != EOF)
string_list_append(&lines, strbuf_detach(&buf, NULL));
- if (opts.stdin_packs)
+ if (opts.input & COMMIT_GRAPH_INPUT_STDIN_PACKS)
pack_indexes = &lines;
- if (opts.stdin_commits) {
+ if (opts.input & COMMIT_GRAPH_INPUT_STDIN_COMMITS) {
commit_hex = &lines;
flags |= COMMIT_GRAPH_WRITE_CHECK_OIDS;
}
diff --git a/commit-graph.c b/commit-graph.c
index f013a84e294b13..3da52847e41e22 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -788,7 +788,8 @@ struct write_commit_graph_context {
unsigned append:1,
report_progress:1,
split:1,
- check_oids:1;
+ check_oids:1,
+ no_input:1;
const struct split_commit_graph_opts *split_opts;
};
@@ -1533,27 +1534,33 @@ static void split_graph_merge_strategy(struct write_commit_graph_context *ctx)
int max_commits = 0;
int size_mult = 2;
+ enum commit_graph_split_flags flags = COMMIT_GRAPH_SPLIT_MERGE_AUTO;
if (ctx->split_opts) {
max_commits = ctx->split_opts->max_commits;
if (ctx->split_opts->size_multiple)
size_mult = ctx->split_opts->size_multiple;
+
+ flags = ctx->split_opts->flags;
}
g = ctx->r->objects->commit_graph;
num_commits = ctx->commits.nr;
ctx->num_commit_graphs_after = ctx->num_commit_graphs_before + 1;
- while (g && (g->num_commits <= size_mult * num_commits ||
- (max_commits && num_commits > max_commits))) {
- if (g->odb != ctx->odb)
- break;
+ if (flags != COMMIT_GRAPH_SPLIT_MERGE_PROHIBITED) {
+ while (g && (g->num_commits <= size_mult * num_commits ||
+ (max_commits && num_commits > max_commits) ||
+ (flags == COMMIT_GRAPH_SPLIT_MERGE_REQUIRED))) {
+ if (g->odb != ctx->odb)
+ break;
- num_commits += g->num_commits;
- g = g->base_graph;
+ num_commits += g->num_commits;
+ g = g->base_graph;
- ctx->num_commit_graphs_after--;
+ ctx->num_commit_graphs_after--;
+ }
}
ctx->new_base_graph = g;
@@ -1775,6 +1782,7 @@ int write_commit_graph(struct object_directory *odb,
ctx->split = flags & COMMIT_GRAPH_WRITE_SPLIT ? 1 : 0;
ctx->check_oids = flags & COMMIT_GRAPH_WRITE_CHECK_OIDS ? 1 : 0;
ctx->split_opts = split_opts;
+ ctx->no_input = flags & COMMIT_GRAPH_WRITE_NO_INPUT ? 1 : 0;
if (ctx->split) {
struct commit_graph *g;
@@ -1833,7 +1841,7 @@ int write_commit_graph(struct object_directory *odb,
goto cleanup;
}
- if (!pack_indexes && !commit_hex)
+ if (!ctx->no_input && !pack_indexes && !commit_hex)
fill_oids_from_all_packs(ctx);
close_reachable(ctx);
@@ -1857,7 +1865,7 @@ int write_commit_graph(struct object_directory *odb,
goto cleanup;
}
- if (!ctx->commits.nr)
+ if (!ctx->commits.nr && (!ctx->split_opts || ctx->split_opts->flags != COMMIT_GRAPH_SPLIT_MERGE_REQUIRED))
goto cleanup;
if (ctx->split) {
diff --git a/commit-graph.h b/commit-graph.h
index e87a6f636000d6..df7f3f5961a43e 100644
--- a/commit-graph.h
+++ b/commit-graph.h
@@ -79,13 +79,21 @@ enum commit_graph_write_flags {
COMMIT_GRAPH_WRITE_PROGRESS = (1 << 1),
COMMIT_GRAPH_WRITE_SPLIT = (1 << 2),
/* Make sure that each OID in the input is a valid commit OID. */
- COMMIT_GRAPH_WRITE_CHECK_OIDS = (1 << 3)
+ COMMIT_GRAPH_WRITE_CHECK_OIDS = (1 << 3),
+ COMMIT_GRAPH_WRITE_NO_INPUT = (1 << 4)
+};
+
+enum commit_graph_split_flags {
+ COMMIT_GRAPH_SPLIT_MERGE_AUTO = 0,
+ COMMIT_GRAPH_SPLIT_MERGE_REQUIRED = 1,
+ COMMIT_GRAPH_SPLIT_MERGE_PROHIBITED = 2
};
struct split_commit_graph_opts {
int size_multiple;
int max_commits;
timestamp_t expire_time;
+ enum commit_graph_split_flags flags;
};
/*
diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh
index 9bf920ae1716dd..117dca317e9739 100755
--- a/t/t5318-commit-graph.sh
+++ b/t/t5318-commit-graph.sh
@@ -227,7 +227,7 @@ graph_git_behavior 'cleared graph, commit 8 vs merge 2' full commits/8 merge/2
test_expect_success 'build graph from latest pack with closure' '
cd "$TRASH_DIRECTORY/full" &&
- cat new-idx | git commit-graph write --stdin-packs &&
+ cat new-idx | git commit-graph write --input=stdin-packs &&
test_path_is_file $objdir/info/commit-graph &&
graph_read_expect "9" "extra_edges"
'
@@ -240,7 +240,7 @@ test_expect_success 'build graph from commits with closure' '
git tag -a -m "merge" tag/merge merge/2 &&
git rev-parse tag/merge >commits-in &&
git rev-parse merge/1 >>commits-in &&
- cat commits-in | git commit-graph write --stdin-commits &&
+ cat commits-in | git commit-graph write --input=stdin-commits &&
test_path_is_file $objdir/info/commit-graph &&
graph_read_expect "6"
'
diff --git a/t/t5324-split-commit-graph.sh b/t/t5324-split-commit-graph.sh
index 53b2e6b4555de7..7614f3915b72a4 100755
--- a/t/t5324-split-commit-graph.sh
+++ b/t/t5324-split-commit-graph.sh
@@ -42,7 +42,7 @@ test_expect_success 'create commits and write commit-graph' '
test_commit $i &&
git branch commits/$i || return 1
done &&
- git commit-graph write --reachable &&
+ git commit-graph write --input=reachable &&
test_path_is_file $infodir/commit-graph &&
graph_read_expect 3
'
@@ -351,4 +351,55 @@ test_expect_success 'split across alternate where alternate is not split' '
test_cmp commit-graph .git/objects/info/commit-graph
'
+test_expect_success '--split=merge-all always merges incrementals' '
+ test_when_finished rm -rf a b c &&
+ rm -rf $graphdir $infodir/commit-graph &&
+ git reset --hard commits/10 &&
+ git rev-list -3 HEAD~4 >a &&
+ git rev-list -2 HEAD~2 >b &&
+ git rev-list -2 HEAD >c &&
+ git commit-graph write --split=no-merge --stdin-commits a &&
+ git rev-list HEAD >b &&
+ git commit-graph write --split --stdin-commits a &&
+ git commit-graph write --split=no-merge --input=stdin-commits graphs.before &&
+ test_line_count = 1 $graphdir/commit-graph-chain &&
+ git commit-graph write --split --input=none &&
+ ls $graphdir/graph-*.graph >graphs.after &&
+ test_cmp graphs.before graphs.after
+'
+
+test_expect_success '--split=merge-all, --input=none merges the chain' '
+ test_when_finished rm -rf a b &&
+ rm -rf $graphdir &&
+ git reset --hard commits/2 &&
+ git rev-list -1 HEAD~1 >a &&
+ git rev-list -1 HEAD >b &&
+ git commit-graph write --split=no-merge --input=stdin-commits