Skip to content

Commit 73f9145

Browse files
pcloudsgitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 2e5910f commit 73f9145

File tree

2 files changed

+83
-13
lines changed

2 files changed

+83
-13
lines changed

dir.c

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2616,23 +2616,67 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long
26162616
return uc;
26172617
}
26182618

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+
26192673
void untracked_cache_invalidate_path(struct index_state *istate,
26202674
const char *path)
26212675
{
2622-
const char *sep;
2623-
struct untracked_cache_dir *d;
26242676
if (!istate->untracked || !istate->untracked->root)
26252677
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));
26362680
}
26372681

26382682
void untracked_cache_remove_from_index(struct index_state *istate,

t/t7063-status-untracked-cache.sh

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ EOF
375375
node creation: 0
376376
gitignore invalidation: 0
377377
directory invalidation: 0
378-
opendir: 1
378+
opendir: 2
379379
EOF
380380
test_cmp ../trace.expect ../trace
381381
'
@@ -543,4 +543,30 @@ EOF
543543
test_cmp ../trace.expect ../trace
544544
'
545545

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+
546572
test_done

0 commit comments

Comments
 (0)