commit: integrate commit graph with commit parsing

Teach Git to inspect a commit graph file to supply the contents of a
struct commit when calling parse_commit_gently(). This implementation
satisfies all post-conditions on the struct commit, including loading
parents, the root tree, and the commit date.

If core.commitGraph is false, then do not check graph files.

In test script t5318-commit-graph.sh, add output-matching conditions on
read-only graph operations.

By loading commits from the graph instead of parsing commit buffers, we
save a lot of time on long commit walks. Here are some performance
results for a copy of the Linux repository where 'master' has 678,653
reachable commits and is behind 'origin/master' by 59,929 commits.

| Command                          | Before | After  | Rel % |
|----------------------------------|--------|--------|-------|
| log --oneline --topo-order -1000 |  8.31s |  0.94s | -88%  |
| branch -vv                       |  1.02s |  0.14s | -86%  |
| rev-list --all                   |  5.89s |  1.07s | -81%  |
| rev-list --all --objects         | 66.15s | 58.45s | -11%  |

Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Derrick Stolee
2018-04-10 08:56:05 -04:00
committed by Junio C Hamano
parent 4f2542b49e
commit 177722b344
6 changed files with 205 additions and 2 deletions

View File

@ -38,7 +38,6 @@
#define GRAPH_MIN_SIZE (5 * GRAPH_CHUNKLOOKUP_WIDTH + GRAPH_FANOUT_SIZE + \
GRAPH_OID_LEN + 8)
char *get_commit_graph_filename(const char *obj_dir)
{
return xstrfmt("%s/info/commit-graph", obj_dir);
@ -179,6 +178,145 @@ cleanup_fail:
exit(1);
}
/* global storage */
static struct commit_graph *commit_graph = NULL;
static void prepare_commit_graph_one(const char *obj_dir)
{
char *graph_name;
if (commit_graph)
return;
graph_name = get_commit_graph_filename(obj_dir);
commit_graph = load_commit_graph_one(graph_name);
FREE_AND_NULL(graph_name);
}
static int prepare_commit_graph_run_once = 0;
static void prepare_commit_graph(void)
{
struct alternate_object_database *alt;
char *obj_dir;
if (prepare_commit_graph_run_once)
return;
prepare_commit_graph_run_once = 1;
obj_dir = get_object_directory();
prepare_commit_graph_one(obj_dir);
prepare_alt_odb();
for (alt = alt_odb_list; !commit_graph && alt; alt = alt->next)
prepare_commit_graph_one(alt->path);
}
static void close_commit_graph(void)
{
if (!commit_graph)
return;
if (commit_graph->graph_fd >= 0) {
munmap((void *)commit_graph->data, commit_graph->data_len);
commit_graph->data = NULL;
close(commit_graph->graph_fd);
}
FREE_AND_NULL(commit_graph);
}
static int bsearch_graph(struct commit_graph *g, struct object_id *oid, uint32_t *pos)
{
return bsearch_hash(oid->hash, g->chunk_oid_fanout,
g->chunk_oid_lookup, g->hash_len, pos);
}
static struct commit_list **insert_parent_or_die(struct commit_graph *g,
uint64_t pos,
struct commit_list **pptr)
{
struct commit *c;
struct object_id oid;
hashcpy(oid.hash, g->chunk_oid_lookup + g->hash_len * pos);
c = lookup_commit(&oid);
if (!c)
die("could not find commit %s", oid_to_hex(&oid));
c->graph_pos = pos;
return &commit_list_insert(c, pptr)->next;
}
static int fill_commit_in_graph(struct commit *item, struct commit_graph *g, uint32_t pos)
{
struct object_id oid;
uint32_t edge_value;
uint32_t *parent_data_ptr;
uint64_t date_low, date_high;
struct commit_list **pptr;
const unsigned char *commit_data = g->chunk_commit_data + (g->hash_len + 16) * pos;
item->object.parsed = 1;
item->graph_pos = pos;
hashcpy(oid.hash, commit_data);
item->tree = lookup_tree(&oid);
date_high = get_be32(commit_data + g->hash_len + 8) & 0x3;
date_low = get_be32(commit_data + g->hash_len + 12);
item->date = (timestamp_t)((date_high << 32) | date_low);
pptr = &item->parents;
edge_value = get_be32(commit_data + g->hash_len);
if (edge_value == GRAPH_PARENT_NONE)
return 1;
pptr = insert_parent_or_die(g, edge_value, pptr);
edge_value = get_be32(commit_data + g->hash_len + 4);
if (edge_value == GRAPH_PARENT_NONE)
return 1;
if (!(edge_value & GRAPH_OCTOPUS_EDGES_NEEDED)) {
pptr = insert_parent_or_die(g, edge_value, pptr);
return 1;
}
parent_data_ptr = (uint32_t*)(g->chunk_large_edges +
4 * (uint64_t)(edge_value & GRAPH_EDGE_LAST_MASK));
do {
edge_value = get_be32(parent_data_ptr);
pptr = insert_parent_or_die(g,
edge_value & GRAPH_EDGE_LAST_MASK,
pptr);
parent_data_ptr++;
} while (!(edge_value & GRAPH_LAST_EDGE));
return 1;
}
int parse_commit_in_graph(struct commit *item)
{
if (!core_commit_graph)
return 0;
if (item->object.parsed)
return 1;
prepare_commit_graph();
if (commit_graph) {
uint32_t pos;
int found;
if (item->graph_pos != COMMIT_NOT_FROM_GRAPH) {
pos = item->graph_pos;
found = 1;
} else {
found = bsearch_graph(commit_graph, &(item->object.oid), &pos);
}
if (found)
return fill_commit_in_graph(item, commit_graph, pos);
}
return 0;
}
static void write_graph_chunk_fanout(struct hashfile *f,
struct commit **commits,
int nr_commits)
@ -530,6 +668,7 @@ void write_commit_graph(const char *obj_dir)
write_graph_chunk_data(f, GRAPH_OID_LEN, commits.list, commits.nr);
write_graph_chunk_large_edges(f, commits.list, commits.nr);
close_commit_graph();
finalize_hashfile(f, NULL, CSUM_HASH_IN_STREAM | CSUM_FSYNC);
commit_lock_file(&lk);