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