commit-graph: verify chains with --shallow mode

If we wrote a commit-graph chain, we only modified the tip file in the chain. It is valuable to verify what we wrote, but not waste time checking files we did not write. Add a '--shallow' option to the 'git commit-graph verify' subcommand and check that it does not read the base graph in a two-file chain. Making the verify subcommand read from a chain of commit-graphs takes some rearranging of the builtin code. Signed-off-by: Derrick Stolee <dstolee@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Derrick Stolee committed Jun 18, 2019 at 11:14 UTC 3da4b609bb14b13672f64af908706462617f53cb
5 files changed +101 -14
Documentation/git-commit-graph.txt
+4 -1
@@ -10,7 +10,7 @@ SYNOPSIS
10 --------
11 [verse]
12 'git commit-graph read' [--object-dir <dir>]
13 -'git commit-graph verify' [--object-dir <dir>]
13 +'git commit-graph verify' [--object-dir <dir>] [--shallow]
14 'git commit-graph write' <options> [--object-dir <dir>]
15
16
@@ -80,6 +80,9 @@ Used for debugging purposes.
80
81 Read the commit-graph file and verify its contents against the object
82 database. Used to check for corrupted data.
83 ++
84 +With the `--shallow` option, only check the tip commit-graph file in
85 +a chain of split commit-graphs.
86
87
88 EXAMPLES
builtin/commit-graph.c
+19 -8
@@ -5,17 +5,18 @@
5 #include "parse-options.h"
6 #include "repository.h"
7 #include "commit-graph.h"
8 +#include "object-store.h"
9
10 static char const * const builtin_commit_graph_usage[] = {
11 N_("git commit-graph [--object-dir <objdir>]"),
12 N_("git commit-graph read [--object-dir <objdir>]"),
12 - N_("git commit-graph verify [--object-dir <objdir>]"),
13 + N_("git commit-graph verify [--object-dir <objdir>] [--shallow]"),
14 N_("git commit-graph write [--object-dir <objdir>] [--append|--split] [--reachable|--stdin-packs|--stdin-commits] <split options>"),
15 NULL
16 };
17
18 static const char * const builtin_commit_graph_verify_usage[] = {
18 - N_("git commit-graph verify [--object-dir <objdir>]"),
19 + N_("git commit-graph verify [--object-dir <objdir>] [--shallow]"),
20 NULL
21 };
22
@@ -36,6 +37,7 @@ static struct opts_commit_graph {
37 int stdin_commits;
38 int append;
39 int split;
40 + int shallow;
41 } opts;
42
43 static int graph_verify(int argc, const char **argv)
@@ -45,11 +47,14 @@ static int graph_verify(int argc, const char **argv)
47 int open_ok;
48 int fd;
49 struct stat st;
50 + int flags = 0;
51
52 static struct option builtin_commit_graph_verify_options[] = {
53 OPT_STRING(0, "object-dir", &opts.obj_dir,
54 N_("dir"),
55 N_("The object directory to store the graph")),
56 + OPT_BOOL(0, "shallow", &opts.shallow,
57 + N_("if the commit-graph is split, only verify the tip file")),
58 OPT_END(),
59 };
60
@@ -59,21 +64,27 @@ static int graph_verify(int argc, const char **argv)
64
65 if (!opts.obj_dir)
66 opts.obj_dir = get_object_directory();
67 + if (opts.shallow)
68 + flags |= COMMIT_GRAPH_VERIFY_SHALLOW;
69
70 graph_name = get_commit_graph_filename(opts.obj_dir);
71 open_ok = open_commit_graph(graph_name, &fd, &st);
65 - if (!open_ok && errno == ENOENT)
66 - return 0;
67 - if (!open_ok)
72 + if (!open_ok && errno != ENOENT)
73 die_errno(_("Could not open commit-graph '%s'"), graph_name);
69 - graph = load_commit_graph_one_fd_st(fd, &st);
74 +
75 FREE_AND_NULL(graph_name);
76
77 + if (open_ok)
78 + graph = load_commit_graph_one_fd_st(fd, &st);
79 + else
80 + graph = read_commit_graph_one(the_repository, opts.obj_dir);
81 +
82 + /* Return failure if open_ok predicted success */
83 if (!graph)
73 - return 1;
84 + return !!open_ok;
85
86 UNLEAK(graph);
76 - return verify_commit_graph(the_repository, graph);
87 + return verify_commit_graph(the_repository, graph, flags);
88 }
89
90 static int graph_read(int argc, const char **argv)
commit-graph.c
+12 -3
@@ -428,7 +428,7 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r, const
428 return graph_chain;
429 }
430
431 -static struct commit_graph *read_commit_graph_one(struct repository *r, const char *obj_dir)
431 +struct commit_graph *read_commit_graph_one(struct repository *r, const char *obj_dir)
432 {
433 struct commit_graph *g = load_commit_graph_v1(r, obj_dir);
434
@@ -1887,7 +1887,7 @@ static void graph_report(const char *fmt, ...)
1887 #define GENERATION_ZERO_EXISTS 1
1888 #define GENERATION_NUMBER_EXISTS 2
1889
1890 -int verify_commit_graph(struct repository *r, struct commit_graph *g)
1890 +int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)
1891 {
1892 uint32_t i, cur_fanout_pos = 0;
1893 struct object_id prev_oid, cur_oid, checksum;
@@ -1895,6 +1895,7 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g)
1895 struct hashfile *f;
1896 int devnull;
1897 struct progress *progress = NULL;
1898 + int local_error = 0;
1899
1900 if (!g) {
1901 graph_report("no commit-graph file loaded");
@@ -1989,6 +1990,9 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g)
1990 break;
1991 }
1992
1993 + /* parse parent in case it is in a base graph */
1994 + parse_commit_in_graph_one(r, g, graph_parents->item);
1995 +
1996 if (!oideq(&graph_parents->item->object.oid, &odb_parents->item->object.oid))
1997 graph_report(_("commit-graph parent for %s is %s != %s"),
1998 oid_to_hex(&cur_oid),
@@ -2040,7 +2044,12 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g)
2044 }
2045 stop_progress(&progress);
2046
2043 - return verify_commit_graph_error;
2047 + local_error = verify_commit_graph_error;
2048 +
2049 + if (!(flags & COMMIT_GRAPH_VERIFY_SHALLOW) && g->base_graph)
2050 + local_error |= verify_commit_graph(r, g->base_graph, flags);
2051 +
2052 + return local_error;
2053 }
2054
2055 void free_commit_graph(struct commit_graph *g)
commit-graph.h
+4 -2
@@ -61,7 +61,7 @@ struct commit_graph {
61 };
62
63 struct commit_graph *load_commit_graph_one_fd_st(int fd, struct stat *st);
64 -
64 +struct commit_graph *read_commit_graph_one(struct repository *r, const char *obj_dir);
65 struct commit_graph *parse_commit_graph(void *graph_map, int fd,
66 size_t graph_size);
67
@@ -95,7 +95,9 @@ int write_commit_graph(const char *obj_dir,
95 unsigned int flags,
96 const struct split_commit_graph_opts *split_opts);
97
98 -int verify_commit_graph(struct repository *r, struct commit_graph *g);
98 +#define COMMIT_GRAPH_VERIFY_SHALLOW (1 << 0)
99 +
100 +int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags);
101
102 void close_commit_graph(struct raw_object_store *);
103 void free_commit_graph(struct commit_graph *);
t/t5324-split-commit-graph.sh
+62
@@ -216,4 +216,66 @@ test_expect_success 'test merge stragety constants' '
216 )
217 '
218
219 +corrupt_file() {
220 + file=$1
221 + pos=$2
222 + data="${3:-\0}"
223 + printf "$data" | dd of="$file" bs=1 seek="$pos" conv=notrunc
224 +}
225 +
226 +test_expect_success 'verify hashes along chain, even in shallow' '
227 + git clone --no-hardlinks . verify &&
228 + (
229 + cd verify &&
230 + git commit-graph verify &&
231 + base_file=$graphdir/graph-$(head -n 1 $graphdir/commit-graph-chain).graph &&
232 + corrupt_file "$base_file" 1760 "\01" &&
233 + test_must_fail git commit-graph verify --shallow 2>test_err &&
234 + grep -v "^+" test_err >err &&
235 + test_i18ngrep "incorrect checksum" err
236 + )
237 +'
238 +
239 +test_expect_success 'verify --shallow does not check base contents' '
240 + git clone --no-hardlinks . verify-shallow &&
241 + (
242 + cd verify-shallow &&
243 + git commit-graph verify &&
244 + base_file=$graphdir/graph-$(head -n 1 $graphdir/commit-graph-chain).graph &&
245 + corrupt_file "$base_file" 1000 "\01" &&
246 + git commit-graph verify --shallow &&
247 + test_must_fail git commit-graph verify 2>test_err &&
248 + grep -v "^+" test_err >err &&
249 + test_i18ngrep "incorrect checksum" err
250 + )
251 +'
252 +
253 +test_expect_success 'warn on base graph chunk incorrect' '
254 + git clone --no-hardlinks . base-chunk &&
255 + (
256 + cd base-chunk &&
257 + git commit-graph verify &&
258 + base_file=$graphdir/graph-$(tail -n 1 $graphdir/commit-graph-chain).graph &&
259 + corrupt_file "$base_file" 1376 "\01" &&
260 + git commit-graph verify --shallow 2>test_err &&
261 + grep -v "^+" test_err >err &&
262 + test_i18ngrep "commit-graph chain does not match" err
263 + )
264 +'
265 +
266 +test_expect_success 'verify after commit-graph-chain corruption' '
267 + git clone --no-hardlinks . verify-chain &&
268 + (
269 + cd verify-chain &&
270 + corrupt_file "$graphdir/commit-graph-chain" 60 "G" &&
271 + git commit-graph verify 2>test_err &&
272 + grep -v "^+" test_err >err &&
273 + test_i18ngrep "invalid commit-graph chain" err &&
274 + corrupt_file "$graphdir/commit-graph-chain" 60 "A" &&
275 + git commit-graph verify 2>test_err &&
276 + grep -v "^+" test_err >err &&
277 + test_i18ngrep "unable to find all commit-graph files" err
278 + )
279 +'
280 +
281 test_done