Merge branch 'ps/connectivity-optim'

The revision traversal API has been optimized by taking advantage
of the commit-graph, when available, to determine if a commit is
reachable from any of the existing refs.

* ps/connectivity-optim:
  revision: avoid hitting packfiles when commits are in commit-graph
  commit-graph: split out function to search commit position
  revision: stop retrieving reference twice
  connected: do not sort input revisions
  revision: separate walk and unsorted flags
This commit is contained in:
Junio C Hamano
2021-09-03 13:49:27 -07:00
9 changed files with 129 additions and 48 deletions

View File

@ -968,6 +968,11 @@ list of the missing objects. Object IDs are prefixed with a ``?'' character.
objects. objects.
endif::git-rev-list[] endif::git-rev-list[]
--unsorted-input::
Show commits in the order they were given on the command line instead
of sorting them in reverse chronological order by commit time. Cannot
be combined with `--no-walk` or `--no-walk=sorted`.
--no-walk[=(sorted|unsorted)]:: --no-walk[=(sorted|unsorted)]::
Only show the given commits, but do not traverse their ancestors. Only show the given commits, but do not traverse their ancestors.
This has no effect if a range is specified. If the argument This has no effect if a range is specified. If the argument
@ -975,7 +980,8 @@ endif::git-rev-list[]
given on the command line. Otherwise (if `sorted` or no argument given on the command line. Otherwise (if `sorted` or no argument
was given), the commits are shown in reverse chronological order was given), the commits are shown in reverse chronological order
by commit time. by commit time.
Cannot be combined with `--graph`. Cannot be combined with `--graph`. Cannot be combined with
`--unsorted-input` if `sorted` or no argument was given.
--do-walk:: --do-walk::
Overrides a previous `--no-walk`. Overrides a previous `--no-walk`.

View File

@ -637,7 +637,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
repo_init_revisions(the_repository, &rev, prefix); repo_init_revisions(the_repository, &rev, prefix);
rev.diff = 1; rev.diff = 1;
rev.always_show_header = 1; rev.always_show_header = 1;
rev.no_walk = REVISION_WALK_NO_WALK_SORTED; rev.no_walk = 1;
rev.diffopt.stat_width = -1; /* Scale to real terminal size */ rev.diffopt.stat_width = -1; /* Scale to real terminal size */
memset(&opt, 0, sizeof(opt)); memset(&opt, 0, sizeof(opt));

View File

@ -191,7 +191,8 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
struct setup_revision_opt s_r_opt; struct setup_revision_opt s_r_opt;
opts->revs = xmalloc(sizeof(*opts->revs)); opts->revs = xmalloc(sizeof(*opts->revs));
repo_init_revisions(the_repository, opts->revs, NULL); repo_init_revisions(the_repository, opts->revs, NULL);
opts->revs->no_walk = REVISION_WALK_NO_WALK_UNSORTED; opts->revs->no_walk = 1;
opts->revs->unsorted_input = 1;
if (argc < 2) if (argc < 2)
usage_with_options(usage_str, options); usage_with_options(usage_str, options);
if (!strcmp(argv[1], "-")) if (!strcmp(argv[1], "-"))

View File

@ -723,7 +723,7 @@ void close_commit_graph(struct raw_object_store *o)
o->commit_graph = NULL; o->commit_graph = NULL;
} }
static int bsearch_graph(struct commit_graph *g, struct object_id *oid, uint32_t *pos) static int bsearch_graph(struct commit_graph *g, const struct object_id *oid, uint32_t *pos)
{ {
return bsearch_hash(oid->hash, g->chunk_oid_fanout, return bsearch_hash(oid->hash, g->chunk_oid_fanout,
g->chunk_oid_lookup, g->hash_len, pos); g->chunk_oid_lookup, g->hash_len, pos);
@ -864,28 +864,57 @@ static int fill_commit_in_graph(struct repository *r,
return 1; return 1;
} }
static int find_commit_in_graph(struct commit *item, struct commit_graph *g, uint32_t *pos) static int search_commit_pos_in_graph(const struct object_id *id, struct commit_graph *g, uint32_t *pos)
{
struct commit_graph *cur_g = g;
uint32_t lex_index;
while (cur_g && !bsearch_graph(cur_g, id, &lex_index))
cur_g = cur_g->base_graph;
if (cur_g) {
*pos = lex_index + cur_g->num_commits_in_base;
return 1;
}
return 0;
}
static int find_commit_pos_in_graph(struct commit *item, struct commit_graph *g, uint32_t *pos)
{ {
uint32_t graph_pos = commit_graph_position(item); uint32_t graph_pos = commit_graph_position(item);
if (graph_pos != COMMIT_NOT_FROM_GRAPH) { if (graph_pos != COMMIT_NOT_FROM_GRAPH) {
*pos = graph_pos; *pos = graph_pos;
return 1; return 1;
} else { } else {
struct commit_graph *cur_g = g; return search_commit_pos_in_graph(&item->object.oid, g, pos);
uint32_t lex_index;
while (cur_g && !bsearch_graph(cur_g, &(item->object.oid), &lex_index))
cur_g = cur_g->base_graph;
if (cur_g) {
*pos = lex_index + cur_g->num_commits_in_base;
return 1;
}
return 0;
} }
} }
struct commit *lookup_commit_in_graph(struct repository *repo, const struct object_id *id)
{
struct commit *commit;
uint32_t pos;
if (!repo->objects->commit_graph)
return NULL;
if (!search_commit_pos_in_graph(id, repo->objects->commit_graph, &pos))
return NULL;
if (!repo_has_object_file(repo, id))
return NULL;
commit = lookup_commit(repo, id);
if (!commit)
return NULL;
if (commit->object.parsed)
return commit;
if (!fill_commit_in_graph(repo, commit, repo->objects->commit_graph, pos))
return NULL;
return commit;
}
static int parse_commit_in_graph_one(struct repository *r, static int parse_commit_in_graph_one(struct repository *r,
struct commit_graph *g, struct commit_graph *g,
struct commit *item) struct commit *item)
@ -895,7 +924,7 @@ static int parse_commit_in_graph_one(struct repository *r,
if (item->object.parsed) if (item->object.parsed)
return 1; return 1;
if (find_commit_in_graph(item, g, &pos)) if (find_commit_pos_in_graph(item, g, &pos))
return fill_commit_in_graph(r, item, g, pos); return fill_commit_in_graph(r, item, g, pos);
return 0; return 0;
@ -921,7 +950,7 @@ void load_commit_graph_info(struct repository *r, struct commit *item)
uint32_t pos; uint32_t pos;
if (!prepare_commit_graph(r)) if (!prepare_commit_graph(r))
return; return;
if (find_commit_in_graph(item, r->objects->commit_graph, &pos)) if (find_commit_pos_in_graph(item, r->objects->commit_graph, &pos))
fill_commit_graph_info(item, r->objects->commit_graph, pos); fill_commit_graph_info(item, r->objects->commit_graph, pos);
} }
@ -1091,9 +1120,9 @@ static int write_graph_chunk_data(struct hashfile *f,
edge_value += ctx->new_num_commits_in_base; edge_value += ctx->new_num_commits_in_base;
else if (ctx->new_base_graph) { else if (ctx->new_base_graph) {
uint32_t pos; uint32_t pos;
if (find_commit_in_graph(parent->item, if (find_commit_pos_in_graph(parent->item,
ctx->new_base_graph, ctx->new_base_graph,
&pos)) &pos))
edge_value = pos; edge_value = pos;
} }
@ -1122,9 +1151,9 @@ static int write_graph_chunk_data(struct hashfile *f,
edge_value += ctx->new_num_commits_in_base; edge_value += ctx->new_num_commits_in_base;
else if (ctx->new_base_graph) { else if (ctx->new_base_graph) {
uint32_t pos; uint32_t pos;
if (find_commit_in_graph(parent->item, if (find_commit_pos_in_graph(parent->item,
ctx->new_base_graph, ctx->new_base_graph,
&pos)) &pos))
edge_value = pos; edge_value = pos;
} }
@ -1235,9 +1264,9 @@ static int write_graph_chunk_extra_edges(struct hashfile *f,
edge_value += ctx->new_num_commits_in_base; edge_value += ctx->new_num_commits_in_base;
else if (ctx->new_base_graph) { else if (ctx->new_base_graph) {
uint32_t pos; uint32_t pos;
if (find_commit_in_graph(parent->item, if (find_commit_pos_in_graph(parent->item,
ctx->new_base_graph, ctx->new_base_graph,
&pos)) &pos))
edge_value = pos; edge_value = pos;
} }

View File

@ -40,6 +40,14 @@ int open_commit_graph(const char *graph_file, int *fd, struct stat *st);
*/ */
int parse_commit_in_graph(struct repository *r, struct commit *item); int parse_commit_in_graph(struct repository *r, struct commit *item);
/*
* Look up the given commit ID in the commit-graph. This will only return a
* commit if the ID exists both in the graph and in the object database such
* that we don't return commits whose object has been pruned. Otherwise, this
* function returns `NULL`.
*/
struct commit *lookup_commit_in_graph(struct repository *repo, const struct object_id *id);
/* /*
* It is possible that we loaded commit contents from the commit buffer, * It is possible that we loaded commit contents from the commit buffer,
* but we also want to ensure the commit-graph content is correctly * but we also want to ensure the commit-graph content is correctly

View File

@ -106,6 +106,7 @@ no_promisor_pack_found:
if (opt->progress) if (opt->progress)
strvec_pushf(&rev_list.args, "--progress=%s", strvec_pushf(&rev_list.args, "--progress=%s",
_("Checking connectivity")); _("Checking connectivity"));
strvec_push(&rev_list.args, "--unsorted-input");
rev_list.git_cmd = 1; rev_list.git_cmd = 1;
rev_list.env = opt->env; rev_list.env = opt->env;

View File

@ -360,20 +360,18 @@ static struct object *get_reference(struct rev_info *revs, const char *name,
unsigned int flags) unsigned int flags)
{ {
struct object *object; struct object *object;
struct commit *commit;
/* /*
* If the repository has commit graphs, repo_parse_commit() avoids * If the repository has commit graphs, we try to opportunistically
* reading the object buffer, so use it whenever possible. * look up the object ID in those graphs. Like this, we can avoid
* parsing commit data from disk.
*/ */
if (oid_object_info(revs->repo, oid, NULL) == OBJ_COMMIT) { commit = lookup_commit_in_graph(revs->repo, oid);
struct commit *c = lookup_commit(revs->repo, oid); if (commit)
if (!repo_parse_commit(revs->repo, c)) object = &commit->object;
object = (struct object *) c; else
else
object = NULL;
} else {
object = parse_object(revs->repo, oid); object = parse_object(revs->repo, oid);
}
if (!object) { if (!object) {
if (revs->ignore_missing) if (revs->ignore_missing)
@ -1534,7 +1532,7 @@ static int handle_one_ref(const char *path, const struct object_id *oid,
object = get_reference(cb->all_revs, path, oid, cb->all_flags); object = get_reference(cb->all_revs, path, oid, cb->all_flags);
add_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags); add_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags);
add_pending_oid(cb->all_revs, path, oid, cb->all_flags); add_pending_object(cb->all_revs, object, path);
return 0; return 0;
} }
@ -2256,6 +2254,10 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
} else if (!strcmp(arg, "--author-date-order")) { } else if (!strcmp(arg, "--author-date-order")) {
revs->sort_order = REV_SORT_BY_AUTHOR_DATE; revs->sort_order = REV_SORT_BY_AUTHOR_DATE;
revs->topo_order = 1; revs->topo_order = 1;
} else if (!strcmp(arg, "--unsorted-input")) {
if (revs->no_walk)
die(_("--unsorted-input is incompatible with --no-walk"));
revs->unsorted_input = 1;
} else if (!strcmp(arg, "--early-output")) { } else if (!strcmp(arg, "--early-output")) {
revs->early_output = 100; revs->early_output = 100;
revs->topo_order = 1; revs->topo_order = 1;
@ -2651,16 +2653,22 @@ static int handle_revision_pseudo_opt(const char *submodule,
} else if (!strcmp(arg, "--not")) { } else if (!strcmp(arg, "--not")) {
*flags ^= UNINTERESTING | BOTTOM; *flags ^= UNINTERESTING | BOTTOM;
} else if (!strcmp(arg, "--no-walk")) { } else if (!strcmp(arg, "--no-walk")) {
revs->no_walk = REVISION_WALK_NO_WALK_SORTED; if (!revs->no_walk && revs->unsorted_input)
die(_("--no-walk is incompatible with --unsorted-input"));
revs->no_walk = 1;
} else if (skip_prefix(arg, "--no-walk=", &optarg)) { } else if (skip_prefix(arg, "--no-walk=", &optarg)) {
if (!revs->no_walk && revs->unsorted_input)
die(_("--no-walk is incompatible with --unsorted-input"));
/* /*
* Detached form ("--no-walk X" as opposed to "--no-walk=X") * Detached form ("--no-walk X" as opposed to "--no-walk=X")
* not allowed, since the argument is optional. * not allowed, since the argument is optional.
*/ */
revs->no_walk = 1;
if (!strcmp(optarg, "sorted")) if (!strcmp(optarg, "sorted"))
revs->no_walk = REVISION_WALK_NO_WALK_SORTED; revs->unsorted_input = 0;
else if (!strcmp(optarg, "unsorted")) else if (!strcmp(optarg, "unsorted"))
revs->no_walk = REVISION_WALK_NO_WALK_UNSORTED; revs->unsorted_input = 1;
else else
return error("invalid argument to --no-walk"); return error("invalid argument to --no-walk");
} else if (!strcmp(arg, "--do-walk")) { } else if (!strcmp(arg, "--do-walk")) {
@ -3584,7 +3592,7 @@ int prepare_revision_walk(struct rev_info *revs)
if (!revs->reflog_info) if (!revs->reflog_info)
prepare_to_use_bloom_filter(revs); prepare_to_use_bloom_filter(revs);
if (revs->no_walk != REVISION_WALK_NO_WALK_UNSORTED) if (!revs->unsorted_input)
commit_list_sort_by_date(&revs->commits); commit_list_sort_by_date(&revs->commits);
if (revs->no_walk) if (revs->no_walk)
return 0; return 0;

View File

@ -79,10 +79,6 @@ struct rev_cmdline_info {
} *rev; } *rev;
}; };
#define REVISION_WALK_WALK 0
#define REVISION_WALK_NO_WALK_SORTED 1
#define REVISION_WALK_NO_WALK_UNSORTED 2
struct oidset; struct oidset;
struct topo_walk_info; struct topo_walk_info;
@ -129,7 +125,8 @@ struct rev_info {
/* Traversal flags */ /* Traversal flags */
unsigned int dense:1, unsigned int dense:1,
prune:1, prune:1,
no_walk:2, no_walk:1,
unsorted_input:1,
remove_empty_trees:1, remove_empty_trees:1,
simplify_history:1, simplify_history:1,
show_pulls:1, show_pulls:1,

View File

@ -169,4 +169,35 @@ test_expect_success 'rev-list --count --objects' '
test_line_count = $count actual test_line_count = $count actual
' '
test_expect_success 'rev-list --unsorted-input results in different sorting' '
git rev-list --unsorted-input HEAD HEAD~ >first &&
git rev-list --unsorted-input HEAD~ HEAD >second &&
! test_cmp first second &&
sort first >first.sorted &&
sort second >second.sorted &&
test_cmp first.sorted second.sorted
'
test_expect_success 'rev-list --unsorted-input incompatible with --no-walk' '
cat >expect <<-EOF &&
fatal: --no-walk is incompatible with --unsorted-input
EOF
test_must_fail git rev-list --unsorted-input --no-walk HEAD 2>error &&
test_cmp expect error &&
test_must_fail git rev-list --unsorted-input --no-walk=sorted HEAD 2>error &&
test_cmp expect error &&
test_must_fail git rev-list --unsorted-input --no-walk=unsorted HEAD 2>error &&
test_cmp expect error &&
cat >expect <<-EOF &&
fatal: --unsorted-input is incompatible with --no-walk
EOF
test_must_fail git rev-list --no-walk --unsorted-input HEAD 2>error &&
test_cmp expect error &&
test_must_fail git rev-list --no-walk=sorted --unsorted-input HEAD 2>error &&
test_cmp expect error &&
test_must_fail git rev-list --no-walk=unsorted --unsorted-input HEAD 2>error &&
test_cmp expect error
'
test_done test_done