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 ||
|
(!untracked || !untracked->valid ||
|
||||||
/*
|
/*
|
||||||
* .. and .gitignore does not exist before
|
* .. and .gitignore does not exist before
|
||||||
* (i.e. null exclude_sha1 and skip_worktree is
|
* (i.e. null exclude_sha1). Then we can skip
|
||||||
* not set). Then we can skip loading .gitignore,
|
* loading .gitignore, which would result in
|
||||||
* which would result in ENOENT anyway.
|
* ENOENT anyway.
|
||||||
* skip_worktree is taken care in read_directory()
|
|
||||||
*/
|
*/
|
||||||
!is_null_sha1(untracked->exclude_sha1))) {
|
!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,
|
static enum path_treatment treat_directory(struct dir_struct *dir,
|
||||||
struct untracked_cache_dir *untracked,
|
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)
|
const struct path_simplify *simplify)
|
||||||
{
|
{
|
||||||
/* The "len-1" is to strip the final '/' */
|
/* 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))
|
if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
|
||||||
return exclude ? path_excluded : path_untracked;
|
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,
|
return read_directory_recursive(dir, dirname, len,
|
||||||
untracked, 1, simplify);
|
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,
|
static enum path_treatment treat_one_path(struct dir_struct *dir,
|
||||||
struct untracked_cache_dir *untracked,
|
struct untracked_cache_dir *untracked,
|
||||||
struct strbuf *path,
|
struct strbuf *path,
|
||||||
|
int baselen,
|
||||||
const struct path_simplify *simplify,
|
const struct path_simplify *simplify,
|
||||||
int dtype, struct dirent *de)
|
int dtype, struct dirent *de)
|
||||||
{
|
{
|
||||||
@ -1496,8 +1497,8 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
|
|||||||
return path_none;
|
return path_none;
|
||||||
case DT_DIR:
|
case DT_DIR:
|
||||||
strbuf_addch(path, '/');
|
strbuf_addch(path, '/');
|
||||||
return treat_directory(dir, untracked, path->buf, path->len, exclude,
|
return treat_directory(dir, untracked, path->buf, path->len,
|
||||||
simplify);
|
baselen, exclude, simplify);
|
||||||
case DT_REG:
|
case DT_REG:
|
||||||
case DT_LNK:
|
case DT_LNK:
|
||||||
return exclude ? path_excluded : path_untracked;
|
return exclude ? path_excluded : path_untracked;
|
||||||
@ -1558,7 +1559,7 @@ static enum path_treatment treat_path(struct dir_struct *dir,
|
|||||||
return path_none;
|
return path_none;
|
||||||
|
|
||||||
dtype = DTYPE(de);
|
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)
|
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;
|
break;
|
||||||
if (simplify_away(sb.buf, sb.len, simplify))
|
if (simplify_away(sb.buf, sb.len, simplify))
|
||||||
break;
|
break;
|
||||||
if (treat_one_path(dir, NULL, &sb, simplify,
|
if (treat_one_path(dir, NULL, &sb, baselen, simplify,
|
||||||
DT_DIR, NULL) == path_none)
|
DT_DIR, NULL) == path_none)
|
||||||
break; /* do not recurse into it */
|
break; /* do not recurse into it */
|
||||||
if (len <= baselen) {
|
if (len <= baselen) {
|
||||||
@ -1880,7 +1881,6 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d
|
|||||||
const struct pathspec *pathspec)
|
const struct pathspec *pathspec)
|
||||||
{
|
{
|
||||||
struct untracked_cache_dir *root;
|
struct untracked_cache_dir *root;
|
||||||
int i;
|
|
||||||
|
|
||||||
if (!dir->untracked || getenv("GIT_DISABLE_UNTRACKED_CACHE"))
|
if (!dir->untracked || getenv("GIT_DISABLE_UNTRACKED_CACHE"))
|
||||||
return NULL;
|
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)
|
if (dir->exclude_list_group[EXC_CMDL].nr)
|
||||||
return NULL;
|
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)) {
|
if (!ident_in_untracked(dir->untracked)) {
|
||||||
warning(_("Untracked cache is disabled on this system."));
|
warning(_("Untracked cache is disabled on this system."));
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -2625,23 +2616,67 @@ done2:
|
|||||||
return uc;
|
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,
|
void untracked_cache_invalidate_path(struct index_state *istate,
|
||||||
const char *path)
|
const char *path)
|
||||||
{
|
{
|
||||||
const char *sep;
|
|
||||||
struct untracked_cache_dir *d;
|
|
||||||
if (!istate->untracked || !istate->untracked->root)
|
if (!istate->untracked || !istate->untracked->root)
|
||||||
return;
|
return;
|
||||||
sep = strrchr(path, '/');
|
invalidate_one_component(istate->untracked, istate->untracked->root,
|
||||||
if (sep)
|
path, strlen(path));
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void untracked_cache_remove_from_index(struct index_state *istate,
|
void untracked_cache_remove_from_index(struct index_state *istate,
|
||||||
|
@ -354,4 +354,219 @@ EOF
|
|||||||
test_cmp ../expect ../actual
|
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
|
test_done
|
||||||
|
Loading…
Reference in New Issue
Block a user