@matheusbsbs / matheus-bagliano / commits / 9068cfb20f
Fork 0

fsck: report non-consecutive duplicate names in trees

Tree entries are sorted in path order, meaning that directory names get a slash ('/') appended implicitly. Git fsck checks if trees contains consecutive duplicates, but due to that ordering there can be non-consecutive duplicates as well if one of them is a directory and the other one isn't. Such a tree cannot be fully checked out. Find these duplicates by recording candidate file names on a stack and check candidate directory names against that stack to find matches. Suggested-by: Brandon Williams <bwilliamseng@gmail.com> Original-test-by: Brandon Williams <bwilliamseng@gmail.com> Signed-off-by: René Scharfe <l.s.r@web.de> Reviewed-by: Luke Diamand <luke@diamand.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>

René Scharfe committed May 10, 2020 at 18:12 UTC 9068cfb20f5699fd639a559af14affaefe43a812
2 files changed +86 -2
fsck.c
+70 -2
@@ -523,6 +523,28 @@ int fsck_walk(struct object *obj, void *data, struct fsck_options *options)
523 }
524 }
525
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 +
548 /*
549 * The entries in a tree are ordered in the _path_ order,
550 * 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)
556 #define TREE_UNORDERED (-1)
557 #define TREE_HAS_DUPS (-2)
558
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)
567 {
568 int len1 = strlen(name1);
569 int len2 = strlen(name2);
@@ -566,6 +595,41 @@ static int verify_ordered(unsigned mode1, const char *name1, unsigned mode2, con
595 c1 = '/';
596 if (!c2 && S_ISDIR(mode2))
597 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 +
633 return c1 < c2 ? 0 : TREE_UNORDERED;
634 }
635
@@ -587,6 +651,7 @@ static int fsck_tree(const struct object_id *oid,
651 struct tree_desc desc;
652 unsigned o_mode;
653 const char *o_name;
654 + struct name_stack df_dup_candidates = { NULL };
655
656 if (init_tree_desc_gently(&desc, buffer, size)) {
657 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,
731 }
732
733 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)) {
736 case TREE_UNORDERED:
737 not_properly_sorted = 1;
738 break;
@@ -682,6 +748,8 @@ static int fsck_tree(const struct object_id *oid,
748 o_name = name;
749 }
750
751 + name_stack_clear(&df_dup_candidates);
752 +
753 if (has_null_sha1)
754 retval += report(options, oid, OBJ_TREE, FSCK_MSG_NULL_SHA1, "contains entries pointing to null sha1");
755 if (has_full_path)
t/t1450-fsck.sh
+16
@@ -234,6 +234,22 @@ test_expect_success 'tree object with duplicate entries' '
234 test_i18ngrep "error in tree .*contains duplicate file entries" out
235 '
236
237 +test_expect_success 'tree object with dublicate names' '
238 + test_when_finished "remove_object \$blob" &&
239 + test_when_finished "remove_object \$tree" &&
240 + test_when_finished "remove_object \$badtree" &&
241 + blob=$(echo blob | git hash-object -w --stdin) &&
242 + printf "100644 blob %s\t%s\n" $blob x.2 >tree &&
243 + tree=$(git mktree <tree) &&
244 + printf "100644 blob %s\t%s\n" $blob x.1 >badtree &&
245 + printf "100644 blob %s\t%s\n" $blob x >>badtree &&
246 + printf "040000 tree %s\t%s\n" $tree x >>badtree &&
247 + badtree=$(git mktree <badtree) &&
248 + test_must_fail git fsck 2>out &&
249 + test_i18ngrep "$badtree" out &&
250 + test_i18ngrep "error in tree .*contains duplicate file entries" out
251 +'
252 +
253 test_expect_success 'unparseable tree object' '
254 test_oid_cache <<-\EOF &&
255 junk sha1:twenty-bytes-of-junk