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 <dstolee@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Derrick Stolee committed Apr 10, 2018 at 08:56 UTC 2a2e32bdc5a80221981939e77643cec3462b4793
5 files changed +254 -6
Documentation/git-commit-graph.txt
+12
@@ -9,6 +9,7 @@ git-commit-graph - Write and verify Git commit graph files
9 SYNOPSIS
10 --------
11 [verse]
12 +'git commit-graph read' [--object-dir <dir>]
13 'git commit-graph write' <options> [--object-dir <dir>]
14
15
@@ -35,6 +36,11 @@ COMMANDS
36 Write a commit graph file based on the commits found in packfiles.
37 Includes all commits from the existing commit graph file.
38
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 +
44
45 EXAMPLES
46 --------
@@ -45,6 +51,12 @@ EXAMPLES
51 $ git commit-graph write
52 ------------------------------------------------
53
54 +* Read basic information from the commit-graph file.
55 ++
56 +------------------------------------------------
57 +$ git commit-graph read
58 +------------------------------------------------
59 +
60
61 GIT
62 ---
builtin/commit-graph.c
+56
@@ -7,10 +7,16 @@
7
8 static char const * const builtin_commit_graph_usage[] = {
9 N_("git commit-graph [--object-dir <objdir>]"),
10 + N_("git commit-graph read [--object-dir <objdir>]"),
11 N_("git commit-graph write [--object-dir <objdir>]"),
12 NULL
13 };
14
15 +static const char * const builtin_commit_graph_read_usage[] = {
16 + N_("git commit-graph read [--object-dir <objdir>]"),
17 + NULL
18 +};
19 +
20 static const char * const builtin_commit_graph_write_usage[] = {
21 N_("git commit-graph write [--object-dir <objdir>]"),
22 NULL
@@ -20,6 +26,54 @@ static struct opts_commit_graph {
26 const char *obj_dir;
27 } opts;
28
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 +
77 static int graph_write(int argc, const char **argv)
78 {
79 static struct option builtin_commit_graph_write_options[] = {
@@ -60,6 +114,8 @@ int cmd_commit_graph(int argc, const char **argv, const char *prefix)
114 PARSE_OPT_STOP_AT_NON_OPTION);
115
116 if (argc > 0) {
117 + if (!strcmp(argv[0], "read"))
118 + return graph_read(argc, argv);
119 if (!strcmp(argv[0], "write"))
120 return graph_write(argc, argv);
121 }
commit-graph.c
+136 -1
@@ -39,11 +39,146 @@
39 GRAPH_OID_LEN + 8)
40
41
42 -static char *get_commit_graph_filename(const char *obj_dir)
42 +char *get_commit_graph_filename(const char *obj_dir)
43 {
44 return xstrfmt("%s/info/commit-graph", obj_dir);
45 }
46
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 +
182 static void write_graph_chunk_fanout(struct hashfile *f,
183 struct commit **commits,
184 int nr_commits)
commit-graph.h
+23
@@ -1,6 +1,29 @@
1 #ifndef COMMIT_GRAPH_H
2 #define COMMIT_GRAPH_H
3
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 +
27 void write_commit_graph(const char *obj_dir);
28
29 #endif
t/t5318-commit-graph.sh
+27 -5
@@ -26,10 +26,28 @@ test_expect_success 'create commits and repack' '
26 git repack
27 '
28
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 +
46 test_expect_success 'write graph' '
47 cd "$TRASH_DIRECTORY/full" &&
48 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"
51 '
52
53 test_expect_success 'Add more commits' '
@@ -72,7 +90,8 @@ test_expect_success 'Add more commits' '
90 test_expect_success 'write graph with merges' '
91 cd "$TRASH_DIRECTORY/full" &&
92 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"
95 '
96
97 test_expect_success 'Add one more commit' '
@@ -99,13 +118,15 @@ test_expect_success 'Add one more commit' '
118 test_expect_success 'write graph with new commit' '
119 cd "$TRASH_DIRECTORY/full" &&
120 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"
123 '
124
125 test_expect_success 'write graph with nothing new' '
126 cd "$TRASH_DIRECTORY/full" &&
127 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"
130 '
131
132 test_expect_success 'setup bare repo' '
@@ -118,7 +139,8 @@ test_expect_success 'setup bare repo' '
139 test_expect_success 'write graph in bare repo' '
140 cd "$TRASH_DIRECTORY/bare" &&
141 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"
144 '
145
146 test_done