Skip to content

Commit

Permalink
untracked cache: fix entry invalidation
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Nguyễn Thái Ngọc Duy authored and Junio C Hamano committed Aug 19, 2015
1 parent 2e5910f commit 73f9145
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 13 deletions.
68 changes: 56 additions & 12 deletions dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -2616,23 +2616,67 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long
return uc;
}

static void invalidate_one_directory(struct untracked_cache *uc,
struct untracked_cache_dir *ucd)
{
uc->dir_invalidated++;
ucd->valid = 0;
ucd->untracked_nr = 0;
}

/*
* Normally when an entry is added or removed from a directory,
* invalidating that directory is enough. No need to touch its
* ancestors. When a directory is shown as "foo/bar/" in git-status
* however, deleting or adding an entry may have cascading effect.
*
* Say the "foo/bar/file" has become untracked, we need to tell the
* untracked_cache_dir of "foo" that "bar/" is not an untracked
* directory any more (because "bar" is managed by foo as an untracked
* "file").
*
* Similarly, if "foo/bar/file" moves from untracked to tracked and it
* was the last untracked entry in the entire "foo", we should show
* "foo/" instead. Which means we have to invalidate past "bar" up to
* "foo".
*
* This function traverses all directories from root to leaf. If there
* is a chance of one of the above cases happening, we invalidate back
* to root. Otherwise we just invalidate the leaf. There may be a more
* sophisticated way than checking for SHOW_OTHER_DIRECTORIES to
* detect these cases and avoid unnecessary invalidation, for example,
* checking for the untracked entry named "bar/" in "foo", but for now
* stick to something safe and simple.
*/
static int invalidate_one_component(struct untracked_cache *uc,
struct untracked_cache_dir *dir,
const char *path, int len)
{
const char *rest = strchr(path, '/');

if (rest) {
int component_len = rest - path;
struct untracked_cache_dir *d =
lookup_untracked(uc, dir, path, component_len);
int ret =
invalidate_one_component(uc, d, rest + 1,
len - (component_len + 1));
if (ret)
invalidate_one_directory(uc, dir);
return ret;
}

invalidate_one_directory(uc, dir);
return uc->dir_flags & DIR_SHOW_OTHER_DIRECTORIES;
}

void untracked_cache_invalidate_path(struct index_state *istate,
const char *path)
{
const char *sep;
struct untracked_cache_dir *d;
if (!istate->untracked || !istate->untracked->root)
return;
sep = strrchr(path, '/');
if (sep)
d = lookup_untracked(istate->untracked,
istate->untracked->root,
path, sep - path);
else
d = istate->untracked->root;
istate->untracked->dir_invalidated++;
d->valid = 0;
d->untracked_nr = 0;
invalidate_one_component(istate->untracked, istate->untracked->root,
path, strlen(path));
}

void untracked_cache_remove_from_index(struct index_state *istate,
Expand Down
28 changes: 27 additions & 1 deletion t/t7063-status-untracked-cache.sh
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ EOF
node creation: 0
gitignore invalidation: 0
directory invalidation: 0
opendir: 1
opendir: 2
EOF
test_cmp ../trace.expect ../trace
'
Expand Down Expand Up @@ -543,4 +543,30 @@ EOF
test_cmp ../trace.expect ../trace
'

test_expect_success 'move entry in subdir from untracked to cached' '
git add dtwo/two &&
git status --porcelain >../status.actual &&
cat >../status.expect <<EOF &&
M done/two
A dtwo/two
?? .gitignore
?? done/five
?? done/sub/
EOF
test_cmp ../status.expect ../status.actual
'

test_expect_success 'move entry in subdir from cached to untracked' '
git rm --cached dtwo/two &&
git status --porcelain >../status.actual &&
cat >../status.expect <<EOF &&
M done/two
?? .gitignore
?? done/five
?? done/sub/
?? dtwo/
EOF
test_cmp ../status.expect ../status.actual
'

test_done

0 comments on commit 73f9145

Please sign in to comment.