Skip to content

Commit b99b5a8

Browse files
authored
Merge pull request libgit2#5763 from lhchavez/cgraph-lookup
commit-graph: Support lookups of entries in a commit-graph
2 parents b33e018 + 1f32ed2 commit b99b5a8

File tree

5 files changed

+252
-0
lines changed

5 files changed

+252
-0
lines changed

fuzzers/commit_graph_fuzzer.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ int LLVMFuzzerInitialize(int *argc, char ***argv)
3232
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
3333
{
3434
git_commit_graph_file cgraph = {{0}};
35+
git_commit_graph_entry e;
3536
git_buf commit_graph_buf = GIT_BUF_INIT;
3637
git_oid oid = {{0}};
3738
bool append_hash = false;
@@ -68,6 +69,10 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
6869
< 0)
6970
goto cleanup;
7071

72+
/* Search for any oid, just to exercise that codepath. */
73+
if (git_commit_graph_entry_find(&e, &cgraph, &oid, GIT_OID_HEXSZ) < 0)
74+
goto cleanup;
75+
7176
cleanup:
7277
git_commit_graph_close(&cgraph);
7378
git_buf_dispose(&commit_graph_buf);

src/commit_graph.c

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include "futils.h"
1111
#include "hash.h"
12+
#include "pack.h"
1213

1314
#define GIT_COMMIT_GRAPH_MISSING_PARENT 0x70000000
1415

@@ -278,6 +279,142 @@ int git_commit_graph_open(git_commit_graph_file **cgraph_out, const char *path)
278279
return 0;
279280
}
280281

282+
static int git_commit_graph_entry_get_byindex(
283+
git_commit_graph_entry *e,
284+
const git_commit_graph_file *cgraph,
285+
size_t pos)
286+
{
287+
const unsigned char *commit_data;
288+
289+
GIT_ASSERT_ARG(e);
290+
GIT_ASSERT_ARG(cgraph);
291+
292+
if (pos >= cgraph->num_commits) {
293+
git_error_set(GIT_ERROR_INVALID, "commit index %zu does not exist", pos);
294+
return GIT_ENOTFOUND;
295+
}
296+
297+
commit_data = cgraph->commit_data + pos * (GIT_OID_RAWSZ + 4 * sizeof(uint32_t));
298+
git_oid_cpy(&e->tree_oid, (const git_oid *)commit_data);
299+
e->parent_indices[0] = ntohl(*((uint32_t *)(commit_data + GIT_OID_RAWSZ)));
300+
e->parent_indices[1]
301+
= ntohl(*((uint32_t *)(commit_data + GIT_OID_RAWSZ + sizeof(uint32_t))));
302+
e->parent_count = (e->parent_indices[0] != GIT_COMMIT_GRAPH_MISSING_PARENT)
303+
+ (e->parent_indices[1] != GIT_COMMIT_GRAPH_MISSING_PARENT);
304+
e->generation = ntohl(*((uint32_t *)(commit_data + GIT_OID_RAWSZ + 2 * sizeof(uint32_t))));
305+
e->commit_time = ntohl(*((uint32_t *)(commit_data + GIT_OID_RAWSZ + 3 * sizeof(uint32_t))));
306+
307+
e->commit_time |= (e->generation & 0x3ull) << 32ull;
308+
e->generation >>= 2u;
309+
if (e->parent_indices[1] & 0x80000000u) {
310+
uint32_t extra_edge_list_pos = e->parent_indices[1] & 0x7fffffff;
311+
312+
/* Make sure we're not being sent out of bounds */
313+
if (extra_edge_list_pos >= cgraph->num_extra_edge_list) {
314+
git_error_set(GIT_ERROR_INVALID,
315+
"commit %u does not exist",
316+
extra_edge_list_pos);
317+
return GIT_ENOTFOUND;
318+
}
319+
320+
e->extra_parents_index = extra_edge_list_pos;
321+
while (extra_edge_list_pos < cgraph->num_extra_edge_list
322+
&& (ntohl(*(
323+
(uint32_t *)(cgraph->extra_edge_list
324+
+ extra_edge_list_pos * sizeof(uint32_t))))
325+
& 0x80000000u)
326+
== 0) {
327+
extra_edge_list_pos++;
328+
e->parent_count++;
329+
}
330+
331+
}
332+
git_oid_cpy(&e->sha1, &cgraph->oid_lookup[pos]);
333+
return 0;
334+
}
335+
336+
int git_commit_graph_entry_find(
337+
git_commit_graph_entry *e,
338+
const git_commit_graph_file *cgraph,
339+
const git_oid *short_oid,
340+
size_t len)
341+
{
342+
int pos, found = 0;
343+
uint32_t hi, lo;
344+
const git_oid *current = NULL;
345+
346+
GIT_ASSERT_ARG(e);
347+
GIT_ASSERT_ARG(cgraph);
348+
GIT_ASSERT_ARG(short_oid);
349+
350+
hi = ntohl(cgraph->oid_fanout[(int)short_oid->id[0]]);
351+
lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(cgraph->oid_fanout[(int)short_oid->id[0] - 1]));
352+
353+
pos = git_pack__lookup_sha1(cgraph->oid_lookup, GIT_OID_RAWSZ, lo, hi, short_oid->id);
354+
355+
if (pos >= 0) {
356+
/* An object matching exactly the oid was found */
357+
found = 1;
358+
current = cgraph->oid_lookup + pos;
359+
} else {
360+
/* No object was found */
361+
/* pos refers to the object with the "closest" oid to short_oid */
362+
pos = -1 - pos;
363+
if (pos < (int)cgraph->num_commits) {
364+
current = cgraph->oid_lookup + pos;
365+
366+
if (!git_oid_ncmp(short_oid, current, len))
367+
found = 1;
368+
}
369+
}
370+
371+
if (found && len != GIT_OID_HEXSZ && pos + 1 < (int)cgraph->num_commits) {
372+
/* Check for ambiguousity */
373+
const git_oid *next = current + 1;
374+
375+
if (!git_oid_ncmp(short_oid, next, len)) {
376+
found = 2;
377+
}
378+
}
379+
380+
if (!found)
381+
return git_odb__error_notfound(
382+
"failed to find offset for multi-pack index entry", short_oid, len);
383+
if (found > 1)
384+
return git_odb__error_ambiguous(
385+
"found multiple offsets for multi-pack index entry");
386+
387+
return git_commit_graph_entry_get_byindex(e, cgraph, pos);
388+
}
389+
390+
int git_commit_graph_entry_parent(
391+
git_commit_graph_entry *parent,
392+
const git_commit_graph_file *cgraph,
393+
const git_commit_graph_entry *entry,
394+
size_t n)
395+
{
396+
GIT_ASSERT_ARG(parent);
397+
GIT_ASSERT_ARG(cgraph);
398+
399+
if (n >= entry->parent_count) {
400+
git_error_set(GIT_ERROR_INVALID, "parent index %zu does not exist", n);
401+
return GIT_ENOTFOUND;
402+
}
403+
404+
if (n == 0 || (n == 1 && entry->parent_count == 2))
405+
return git_commit_graph_entry_get_byindex(parent, cgraph, entry->parent_indices[n]);
406+
407+
return git_commit_graph_entry_get_byindex(
408+
parent,
409+
cgraph,
410+
ntohl(
411+
*(uint32_t *)(cgraph->extra_edge_list
412+
+ (entry->extra_parents_index + n - 1)
413+
* sizeof(uint32_t)))
414+
& 0x7fffffff);
415+
}
416+
417+
281418
int git_commit_graph_close(git_commit_graph_file *cgraph)
282419
{
283420
GIT_ASSERT_ARG(cgraph);

src/commit_graph.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,48 @@ typedef struct git_commit_graph_file {
5757
git_buf filename;
5858
} git_commit_graph_file;
5959

60+
/**
61+
* An entry in the commit-graph file. Provides a subset of the information that
62+
* can be obtained from the commit header.
63+
*/
64+
typedef struct git_commit_graph_entry {
65+
/* The generation number of the commit within the graph */
66+
size_t generation;
67+
68+
/* Time in seconds from UNIX epoch. */
69+
git_time_t commit_time;
70+
71+
/* The number of parents of the commit. */
72+
size_t parent_count;
73+
74+
/*
75+
* The indices of the parent commits within the Commit Data table. The value
76+
* of `GIT_COMMIT_GRAPH_MISSING_PARENT` indicates that no parent is in that
77+
* position.
78+
*/
79+
size_t parent_indices[2];
80+
81+
/* The index within the Extra Edge List of any parent after the first two. */
82+
size_t extra_parents_index;
83+
84+
/* The SHA-1 hash of the root tree of the commit. */
85+
git_oid tree_oid;
86+
87+
/* The SHA-1 hash of the requested commit. */
88+
git_oid sha1;
89+
} git_commit_graph_entry;
90+
6091
int git_commit_graph_open(git_commit_graph_file **cgraph_out, const char *path);
92+
int git_commit_graph_entry_find(
93+
git_commit_graph_entry *e,
94+
const git_commit_graph_file *cgraph,
95+
const git_oid *short_oid,
96+
size_t len);
97+
int git_commit_graph_entry_parent(
98+
git_commit_graph_entry *parent,
99+
const git_commit_graph_file *cgraph,
100+
const git_commit_graph_entry *entry,
101+
size_t n);
61102
int git_commit_graph_close(git_commit_graph_file *cgraph);
62103
void git_commit_graph_free(git_commit_graph_file *cgraph);
63104

tests/graph/commit_graph.c

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,81 @@ void test_graph_commit_graph__parse(void)
88
{
99
git_repository *repo;
1010
struct git_commit_graph_file *cgraph;
11+
struct git_commit_graph_entry e, parent;
12+
git_oid id;
1113
git_buf commit_graph_path = GIT_BUF_INIT;
1214

1315
cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
1416
cl_git_pass(git_buf_joinpath(&commit_graph_path, git_repository_path(repo), "objects/info/commit-graph"));
1517
cl_git_pass(git_commit_graph_open(&cgraph, git_buf_cstr(&commit_graph_path)));
1618

19+
cl_git_pass(git_oid_fromstr(&id, "5001298e0c09ad9c34e4249bc5801c75e9754fa5"));
20+
cl_git_pass(git_commit_graph_entry_find(&e, cgraph, &id, GIT_OID_HEXSZ));
21+
cl_assert_equal_oid(&e.sha1, &id);
22+
cl_git_pass(git_oid_fromstr(&id, "418382dff1ffb8bdfba833f4d8bbcde58b1e7f47"));
23+
cl_assert_equal_oid(&e.tree_oid, &id);
24+
cl_assert_equal_i(e.generation, 1);
25+
cl_assert_equal_i(e.commit_time, 1273610423ull);
26+
cl_assert_equal_i(e.parent_count, 0);
27+
28+
cl_git_pass(git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"));
29+
cl_git_pass(git_commit_graph_entry_find(&e, cgraph, &id, GIT_OID_HEXSZ));
30+
cl_assert_equal_oid(&e.sha1, &id);
31+
cl_assert_equal_i(e.generation, 5);
32+
cl_assert_equal_i(e.commit_time, 1274813907ull);
33+
cl_assert_equal_i(e.parent_count, 2);
34+
35+
cl_git_pass(git_oid_fromstr(&id, "9fd738e8f7967c078dceed8190330fc8648ee56a"));
36+
cl_git_pass(git_commit_graph_entry_parent(&parent, cgraph, &e, 0));
37+
cl_assert_equal_oid(&parent.sha1, &id);
38+
cl_assert_equal_i(parent.generation, 4);
39+
40+
cl_git_pass(git_oid_fromstr(&id, "c47800c7266a2be04c571c04d5a6614691ea99bd"));
41+
cl_git_pass(git_commit_graph_entry_parent(&parent, cgraph, &e, 1));
42+
cl_assert_equal_oid(&parent.sha1, &id);
43+
cl_assert_equal_i(parent.generation, 3);
44+
45+
git_commit_graph_free(cgraph);
46+
git_repository_free(repo);
47+
git_buf_dispose(&commit_graph_path);
48+
}
49+
50+
void test_graph_commit_graph__parse_octopus_merge(void)
51+
{
52+
git_repository *repo;
53+
struct git_commit_graph_file *cgraph;
54+
struct git_commit_graph_entry e, parent;
55+
git_oid id;
56+
git_buf commit_graph_path = GIT_BUF_INIT;
57+
58+
cl_git_pass(git_repository_open(&repo, cl_fixture("merge-recursive/.gitted")));
59+
cl_git_pass(git_buf_joinpath(&commit_graph_path, git_repository_path(repo), "objects/info/commit-graph"));
60+
cl_git_pass(git_commit_graph_open(&cgraph, git_buf_cstr(&commit_graph_path)));
61+
62+
cl_git_pass(git_oid_fromstr(&id, "d71c24b3b113fd1d1909998c5bfe33b86a65ee03"));
63+
cl_git_pass(git_commit_graph_entry_find(&e, cgraph, &id, GIT_OID_HEXSZ));
64+
cl_assert_equal_oid(&e.sha1, &id);
65+
cl_git_pass(git_oid_fromstr(&id, "348f16ffaeb73f319a75cec5b16a0a47d2d5e27c"));
66+
cl_assert_equal_oid(&e.tree_oid, &id);
67+
cl_assert_equal_i(e.generation, 7);
68+
cl_assert_equal_i(e.commit_time, 1447083009ull);
69+
cl_assert_equal_i(e.parent_count, 3);
70+
71+
cl_git_pass(git_oid_fromstr(&id, "ad2ace9e15f66b3d1138922e6ffdc3ea3f967fa6"));
72+
cl_git_pass(git_commit_graph_entry_parent(&parent, cgraph, &e, 0));
73+
cl_assert_equal_oid(&parent.sha1, &id);
74+
cl_assert_equal_i(parent.generation, 6);
75+
76+
cl_git_pass(git_oid_fromstr(&id, "483065df53c0f4a02cdc6b2910b05d388fc17ffb"));
77+
cl_git_pass(git_commit_graph_entry_parent(&parent, cgraph, &e, 1));
78+
cl_assert_equal_oid(&parent.sha1, &id);
79+
cl_assert_equal_i(parent.generation, 2);
80+
81+
cl_git_pass(git_oid_fromstr(&id, "815b5a1c80ca749d705c7aa0cb294a00cbedd340"));
82+
cl_git_pass(git_commit_graph_entry_parent(&parent, cgraph, &e, 2));
83+
cl_assert_equal_oid(&parent.sha1, &id);
84+
cl_assert_equal_i(parent.generation, 6);
85+
1786
git_commit_graph_free(cgraph);
1887
git_repository_free(repo);
1988
git_buf_dispose(&commit_graph_path);
Binary file not shown.

0 commit comments

Comments
 (0)