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