Skip to content

Commit 2a2e32b

Browse files
derrickstoleegitster
authored andcommitted
commit-graph: implement git commit-graph read
Teach git-commit-graph to read commit graph files and summarize their contents. Use the read subcommand to verify the contents of a commit graph file in the tests. Signed-off-by: Derrick Stolee <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent f237c8b commit 2a2e32b

File tree

5 files changed

+254
-6
lines changed

5 files changed

+254
-6
lines changed

Documentation/git-commit-graph.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ git-commit-graph - Write and verify Git commit graph files
99
SYNOPSIS
1010
--------
1111
[verse]
12+
'git commit-graph read' [--object-dir <dir>]
1213
'git commit-graph write' <options> [--object-dir <dir>]
1314

1415

@@ -35,6 +36,11 @@ COMMANDS
3536
Write a commit graph file based on the commits found in packfiles.
3637
Includes all commits from the existing commit graph file.
3738

39+
'read'::
40+
41+
Read a graph file given by the commit-graph file and output basic
42+
details about the graph file. Used for debugging purposes.
43+
3844

3945
EXAMPLES
4046
--------
@@ -45,6 +51,12 @@ EXAMPLES
4551
$ git commit-graph write
4652
------------------------------------------------
4753

54+
* Read basic information from the commit-graph file.
55+
+
56+
------------------------------------------------
57+
$ git commit-graph read
58+
------------------------------------------------
59+
4860

4961
GIT
5062
---

builtin/commit-graph.c

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@
77

88
static char const * const builtin_commit_graph_usage[] = {
99
N_("git commit-graph [--object-dir <objdir>]"),
10+
N_("git commit-graph read [--object-dir <objdir>]"),
1011
N_("git commit-graph write [--object-dir <objdir>]"),
1112
NULL
1213
};
1314

15+
static const char * const builtin_commit_graph_read_usage[] = {
16+
N_("git commit-graph read [--object-dir <objdir>]"),
17+
NULL
18+
};
19+
1420
static const char * const builtin_commit_graph_write_usage[] = {
1521
N_("git commit-graph write [--object-dir <objdir>]"),
1622
NULL
@@ -20,6 +26,54 @@ static struct opts_commit_graph {
2026
const char *obj_dir;
2127
} opts;
2228

29+
static int graph_read(int argc, const char **argv)
30+
{
31+
struct commit_graph *graph = NULL;
32+
char *graph_name;
33+
34+
static struct option builtin_commit_graph_read_options[] = {
35+
OPT_STRING(0, "object-dir", &opts.obj_dir,
36+
N_("dir"),
37+
N_("The object directory to store the graph")),
38+
OPT_END(),
39+
};
40+
41+
argc = parse_options(argc, argv, NULL,
42+
builtin_commit_graph_read_options,
43+
builtin_commit_graph_read_usage, 0);
44+
45+
if (!opts.obj_dir)
46+
opts.obj_dir = get_object_directory();
47+
48+
graph_name = get_commit_graph_filename(opts.obj_dir);
49+
graph = load_commit_graph_one(graph_name);
50+
51+
if (!graph)
52+
die("graph file %s does not exist", graph_name);
53+
FREE_AND_NULL(graph_name);
54+
55+
printf("header: %08x %d %d %d %d\n",
56+
ntohl(*(uint32_t*)graph->data),
57+
*(unsigned char*)(graph->data + 4),
58+
*(unsigned char*)(graph->data + 5),
59+
*(unsigned char*)(graph->data + 6),
60+
*(unsigned char*)(graph->data + 7));
61+
printf("num_commits: %u\n", graph->num_commits);
62+
printf("chunks:");
63+
64+
if (graph->chunk_oid_fanout)
65+
printf(" oid_fanout");
66+
if (graph->chunk_oid_lookup)
67+
printf(" oid_lookup");
68+
if (graph->chunk_commit_data)
69+
printf(" commit_metadata");
70+
if (graph->chunk_large_edges)
71+
printf(" large_edges");
72+
printf("\n");
73+
74+
return 0;
75+
}
76+
2377
static int graph_write(int argc, const char **argv)
2478
{
2579
static struct option builtin_commit_graph_write_options[] = {
@@ -60,6 +114,8 @@ int cmd_commit_graph(int argc, const char **argv, const char *prefix)
60114
PARSE_OPT_STOP_AT_NON_OPTION);
61115

62116
if (argc > 0) {
117+
if (!strcmp(argv[0], "read"))
118+
return graph_read(argc, argv);
63119
if (!strcmp(argv[0], "write"))
64120
return graph_write(argc, argv);
65121
}

commit-graph.c

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,146 @@
3939
GRAPH_OID_LEN + 8)
4040

4141

42-
static char *get_commit_graph_filename(const char *obj_dir)
42+
char *get_commit_graph_filename(const char *obj_dir)
4343
{
4444
return xstrfmt("%s/info/commit-graph", obj_dir);
4545
}
4646

47+
static struct commit_graph *alloc_commit_graph(void)
48+
{
49+
struct commit_graph *g = xcalloc(1, sizeof(*g));
50+
g->graph_fd = -1;
51+
52+
return g;
53+
}
54+
55+
struct commit_graph *load_commit_graph_one(const char *graph_file)
56+
{
57+
void *graph_map;
58+
const unsigned char *data, *chunk_lookup;
59+
size_t graph_size;
60+
struct stat st;
61+
uint32_t i;
62+
struct commit_graph *graph;
63+
int fd = git_open(graph_file);
64+
uint64_t last_chunk_offset;
65+
uint32_t last_chunk_id;
66+
uint32_t graph_signature;
67+
unsigned char graph_version, hash_version;
68+
69+
if (fd < 0)
70+
return NULL;
71+
if (fstat(fd, &st)) {
72+
close(fd);
73+
return NULL;
74+
}
75+
graph_size = xsize_t(st.st_size);
76+
77+
if (graph_size < GRAPH_MIN_SIZE) {
78+
close(fd);
79+
die("graph file %s is too small", graph_file);
80+
}
81+
graph_map = xmmap(NULL, graph_size, PROT_READ, MAP_PRIVATE, fd, 0);
82+
data = (const unsigned char *)graph_map;
83+
84+
graph_signature = get_be32(data);
85+
if (graph_signature != GRAPH_SIGNATURE) {
86+
error("graph signature %X does not match signature %X",
87+
graph_signature, GRAPH_SIGNATURE);
88+
goto cleanup_fail;
89+
}
90+
91+
graph_version = *(unsigned char*)(data + 4);
92+
if (graph_version != GRAPH_VERSION) {
93+
error("graph version %X does not match version %X",
94+
graph_version, GRAPH_VERSION);
95+
goto cleanup_fail;
96+
}
97+
98+
hash_version = *(unsigned char*)(data + 5);
99+
if (hash_version != GRAPH_OID_VERSION) {
100+
error("hash version %X does not match version %X",
101+
hash_version, GRAPH_OID_VERSION);
102+
goto cleanup_fail;
103+
}
104+
105+
graph = alloc_commit_graph();
106+
107+
graph->hash_len = GRAPH_OID_LEN;
108+
graph->num_chunks = *(unsigned char*)(data + 6);
109+
graph->graph_fd = fd;
110+
graph->data = graph_map;
111+
graph->data_len = graph_size;
112+
113+
last_chunk_id = 0;
114+
last_chunk_offset = 8;
115+
chunk_lookup = data + 8;
116+
for (i = 0; i < graph->num_chunks; i++) {
117+
uint32_t chunk_id = get_be32(chunk_lookup + 0);
118+
uint64_t chunk_offset = get_be64(chunk_lookup + 4);
119+
int chunk_repeated = 0;
120+
121+
chunk_lookup += GRAPH_CHUNKLOOKUP_WIDTH;
122+
123+
if (chunk_offset > graph_size - GIT_MAX_RAWSZ) {
124+
error("improper chunk offset %08x%08x", (uint32_t)(chunk_offset >> 32),
125+
(uint32_t)chunk_offset);
126+
goto cleanup_fail;
127+
}
128+
129+
switch (chunk_id) {
130+
case GRAPH_CHUNKID_OIDFANOUT:
131+
if (graph->chunk_oid_fanout)
132+
chunk_repeated = 1;
133+
else
134+
graph->chunk_oid_fanout = (uint32_t*)(data + chunk_offset);
135+
break;
136+
137+
case GRAPH_CHUNKID_OIDLOOKUP:
138+
if (graph->chunk_oid_lookup)
139+
chunk_repeated = 1;
140+
else
141+
graph->chunk_oid_lookup = data + chunk_offset;
142+
break;
143+
144+
case GRAPH_CHUNKID_DATA:
145+
if (graph->chunk_commit_data)
146+
chunk_repeated = 1;
147+
else
148+
graph->chunk_commit_data = data + chunk_offset;
149+
break;
150+
151+
case GRAPH_CHUNKID_LARGEEDGES:
152+
if (graph->chunk_large_edges)
153+
chunk_repeated = 1;
154+
else
155+
graph->chunk_large_edges = data + chunk_offset;
156+
break;
157+
}
158+
159+
if (chunk_repeated) {
160+
error("chunk id %08x appears multiple times", chunk_id);
161+
goto cleanup_fail;
162+
}
163+
164+
if (last_chunk_id == GRAPH_CHUNKID_OIDLOOKUP)
165+
{
166+
graph->num_commits = (chunk_offset - last_chunk_offset)
167+
/ graph->hash_len;
168+
}
169+
170+
last_chunk_id = chunk_id;
171+
last_chunk_offset = chunk_offset;
172+
}
173+
174+
return graph;
175+
176+
cleanup_fail:
177+
munmap(graph_map, graph_size);
178+
close(fd);
179+
exit(1);
180+
}
181+
47182
static void write_graph_chunk_fanout(struct hashfile *f,
48183
struct commit **commits,
49184
int nr_commits)

commit-graph.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,29 @@
11
#ifndef COMMIT_GRAPH_H
22
#define COMMIT_GRAPH_H
33

4+
#include "git-compat-util.h"
5+
6+
char *get_commit_graph_filename(const char *obj_dir);
7+
8+
struct commit_graph {
9+
int graph_fd;
10+
11+
const unsigned char *data;
12+
size_t data_len;
13+
14+
unsigned char hash_len;
15+
unsigned char num_chunks;
16+
uint32_t num_commits;
17+
struct object_id oid;
18+
19+
const uint32_t *chunk_oid_fanout;
20+
const unsigned char *chunk_oid_lookup;
21+
const unsigned char *chunk_commit_data;
22+
const unsigned char *chunk_large_edges;
23+
};
24+
25+
struct commit_graph *load_commit_graph_one(const char *graph_file);
26+
427
void write_commit_graph(const char *obj_dir);
528

629
#endif

t/t5318-commit-graph.sh

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,28 @@ test_expect_success 'create commits and repack' '
2626
git repack
2727
'
2828

29+
graph_read_expect() {
30+
OPTIONAL=""
31+
NUM_CHUNKS=3
32+
if test ! -z $2
33+
then
34+
OPTIONAL=" $2"
35+
NUM_CHUNKS=$((3 + $(echo "$2" | wc -w)))
36+
fi
37+
cat >expect <<- EOF
38+
header: 43475048 1 1 $NUM_CHUNKS 0
39+
num_commits: $1
40+
chunks: oid_fanout oid_lookup commit_metadata$OPTIONAL
41+
EOF
42+
git commit-graph read >output &&
43+
test_cmp expect output
44+
}
45+
2946
test_expect_success 'write graph' '
3047
cd "$TRASH_DIRECTORY/full" &&
3148
graph1=$(git commit-graph write) &&
32-
test_path_is_file $objdir/info/commit-graph
49+
test_path_is_file $objdir/info/commit-graph &&
50+
graph_read_expect "3"
3351
'
3452

3553
test_expect_success 'Add more commits' '
@@ -72,7 +90,8 @@ test_expect_success 'Add more commits' '
7290
test_expect_success 'write graph with merges' '
7391
cd "$TRASH_DIRECTORY/full" &&
7492
git commit-graph write &&
75-
test_path_is_file $objdir/info/commit-graph
93+
test_path_is_file $objdir/info/commit-graph &&
94+
graph_read_expect "10" "large_edges"
7695
'
7796

7897
test_expect_success 'Add one more commit' '
@@ -99,13 +118,15 @@ test_expect_success 'Add one more commit' '
99118
test_expect_success 'write graph with new commit' '
100119
cd "$TRASH_DIRECTORY/full" &&
101120
git commit-graph write &&
102-
test_path_is_file $objdir/info/commit-graph
121+
test_path_is_file $objdir/info/commit-graph &&
122+
graph_read_expect "11" "large_edges"
103123
'
104124

105125
test_expect_success 'write graph with nothing new' '
106126
cd "$TRASH_DIRECTORY/full" &&
107127
git commit-graph write &&
108-
test_path_is_file $objdir/info/commit-graph
128+
test_path_is_file $objdir/info/commit-graph &&
129+
graph_read_expect "11" "large_edges"
109130
'
110131

111132
test_expect_success 'setup bare repo' '
@@ -118,7 +139,8 @@ test_expect_success 'setup bare repo' '
118139
test_expect_success 'write graph in bare repo' '
119140
cd "$TRASH_DIRECTORY/bare" &&
120141
git commit-graph write &&
121-
test_path_is_file $baredir/info/commit-graph
142+
test_path_is_file $baredir/info/commit-graph &&
143+
graph_read_expect "11" "large_edges"
122144
'
123145

124146
test_done

0 commit comments

Comments
 (0)