Skip to content

Commit c3c0020

Browse files
committed
Merge branch 'jk/commit-graph-verify-fix'
Various fixes to "git commit-graph verify". * jk/commit-graph-verify-fix: commit-graph: report incomplete chains during verification commit-graph: tighten chain size check commit-graph: detect read errors when verifying graph chain t5324: harmonize sha1/sha256 graph chain corruption commit-graph: check mixed generation validation when loading chain file commit-graph: factor out chain opening function
2 parents 42b495e + 5f25919 commit c3c0020

File tree

4 files changed

+158
-55
lines changed

4 files changed

+158
-55
lines changed

builtin/commit-graph.c

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,12 @@ static int graph_verify(int argc, const char **argv, const char *prefix)
6969
struct commit_graph *graph = NULL;
7070
struct object_directory *odb = NULL;
7171
char *graph_name;
72-
int open_ok;
72+
char *chain_name;
73+
enum { OPENED_NONE, OPENED_GRAPH, OPENED_CHAIN } opened = OPENED_NONE;
7374
int fd;
7475
struct stat st;
7576
int flags = 0;
77+
int incomplete_chain = 0;
7678
int ret;
7779

7880
static struct option builtin_commit_graph_verify_options[] = {
@@ -102,24 +104,39 @@ static int graph_verify(int argc, const char **argv, const char *prefix)
102104

103105
odb = find_odb(the_repository, opts.obj_dir);
104106
graph_name = get_commit_graph_filename(odb);
105-
open_ok = open_commit_graph(graph_name, &fd, &st);
106-
if (!open_ok && errno != ENOENT)
107+
chain_name = get_commit_graph_chain_filename(odb);
108+
if (open_commit_graph(graph_name, &fd, &st))
109+
opened = OPENED_GRAPH;
110+
else if (errno != ENOENT)
107111
die_errno(_("Could not open commit-graph '%s'"), graph_name);
112+
else if (open_commit_graph_chain(chain_name, &fd, &st))
113+
opened = OPENED_CHAIN;
114+
else if (errno != ENOENT)
115+
die_errno(_("could not open commit-graph chain '%s'"), chain_name);
108116

109117
FREE_AND_NULL(graph_name);
118+
FREE_AND_NULL(chain_name);
110119
FREE_AND_NULL(options);
111120

112-
if (open_ok)
121+
if (opened == OPENED_NONE)
122+
return 0;
123+
else if (opened == OPENED_GRAPH)
113124
graph = load_commit_graph_one_fd_st(the_repository, fd, &st, odb);
114125
else
115-
graph = read_commit_graph_one(the_repository, odb);
126+
graph = load_commit_graph_chain_fd_st(the_repository, fd, &st,
127+
&incomplete_chain);
116128

117-
/* Return failure if open_ok predicted success */
118129
if (!graph)
119-
return !!open_ok;
130+
return 1;
120131

121132
ret = verify_commit_graph(the_repository, graph, flags);
122133
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+
123140
return ret;
124141
}
125142

commit-graph.c

Lines changed: 69 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,31 @@ static struct commit_graph *load_commit_graph_v1(struct repository *r,
473473
return g;
474474
}
475475

476+
/*
477+
* returns 1 if and only if all graphs in the chain have
478+
* corrected commit dates stored in the generation_data chunk.
479+
*/
480+
static int validate_mixed_generation_chain(struct commit_graph *g)
481+
{
482+
int read_generation_data = 1;
483+
struct commit_graph *p = g;
484+
485+
while (read_generation_data && p) {
486+
read_generation_data = p->read_generation_data;
487+
p = p->base_graph;
488+
}
489+
490+
if (read_generation_data)
491+
return 1;
492+
493+
while (g) {
494+
g->read_generation_data = 0;
495+
g = g->base_graph;
496+
}
497+
498+
return 0;
499+
}
500+
476501
static int add_graph_to_chain(struct commit_graph *g,
477502
struct commit_graph *chain,
478503
struct object_id *oids,
@@ -513,31 +538,41 @@ static int add_graph_to_chain(struct commit_graph *g,
513538
return 1;
514539
}
515540

516-
static struct commit_graph *load_commit_graph_chain(struct repository *r,
517-
struct object_directory *odb)
541+
int open_commit_graph_chain(const char *chain_file,
542+
int *fd, struct stat *st)
543+
{
544+
*fd = git_open(chain_file);
545+
if (*fd < 0)
546+
return 0;
547+
if (fstat(*fd, st)) {
548+
close(*fd);
549+
return 0;
550+
}
551+
if (st->st_size < the_hash_algo->hexsz) {
552+
close(*fd);
553+
if (!st->st_size) {
554+
/* treat empty files the same as missing */
555+
errno = ENOENT;
556+
} else {
557+
warning("commit-graph chain file too small");
558+
errno = EINVAL;
559+
}
560+
return 0;
561+
}
562+
return 1;
563+
}
564+
565+
struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r,
566+
int fd, struct stat *st,
567+
int *incomplete_chain)
518568
{
519569
struct commit_graph *graph_chain = NULL;
520570
struct strbuf line = STRBUF_INIT;
521-
struct stat st;
522571
struct object_id *oids;
523572
int i = 0, valid = 1, count;
524-
char *chain_name = get_commit_graph_chain_filename(odb);
525-
FILE *fp;
526-
int stat_res;
527-
528-
fp = fopen(chain_name, "r");
529-
stat_res = stat(chain_name, &st);
530-
free(chain_name);
573+
FILE *fp = xfdopen(fd, "r");
531574

532-
if (!fp)
533-
return NULL;
534-
if (stat_res ||
535-
st.st_size <= the_hash_algo->hexsz) {
536-
fclose(fp);
537-
return NULL;
538-
}
539-
540-
count = st.st_size / (the_hash_algo->hexsz + 1);
575+
count = st->st_size / (the_hash_algo->hexsz + 1);
541576
CALLOC_ARRAY(oids, count);
542577

543578
prepare_alt_odb(r);
@@ -578,36 +613,32 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r,
578613
}
579614
}
580615

616+
validate_mixed_generation_chain(graph_chain);
617+
581618
free(oids);
582619
fclose(fp);
583620
strbuf_release(&line);
584621

622+
*incomplete_chain = !valid;
585623
return graph_chain;
586624
}
587625

588-
/*
589-
* returns 1 if and only if all graphs in the chain have
590-
* corrected commit dates stored in the generation_data chunk.
591-
*/
592-
static int validate_mixed_generation_chain(struct commit_graph *g)
626+
static struct commit_graph *load_commit_graph_chain(struct repository *r,
627+
struct object_directory *odb)
593628
{
594-
int read_generation_data = 1;
595-
struct commit_graph *p = g;
596-
597-
while (read_generation_data && p) {
598-
read_generation_data = p->read_generation_data;
599-
p = p->base_graph;
600-
}
601-
602-
if (read_generation_data)
603-
return 1;
629+
char *chain_file = get_commit_graph_chain_filename(odb);
630+
struct stat st;
631+
int fd;
632+
struct commit_graph *g = NULL;
604633

605-
while (g) {
606-
g->read_generation_data = 0;
607-
g = g->base_graph;
634+
if (open_commit_graph_chain(chain_file, &fd, &st)) {
635+
int incomplete;
636+
/* ownership of fd is taken over by load function */
637+
g = load_commit_graph_chain_fd_st(r, fd, &st, &incomplete);
608638
}
609639

610-
return 0;
640+
free(chain_file);
641+
return g;
611642
}
612643

613644
struct commit_graph *read_commit_graph_one(struct repository *r,
@@ -618,8 +649,6 @@ struct commit_graph *read_commit_graph_one(struct repository *r,
618649
if (!g)
619650
g = load_commit_graph_chain(r, odb);
620651

621-
validate_mixed_generation_chain(g);
622-
623652
return g;
624653
}
625654

commit-graph.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ struct string_list;
2626
char *get_commit_graph_filename(struct object_directory *odb);
2727
char *get_commit_graph_chain_filename(struct object_directory *odb);
2828
int open_commit_graph(const char *graph_file, int *fd, struct stat *st);
29+
int open_commit_graph_chain(const char *chain_file, int *fd, struct stat *st);
2930

3031
/*
3132
* Given a commit struct, try to fill the commit struct info, including:
@@ -105,6 +106,9 @@ struct commit_graph {
105106
struct commit_graph *load_commit_graph_one_fd_st(struct repository *r,
106107
int fd, struct stat *st,
107108
struct object_directory *odb);
109+
struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r,
110+
int fd, struct stat *st,
111+
int *incomplete_chain);
108112
struct commit_graph *read_commit_graph_one(struct repository *r,
109113
struct object_directory *odb);
110114

t/t5324-split-commit-graph.sh

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,32 @@ test_expect_success 'verify hashes along chain, even in shallow' '
285285
)
286286
'
287287

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+
288314
test_expect_success 'verify --shallow does not check base contents' '
289315
git clone --no-hardlinks . verify-shallow &&
290316
(
@@ -306,27 +332,54 @@ test_expect_success 'warn on base graph chunk incorrect' '
306332
git commit-graph verify &&
307333
base_file=$graphdir/graph-$(tail -n 1 $graphdir/commit-graph-chain).graph &&
308334
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 &&
310336
grep -v "^+" test_err >err &&
311337
test_i18ngrep "commit-graph chain does not match" err
312338
)
313339
'
314340

315-
test_expect_success 'verify after commit-graph-chain corruption' '
316-
git clone --no-hardlinks . verify-chain &&
341+
test_expect_success 'verify after commit-graph-chain corruption (base)' '
342+
git clone --no-hardlinks . verify-chain-base &&
343+
(
344+
cd verify-chain-base &&
345+
corrupt_file "$graphdir/commit-graph-chain" 30 "G" &&
346+
test_must_fail git commit-graph verify 2>test_err &&
347+
grep -v "^+" test_err >err &&
348+
test_i18ngrep "invalid commit-graph chain" err &&
349+
corrupt_file "$graphdir/commit-graph-chain" 30 "A" &&
350+
test_must_fail git commit-graph verify 2>test_err &&
351+
grep -v "^+" test_err >err &&
352+
test_i18ngrep "unable to find all commit-graph files" err
353+
)
354+
'
355+
356+
test_expect_success 'verify after commit-graph-chain corruption (tip)' '
357+
git clone --no-hardlinks . verify-chain-tip &&
317358
(
318-
cd verify-chain &&
319-
corrupt_file "$graphdir/commit-graph-chain" 60 "G" &&
320-
git commit-graph verify 2>test_err &&
359+
cd verify-chain-tip &&
360+
corrupt_file "$graphdir/commit-graph-chain" 70 "G" &&
361+
test_must_fail git commit-graph verify 2>test_err &&
321362
grep -v "^+" test_err >err &&
322363
test_i18ngrep "invalid commit-graph chain" err &&
323-
corrupt_file "$graphdir/commit-graph-chain" 60 "A" &&
324-
git commit-graph verify 2>test_err &&
364+
corrupt_file "$graphdir/commit-graph-chain" 70 "A" &&
365+
test_must_fail git commit-graph verify 2>test_err &&
325366
grep -v "^+" test_err >err &&
326367
test_i18ngrep "unable to find all commit-graph files" err
327368
)
328369
'
329370

371+
test_expect_success 'verify notices too-short chain file' '
372+
git clone --no-hardlinks . verify-chain-short &&
373+
(
374+
cd verify-chain-short &&
375+
git commit-graph verify &&
376+
echo "garbage" >$graphdir/commit-graph-chain &&
377+
test_must_fail git commit-graph verify 2>test_err &&
378+
grep -v "^+" test_err >err &&
379+
grep "commit-graph chain file too small" err
380+
)
381+
'
382+
330383
test_expect_success 'verify across alternates' '
331384
git clone --no-hardlinks . verify-alt &&
332385
(

0 commit comments

Comments
 (0)