Merge branch 'dt/untracked-subdir' into maint
The experimental untracked-cache feature were buggy when paths with a few levels of subdirectories are involved. * dt/untracked-subdir: untracked cache: fix entry invalidation untracked-cache: fix subdirectory handling t7063: use --force-untracked-cache to speed up a bit untracked-cache: support sparse checkout
This commit is contained in:
commit
8833ccd7d0
99
dir.c
99
dir.c
@ -1078,10 +1078,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
|
||||
(!untracked || !untracked->valid ||
|
||||
/*
|
||||
* .. and .gitignore does not exist before
|
||||
* (i.e. null exclude_sha1 and skip_worktree is
|
||||
* not set). Then we can skip loading .gitignore,
|
||||
* which would result in ENOENT anyway.
|
||||
* skip_worktree is taken care in read_directory()
|
||||
* (i.e. null exclude_sha1). Then we can skip
|
||||
* loading .gitignore, which would result in
|
||||
* ENOENT anyway.
|
||||
*/
|
||||
!is_null_sha1(untracked->exclude_sha1))) {
|
||||
/*
|
||||
@ -1298,7 +1297,7 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
|
||||
*/
|
||||
static enum path_treatment treat_directory(struct dir_struct *dir,
|
||||
struct untracked_cache_dir *untracked,
|
||||
const char *dirname, int len, int exclude,
|
||||
const char *dirname, int len, int baselen, int exclude,
|
||||
const struct path_simplify *simplify)
|
||||
{
|
||||
/* The "len-1" is to strip the final '/' */
|
||||
@ -1325,7 +1324,8 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
|
||||
if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
|
||||
return exclude ? path_excluded : path_untracked;
|
||||
|
||||
untracked = lookup_untracked(dir->untracked, untracked, dirname, len);
|
||||
untracked = lookup_untracked(dir->untracked, untracked,
|
||||
dirname + baselen, len - baselen);
|
||||
return read_directory_recursive(dir, dirname, len,
|
||||
untracked, 1, simplify);
|
||||
}
|
||||
@ -1445,6 +1445,7 @@ static int get_dtype(struct dirent *de, const char *path, int len)
|
||||
static enum path_treatment treat_one_path(struct dir_struct *dir,
|
||||
struct untracked_cache_dir *untracked,
|
||||
struct strbuf *path,
|
||||
int baselen,
|
||||
const struct path_simplify *simplify,
|
||||
int dtype, struct dirent *de)
|
||||
{
|
||||
@ -1496,8 +1497,8 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
|
||||
return path_none;
|
||||
case DT_DIR:
|
||||
strbuf_addch(path, '/');
|
||||
return treat_directory(dir, untracked, path->buf, path->len, exclude,
|
||||
simplify);
|
||||
return treat_directory(dir, untracked, path->buf, path->len,
|
||||
baselen, exclude, simplify);
|
||||
case DT_REG:
|
||||
case DT_LNK:
|
||||
return exclude ? path_excluded : path_untracked;
|
||||
@ -1558,7 +1559,7 @@ static enum path_treatment treat_path(struct dir_struct *dir,
|
||||
return path_none;
|
||||
|
||||
dtype = DTYPE(de);
|
||||
return treat_one_path(dir, untracked, path, simplify, dtype, de);
|
||||
return treat_one_path(dir, untracked, path, baselen, simplify, dtype, de);
|
||||
}
|
||||
|
||||
static void add_untracked(struct untracked_cache_dir *dir, const char *name)
|
||||
@ -1828,7 +1829,7 @@ static int treat_leading_path(struct dir_struct *dir,
|
||||
break;
|
||||
if (simplify_away(sb.buf, sb.len, simplify))
|
||||
break;
|
||||
if (treat_one_path(dir, NULL, &sb, simplify,
|
||||
if (treat_one_path(dir, NULL, &sb, baselen, simplify,
|
||||
DT_DIR, NULL) == path_none)
|
||||
break; /* do not recurse into it */
|
||||
if (len <= baselen) {
|
||||
@ -1880,7 +1881,6 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d
|
||||
const struct pathspec *pathspec)
|
||||
{
|
||||
struct untracked_cache_dir *root;
|
||||
int i;
|
||||
|
||||
if (!dir->untracked || getenv("GIT_DISABLE_UNTRACKED_CACHE"))
|
||||
return NULL;
|
||||
@ -1932,15 +1932,6 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d
|
||||
if (dir->exclude_list_group[EXC_CMDL].nr)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* An optimization in prep_exclude() does not play well with
|
||||
* CE_SKIP_WORKTREE. It's a rare case anyway, if a single
|
||||
* entry has that bit set, disable the whole untracked cache.
|
||||
*/
|
||||
for (i = 0; i < active_nr; i++)
|
||||
if (ce_skip_worktree(active_cache[i]))
|
||||
return NULL;
|
||||
|
||||
if (!ident_in_untracked(dir->untracked)) {
|
||||
warning(_("Untracked cache is disabled on this system."));
|
||||
return NULL;
|
||||
@ -2625,23 +2616,67 @@ done2:
|
||||
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,
|
||||
|
@ -354,4 +354,219 @@ EOF
|
||||
test_cmp ../expect ../actual
|
||||
'
|
||||
|
||||
test_expect_success 'set up for sparse checkout testing' '
|
||||
echo two >done/.gitignore &&
|
||||
echo three >>done/.gitignore &&
|
||||
echo two >done/two &&
|
||||
git add -f done/two done/.gitignore &&
|
||||
git commit -m "first commit"
|
||||
'
|
||||
|
||||
test_expect_success 'status after commit' '
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
git status --porcelain >../actual &&
|
||||
cat >../status.expect <<EOF &&
|
||||
?? .gitignore
|
||||
?? dtwo/
|
||||
EOF
|
||||
test_cmp ../status.expect ../actual &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
gitignore invalidation: 0
|
||||
directory invalidation: 0
|
||||
opendir: 2
|
||||
EOF
|
||||
test_cmp ../trace.expect ../trace
|
||||
'
|
||||
|
||||
test_expect_success 'untracked cache correct after commit' '
|
||||
test-dump-untracked-cache >../actual &&
|
||||
cat >../expect <<EOF &&
|
||||
info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
|
||||
core.excludesfile 0000000000000000000000000000000000000000
|
||||
exclude_per_dir .gitignore
|
||||
flags 00000006
|
||||
/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
|
||||
.gitignore
|
||||
dtwo/
|
||||
/done/ 0000000000000000000000000000000000000000 recurse valid
|
||||
/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
|
||||
/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
|
||||
two
|
||||
EOF
|
||||
test_cmp ../expect ../actual
|
||||
'
|
||||
|
||||
test_expect_success 'set up sparse checkout' '
|
||||
echo "done/[a-z]*" >.git/info/sparse-checkout &&
|
||||
test_config core.sparsecheckout true &&
|
||||
git checkout master &&
|
||||
git update-index --force-untracked-cache &&
|
||||
git status --porcelain >/dev/null && # prime the cache
|
||||
test_path_is_missing done/.gitignore &&
|
||||
test_path_is_file done/one
|
||||
'
|
||||
|
||||
test_expect_success 'create/modify files, some of which are gitignored' '
|
||||
echo two bis >done/two &&
|
||||
echo three >done/three && # three is gitignored
|
||||
echo four >done/four && # four is gitignored at a higher level
|
||||
echo five >done/five # five is not gitignored
|
||||
'
|
||||
|
||||
test_expect_success 'test sparse status with untracked cache' '
|
||||
: >../trace &&
|
||||
avoid_racy &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
git status --porcelain >../status.actual &&
|
||||
cat >../status.expect <<EOF &&
|
||||
M done/two
|
||||
?? .gitignore
|
||||
?? done/five
|
||||
?? dtwo/
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.actual &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
gitignore invalidation: 1
|
||||
directory invalidation: 2
|
||||
opendir: 2
|
||||
EOF
|
||||
test_cmp ../trace.expect ../trace
|
||||
'
|
||||
|
||||
test_expect_success 'untracked cache correct after status' '
|
||||
test-dump-untracked-cache >../actual &&
|
||||
cat >../expect <<EOF &&
|
||||
info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
|
||||
core.excludesfile 0000000000000000000000000000000000000000
|
||||
exclude_per_dir .gitignore
|
||||
flags 00000006
|
||||
/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
|
||||
.gitignore
|
||||
dtwo/
|
||||
/done/ 1946f0437f90c5005533cbe1736a6451ca301714 recurse valid
|
||||
five
|
||||
/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
|
||||
/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
|
||||
two
|
||||
EOF
|
||||
test_cmp ../expect ../actual
|
||||
'
|
||||
|
||||
test_expect_success 'test sparse status again with untracked cache' '
|
||||
avoid_racy &&
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
git status --porcelain >../status.actual &&
|
||||
cat >../status.expect <<EOF &&
|
||||
M done/two
|
||||
?? .gitignore
|
||||
?? done/five
|
||||
?? dtwo/
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.actual &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
gitignore invalidation: 0
|
||||
directory invalidation: 0
|
||||
opendir: 0
|
||||
EOF
|
||||
test_cmp ../trace.expect ../trace
|
||||
'
|
||||
|
||||
test_expect_success 'set up for test of subdir and sparse checkouts' '
|
||||
mkdir done/sub &&
|
||||
mkdir done/sub/sub &&
|
||||
echo "sub" > done/sub/sub/file
|
||||
'
|
||||
|
||||
test_expect_success 'test sparse status with untracked cache and subdir' '
|
||||
avoid_racy &&
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
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 &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 2
|
||||
gitignore invalidation: 0
|
||||
directory invalidation: 1
|
||||
opendir: 3
|
||||
EOF
|
||||
test_cmp ../trace.expect ../trace
|
||||
'
|
||||
|
||||
test_expect_success 'verify untracked cache dump (sparse/subdirs)' '
|
||||
test-dump-untracked-cache >../actual &&
|
||||
cat >../expect <<EOF &&
|
||||
info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
|
||||
core.excludesfile 0000000000000000000000000000000000000000
|
||||
exclude_per_dir .gitignore
|
||||
flags 00000006
|
||||
/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
|
||||
.gitignore
|
||||
dtwo/
|
||||
/done/ 1946f0437f90c5005533cbe1736a6451ca301714 recurse valid
|
||||
five
|
||||
sub/
|
||||
/done/sub/ 0000000000000000000000000000000000000000 recurse check_only valid
|
||||
sub/
|
||||
/done/sub/sub/ 0000000000000000000000000000000000000000 recurse check_only valid
|
||||
file
|
||||
/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
|
||||
/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
|
||||
two
|
||||
EOF
|
||||
test_cmp ../expect ../actual
|
||||
'
|
||||
|
||||
test_expect_success 'test sparse status again with untracked cache and subdir' '
|
||||
avoid_racy &&
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
git status --porcelain >../status.actual &&
|
||||
test_cmp ../status.expect ../status.actual &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
gitignore invalidation: 0
|
||||
directory invalidation: 0
|
||||
opendir: 0
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user