commit-graph: report incomplete chains during verification

The load_commit_graph_chain_fd_st() function will stop loading chains when it sees an error. But if it has loaded any graph slice at all, it will return it. This is a good thing for normal use (we use what data we can, and this is just an optimization). But it's a bad thing for "commit-graph verify", which should be careful about finding any irregularities. We do complain to stderr with a warning(), but the verify command still exits with a successful return code. The new tests here cover corruption of both the base and tip slices of the chain. The corruption of the base file already works (it is the first file we look at, so when we see the error we return NULL). The "tip" case is what is fixed by this patch (it complains to stderr but still returns the base slice). Likewise the existing tests for corruption of the commit-graph-chain file itself need to be updated. We already exited non-zero correctly for the "base" case, but the "tip" case can now do so, too. Note that this also causes us to adjust a test later in the file that similarly corrupts a tip (though confusingly the test script calls this "base"). It checks stderr but erroneously expects the whole "verify" command to exit with a successful code. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Sep 28, 2023 at 00:39 UTC 5f259197eea0a3acc48f46748778f33c935476cb
4 files changed +45 -7
builtin/commit-graph.c
+9 -1
@@ -74,6 +74,7 @@ static int graph_verify(int argc, const char **argv, const char *prefix)
74 int fd;
75 struct stat st;
76 int flags = 0;
77 + int incomplete_chain = 0;
78 int ret;
79
80 static struct option builtin_commit_graph_verify_options[] = {
@@ -122,13 +123,20 @@ static int graph_verify(int argc, const char **argv, const char *prefix)
123 else if (opened == OPENED_GRAPH)
124 graph = load_commit_graph_one_fd_st(the_repository, fd, &st, odb);
125 else
125 - graph = load_commit_graph_chain_fd_st(the_repository, fd, &st);
126 + graph = load_commit_graph_chain_fd_st(the_repository, fd, &st,
127 + &incomplete_chain);
128
129 if (!graph)
130 return 1;
131
132 ret = verify_commit_graph(the_repository, graph, flags);
133 free_commit_graph(graph);
134 +
135 + if (incomplete_chain) {
136 + error("one or more commit-graph chain files could not be loaded");
137 + ret |= 1;
138 + }
139 +
140 return ret;
141 }
142
commit-graph.c
+5 -2
@@ -563,7 +563,8 @@ int open_commit_graph_chain(const char *chain_file,
563 }
564
565 struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r,
566 - int fd, struct stat *st)
566 + int fd, struct stat *st,
567 + int *incomplete_chain)
568 {
569 struct commit_graph *graph_chain = NULL;
570 struct strbuf line = STRBUF_INIT;
@@ -618,6 +619,7 @@ struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r,
619 fclose(fp);
620 strbuf_release(&line);
621
622 + *incomplete_chain = !valid;
623 return graph_chain;
624 }
625
@@ -630,8 +632,9 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r,
632 struct commit_graph *g = NULL;
633
634 if (open_commit_graph_chain(chain_file, &fd, &st)) {
635 + int incomplete;
636 /* ownership of fd is taken over by load function */
634 - g = load_commit_graph_chain_fd_st(r, fd, &st);
637 + g = load_commit_graph_chain_fd_st(r, fd, &st, &incomplete);
638 }
639
640 free(chain_file);
commit-graph.h
+2 -1
@@ -107,7 +107,8 @@ struct commit_graph *load_commit_graph_one_fd_st(struct repository *r,
107 int fd, struct stat *st,
108 struct object_directory *odb);
109 struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r,
110 - int fd, struct stat *st);
110 + int fd, struct stat *st,
111 + int *incomplete_chain);
112 struct commit_graph *read_commit_graph_one(struct repository *r,
113 struct object_directory *odb);
114
t/t5324-split-commit-graph.sh
+29 -3
@@ -285,6 +285,32 @@ test_expect_success 'verify hashes along chain, even in shallow' '
285 )
286 '
287
288 +test_expect_success 'verify notices chain slice which is bogus (base)' '
289 + git clone --no-hardlinks . verify-chain-bogus-base &&
290 + (
291 + cd verify-chain-bogus-base &&
292 + git commit-graph verify &&
293 + base_file=$graphdir/graph-$(sed -n 1p $graphdir/commit-graph-chain).graph &&
294 + echo "garbage" >$base_file &&
295 + test_must_fail git commit-graph verify 2>test_err &&
296 + grep -v "^+" test_err >err &&
297 + grep "commit-graph file is too small" err
298 + )
299 +'
300 +
301 +test_expect_success 'verify notices chain slice which is bogus (tip)' '
302 + git clone --no-hardlinks . verify-chain-bogus-tip &&
303 + (
304 + cd verify-chain-bogus-tip &&
305 + git commit-graph verify &&
306 + tip_file=$graphdir/graph-$(sed -n 2p $graphdir/commit-graph-chain).graph &&
307 + echo "garbage" >$tip_file &&
308 + test_must_fail git commit-graph verify 2>test_err &&
309 + grep -v "^+" test_err >err &&
310 + grep "commit-graph file is too small" err
311 + )
312 +'
313 +
314 test_expect_success 'verify --shallow does not check base contents' '
315 git clone --no-hardlinks . verify-shallow &&
316 (
@@ -306,7 +332,7 @@ test_expect_success 'warn on base graph chunk incorrect' '
332 git commit-graph verify &&
333 base_file=$graphdir/graph-$(tail -n 1 $graphdir/commit-graph-chain).graph &&
334 corrupt_file "$base_file" $(test_oid base) "\01" &&
309 - git commit-graph verify --shallow 2>test_err &&
335 + test_must_fail git commit-graph verify --shallow 2>test_err &&
336 grep -v "^+" test_err >err &&
337 test_i18ngrep "commit-graph chain does not match" err
338 )
@@ -332,11 +358,11 @@ test_expect_success 'verify after commit-graph-chain corruption (tip)' '
358 (
359 cd verify-chain-tip &&
360 corrupt_file "$graphdir/commit-graph-chain" 70 "G" &&
335 - git commit-graph verify 2>test_err &&
361 + test_must_fail git commit-graph verify 2>test_err &&
362 grep -v "^+" test_err >err &&
363 test_i18ngrep "invalid commit-graph chain" err &&
364 corrupt_file "$graphdir/commit-graph-chain" 70 "A" &&
339 - git commit-graph verify 2>test_err &&
365 + test_must_fail git commit-graph verify 2>test_err &&
366 grep -v "^+" test_err >err &&
367 test_i18ngrep "unable to find all commit-graph files" err
368 )