Skip to content

Commit 0498840

Browse files
committed
Merge branch 'rs/fsck-duplicate-names-in-trees'
"git fsck" ensures that the paths recorded in tree objects are sorted and without duplicates, but it failed to notice a case where a blob is followed by entries that sort before a tree with the same name. This has been corrected. * rs/fsck-duplicate-names-in-trees: fsck: report non-consecutive duplicate names in trees
2 parents f4507ce + 9068cfb commit 0498840

File tree

2 files changed

+86
-2
lines changed

2 files changed

+86
-2
lines changed

fsck.c

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,28 @@ int fsck_walk(struct object *obj, void *data, struct fsck_options *options)
523523
}
524524
}
525525

526+
struct name_stack {
527+
const char **names;
528+
size_t nr, alloc;
529+
};
530+
531+
static void name_stack_push(struct name_stack *stack, const char *name)
532+
{
533+
ALLOC_GROW(stack->names, stack->nr + 1, stack->alloc);
534+
stack->names[stack->nr++] = name;
535+
}
536+
537+
static const char *name_stack_pop(struct name_stack *stack)
538+
{
539+
return stack->nr ? stack->names[--stack->nr] : NULL;
540+
}
541+
542+
static void name_stack_clear(struct name_stack *stack)
543+
{
544+
FREE_AND_NULL(stack->names);
545+
stack->nr = stack->alloc = 0;
546+
}
547+
526548
/*
527549
* The entries in a tree are ordered in the _path_ order,
528550
* which means that a directory entry is ordered by adding
@@ -534,7 +556,14 @@ int fsck_walk(struct object *obj, void *data, struct fsck_options *options)
534556
#define TREE_UNORDERED (-1)
535557
#define TREE_HAS_DUPS (-2)
536558

537-
static int verify_ordered(unsigned mode1, const char *name1, unsigned mode2, const char *name2)
559+
static int is_less_than_slash(unsigned char c)
560+
{
561+
return '\0' < c && c < '/';
562+
}
563+
564+
static int verify_ordered(unsigned mode1, const char *name1,
565+
unsigned mode2, const char *name2,
566+
struct name_stack *candidates)
538567
{
539568
int len1 = strlen(name1);
540569
int len2 = strlen(name2);
@@ -566,6 +595,41 @@ static int verify_ordered(unsigned mode1, const char *name1, unsigned mode2, con
566595
c1 = '/';
567596
if (!c2 && S_ISDIR(mode2))
568597
c2 = '/';
598+
599+
/*
600+
* There can be non-consecutive duplicates due to the implicitly
601+
* add slash, e.g.:
602+
*
603+
* foo
604+
* foo.bar
605+
* foo.bar.baz
606+
* foo.bar/
607+
* foo/
608+
*
609+
* Record non-directory candidates (like "foo" and "foo.bar" in
610+
* the example) on a stack and check directory candidates (like
611+
* foo/" and "foo.bar/") against that stack.
612+
*/
613+
if (!c1 && is_less_than_slash(c2)) {
614+
name_stack_push(candidates, name1);
615+
} else if (c2 == '/' && is_less_than_slash(c1)) {
616+
for (;;) {
617+
const char *p;
618+
const char *f_name = name_stack_pop(candidates);
619+
620+
if (!f_name)
621+
break;
622+
if (!skip_prefix(name2, f_name, &p))
623+
break;
624+
if (!*p)
625+
return TREE_HAS_DUPS;
626+
if (is_less_than_slash(*p)) {
627+
name_stack_push(candidates, f_name);
628+
break;
629+
}
630+
}
631+
}
632+
569633
return c1 < c2 ? 0 : TREE_UNORDERED;
570634
}
571635

@@ -587,6 +651,7 @@ static int fsck_tree(const struct object_id *oid,
587651
struct tree_desc desc;
588652
unsigned o_mode;
589653
const char *o_name;
654+
struct name_stack df_dup_candidates = { NULL };
590655

591656
if (init_tree_desc_gently(&desc, buffer, size)) {
592657
retval += report(options, oid, OBJ_TREE, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree");
@@ -666,7 +731,8 @@ static int fsck_tree(const struct object_id *oid,
666731
}
667732

668733
if (o_name) {
669-
switch (verify_ordered(o_mode, o_name, mode, name)) {
734+
switch (verify_ordered(o_mode, o_name, mode, name,
735+
&df_dup_candidates)) {
670736
case TREE_UNORDERED:
671737
not_properly_sorted = 1;
672738
break;
@@ -682,6 +748,8 @@ static int fsck_tree(const struct object_id *oid,
682748
o_name = name;
683749
}
684750

751+
name_stack_clear(&df_dup_candidates);
752+
685753
if (has_null_sha1)
686754
retval += report(options, oid, OBJ_TREE, FSCK_MSG_NULL_SHA1, "contains entries pointing to null sha1");
687755
if (has_full_path)

t/t1450-fsck.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,22 @@ test_expect_success 'tree object with duplicate entries' '
257257
test_i18ngrep "error in tree .*contains duplicate file entries" out
258258
'
259259

260+
test_expect_success 'tree object with dublicate names' '
261+
test_when_finished "remove_object \$blob" &&
262+
test_when_finished "remove_object \$tree" &&
263+
test_when_finished "remove_object \$badtree" &&
264+
blob=$(echo blob | git hash-object -w --stdin) &&
265+
printf "100644 blob %s\t%s\n" $blob x.2 >tree &&
266+
tree=$(git mktree <tree) &&
267+
printf "100644 blob %s\t%s\n" $blob x.1 >badtree &&
268+
printf "100644 blob %s\t%s\n" $blob x >>badtree &&
269+
printf "040000 tree %s\t%s\n" $tree x >>badtree &&
270+
badtree=$(git mktree <badtree) &&
271+
test_must_fail git fsck 2>out &&
272+
test_i18ngrep "$badtree" out &&
273+
test_i18ngrep "error in tree .*contains duplicate file entries" out
274+
'
275+
260276
test_expect_success 'unparseable tree object' '
261277
test_oid_cache <<-\EOF &&
262278
junk sha1:twenty-bytes-of-junk

0 commit comments

Comments
 (0)