untracked cache: fix entry invalidation
First, the current code in untracked_cache_invalidate_path() is wrong because it can only handle paths "a" or "a/b", not "a/b/c" because lookup_untracked() only looks for entries directly under the given directory. In the last case, it will look for the entry "b/c" in directory "a" instead. This means if you delete or add an entry in a subdirectory, untracked cache may become out of date because it does not invalidate properly. This is noticed by David Turner. The second problem is about invalidation inside a fully untracked/excluded directory. In this case we may have to invalidate back to root. See the comment block for detail. Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Nguyễn Thái Ngọc Duy committed
Aug 19, 2015 at 20:01 UTC
73f9145fbf748d39dd1e145ec846a5481cf7a36f
2 files changed
+83
-13
dir.c
+56
-12
@@ -2616,23 +2616,67 @@ done2:
2616
return uc;
2617
}
2618
2619
+static void invalidate_one_directory(struct untracked_cache *uc,
2620
+ struct untracked_cache_dir *ucd)
2621
+{
2622
+ uc->dir_invalidated++;
2623
+ ucd->valid = 0;
2624
+ ucd->untracked_nr = 0;
2625
+}
2626
+
2627
+/*
2628
+ * Normally when an entry is added or removed from a directory,
2629
+ * invalidating that directory is enough. No need to touch its
2630
+ * ancestors. When a directory is shown as "foo/bar/" in git-status
2631
+ * however, deleting or adding an entry may have cascading effect.
2632
+ *
2633
+ * Say the "foo/bar/file" has become untracked, we need to tell the
2634
+ * untracked_cache_dir of "foo" that "bar/" is not an untracked
2635
+ * directory any more (because "bar" is managed by foo as an untracked
2636
+ * "file").
2637
+ *
2638
+ * Similarly, if "foo/bar/file" moves from untracked to tracked and it
2639
+ * was the last untracked entry in the entire "foo", we should show
2640
+ * "foo/" instead. Which means we have to invalidate past "bar" up to
2641
+ * "foo".
2642
+ *
2643
+ * This function traverses all directories from root to leaf. If there
2644
+ * is a chance of one of the above cases happening, we invalidate back
2645
+ * to root. Otherwise we just invalidate the leaf. There may be a more
2646
+ * sophisticated way than checking for SHOW_OTHER_DIRECTORIES to
2647
+ * detect these cases and avoid unnecessary invalidation, for example,
2648
+ * checking for the untracked entry named "bar/" in "foo", but for now
2649
+ * stick to something safe and simple.
2650
+ */
2651
+static int invalidate_one_component(struct untracked_cache *uc,
2652
+ struct untracked_cache_dir *dir,
2653
+ const char *path, int len)
2654
+{
2655
+ const char *rest = strchr(path, '/');
2656
+
2657
+ if (rest) {
2658
+ int component_len = rest - path;
2659
+ struct untracked_cache_dir *d =
2660
+ lookup_untracked(uc, dir, path, component_len);
2661
+ int ret =
2662
+ invalidate_one_component(uc, d, rest + 1,
2663
+ len - (component_len + 1));
2664
+ if (ret)
2665
+ invalidate_one_directory(uc, dir);
2666
+ return ret;
2667
+ }
2668
+
2669
+ invalidate_one_directory(uc, dir);
2670
+ return uc->dir_flags & DIR_SHOW_OTHER_DIRECTORIES;
2671
+}
2672
+
2673
void untracked_cache_invalidate_path(struct index_state *istate,
2674
const char *path)
2675
{
2622
- const char *sep;
2623
- struct untracked_cache_dir *d;
2676
if (!istate->untracked || !istate->untracked->root)
2677
return;
2626
- sep = strrchr(path, '/');
2627
- if (sep)
2628
- d = lookup_untracked(istate->untracked,
2629
- istate->untracked->root,
2630
- path, sep - path);
2631
- else
2632
- d = istate->untracked->root;
2633
- istate->untracked->dir_invalidated++;
2634
- d->valid = 0;
2635
- d->untracked_nr = 0;
2678
+ invalidate_one_component(istate->untracked, istate->untracked->root,
2679
+ path, strlen(path));
2680
}
2681
2682
void untracked_cache_remove_from_index(struct index_state *istate,
t/t7063-status-untracked-cache.sh
+27
-1
@@ -375,7 +375,7 @@ EOF
375
node creation: 0
376
gitignore invalidation: 0
377
directory invalidation: 0
378
-opendir: 1
378
+opendir: 2
379
EOF
380
test_cmp ../trace.expect ../trace
381
'
@@ -543,4 +543,30 @@ EOF
543
test_cmp ../trace.expect ../trace
544
'
545
546
+test_expect_success 'move entry in subdir from untracked to cached' '
547
+ git add dtwo/two &&
548
+ git status --porcelain >../status.actual &&
549
+ cat >../status.expect <<EOF &&
550
+ M done/two
551
+A dtwo/two
552
+?? .gitignore
553
+?? done/five
554
+?? done/sub/
555
+EOF
556
+ test_cmp ../status.expect ../status.actual
557
+'
558
+
559
+test_expect_success 'move entry in subdir from cached to untracked' '
560
+ git rm --cached dtwo/two &&
561
+ git status --porcelain >../status.actual &&
562
+ cat >../status.expect <<EOF &&
563
+ M done/two
564
+?? .gitignore
565
+?? done/five
566
+?? done/sub/
567
+?? dtwo/
568
+EOF
569
+ test_cmp ../status.expect ../status.actual
570
+'
571
+
572
test_done