зеркало из https://github.com/microsoft/git.git
Merge branch 'kb/status-ignored-optim-2'
Fixes a handful of issues in the code to traverse working tree to find untracked and/or ignored files, cleans up and optimizes the codepath in general. * kb/status-ignored-optim-2: dir.c: git-status --ignored: don't scan the work tree twice dir.c: git-status --ignored: don't scan the work tree three times dir.c: git-status: avoid is_excluded checks for tracked files dir.c: replace is_path_excluded with now equivalent is_excluded API dir.c: unify is_excluded and is_path_excluded APIs dir.c: move prep_exclude dir.c: factor out parts of last_exclude_matching for later reuse dir.c: git-clean -d -X: don't delete tracked directories dir.c: make 'git-status --ignored' work within leading directories dir.c: git-status --ignored: don't list empty directories as ignored dir.c: git-ls-files --directories: don't hide empty directories dir.c: git-status --ignored: don't list empty ignored directories dir.c: git-status --ignored: don't list files in ignored directories dir.c: git-status --ignored: don't drop ignored directories
This commit is contained in:
Коммит
7093d2c0dd
|
@ -22,12 +22,23 @@ The notable options are:
|
||||||
|
|
||||||
`flags`::
|
`flags`::
|
||||||
|
|
||||||
A bit-field of options:
|
A bit-field of options (the `*IGNORED*` flags are mutually exclusive):
|
||||||
|
|
||||||
`DIR_SHOW_IGNORED`:::
|
`DIR_SHOW_IGNORED`:::
|
||||||
|
|
||||||
The traversal is for finding just ignored files, not unignored
|
Return just ignored files in `entries[]`, not untracked files.
|
||||||
files.
|
|
||||||
|
`DIR_SHOW_IGNORED_TOO`:::
|
||||||
|
|
||||||
|
Similar to `DIR_SHOW_IGNORED`, but return ignored files in `ignored[]`
|
||||||
|
in addition to untracked files in `entries[]`.
|
||||||
|
|
||||||
|
`DIR_COLLECT_IGNORED`:::
|
||||||
|
|
||||||
|
Special mode for git-add. Return ignored files in `ignored[]` and
|
||||||
|
untracked files in `entries[]`. Only returns ignored files that match
|
||||||
|
pathspec exactly (no wildcards). Does not recurse into ignored
|
||||||
|
directories.
|
||||||
|
|
||||||
`DIR_SHOW_OTHER_DIRECTORIES`:::
|
`DIR_SHOW_OTHER_DIRECTORIES`:::
|
||||||
|
|
||||||
|
@ -57,6 +68,14 @@ The result of the enumeration is left in these fields:
|
||||||
|
|
||||||
Internal use; keeps track of allocation of `entries[]` array.
|
Internal use; keeps track of allocation of `entries[]` array.
|
||||||
|
|
||||||
|
`ignored[]`::
|
||||||
|
|
||||||
|
An array of `struct dir_entry`, used for ignored paths with the
|
||||||
|
`DIR_SHOW_IGNORED_TOO` and `DIR_COLLECT_IGNORED` flags.
|
||||||
|
|
||||||
|
`ignored_nr`::
|
||||||
|
|
||||||
|
The number of members in `ignored[]` array.
|
||||||
|
|
||||||
Calling sequence
|
Calling sequence
|
||||||
----------------
|
----------------
|
||||||
|
|
|
@ -545,9 +545,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
|
||||||
|
|
||||||
if (pathspec) {
|
if (pathspec) {
|
||||||
int i;
|
int i;
|
||||||
struct path_exclude_check check;
|
|
||||||
|
|
||||||
path_exclude_check_init(&check, &dir);
|
|
||||||
if (!seen)
|
if (!seen)
|
||||||
seen = find_pathspecs_matching_against_index(pathspec);
|
seen = find_pathspecs_matching_against_index(pathspec);
|
||||||
for (i = 0; pathspec[i]; i++) {
|
for (i = 0; pathspec[i]; i++) {
|
||||||
|
@ -555,7 +553,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
|
||||||
&& !file_exists(pathspec[i])) {
|
&& !file_exists(pathspec[i])) {
|
||||||
if (ignore_missing) {
|
if (ignore_missing) {
|
||||||
int dtype = DT_UNKNOWN;
|
int dtype = DT_UNKNOWN;
|
||||||
if (is_path_excluded(&check, pathspec[i], -1, &dtype))
|
if (is_excluded(&dir, pathspec[i], &dtype))
|
||||||
dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i]));
|
dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i]));
|
||||||
} else
|
} else
|
||||||
die(_("pathspec '%s' did not match any files"),
|
die(_("pathspec '%s' did not match any files"),
|
||||||
|
@ -563,7 +561,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
free(seen);
|
free(seen);
|
||||||
path_exclude_check_clear(&check);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
plug_bulk_checkin();
|
plug_bulk_checkin();
|
||||||
|
|
|
@ -59,7 +59,6 @@ static int check_ignore(const char *prefix, const char **pathspec)
|
||||||
const char *path, *full_path;
|
const char *path, *full_path;
|
||||||
char *seen;
|
char *seen;
|
||||||
int num_ignored = 0, dtype = DT_UNKNOWN, i;
|
int num_ignored = 0, dtype = DT_UNKNOWN, i;
|
||||||
struct path_exclude_check check;
|
|
||||||
struct exclude *exclude;
|
struct exclude *exclude;
|
||||||
|
|
||||||
/* read_cache() is only necessary so we can watch out for submodules. */
|
/* read_cache() is only necessary so we can watch out for submodules. */
|
||||||
|
@ -67,7 +66,6 @@ static int check_ignore(const char *prefix, const char **pathspec)
|
||||||
die(_("index file corrupt"));
|
die(_("index file corrupt"));
|
||||||
|
|
||||||
memset(&dir, 0, sizeof(dir));
|
memset(&dir, 0, sizeof(dir));
|
||||||
dir.flags |= DIR_COLLECT_IGNORED;
|
|
||||||
setup_standard_excludes(&dir);
|
setup_standard_excludes(&dir);
|
||||||
|
|
||||||
if (!pathspec || !*pathspec) {
|
if (!pathspec || !*pathspec) {
|
||||||
|
@ -76,7 +74,6 @@ static int check_ignore(const char *prefix, const char **pathspec)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
path_exclude_check_init(&check, &dir);
|
|
||||||
/*
|
/*
|
||||||
* look for pathspecs matching entries in the index, since these
|
* look for pathspecs matching entries in the index, since these
|
||||||
* should not be ignored, in order to be consistent with
|
* should not be ignored, in order to be consistent with
|
||||||
|
@ -90,8 +87,7 @@ static int check_ignore(const char *prefix, const char **pathspec)
|
||||||
full_path = check_path_for_gitlink(full_path);
|
full_path = check_path_for_gitlink(full_path);
|
||||||
die_if_path_beyond_symlink(full_path, prefix);
|
die_if_path_beyond_symlink(full_path, prefix);
|
||||||
if (!seen[i]) {
|
if (!seen[i]) {
|
||||||
exclude = last_exclude_matching_path(&check, full_path,
|
exclude = last_exclude_matching(&dir, full_path, &dtype);
|
||||||
-1, &dtype);
|
|
||||||
if (exclude) {
|
if (exclude) {
|
||||||
if (!quiet)
|
if (!quiet)
|
||||||
output_exclude(path, exclude);
|
output_exclude(path, exclude);
|
||||||
|
@ -101,7 +97,6 @@ static int check_ignore(const char *prefix, const char **pathspec)
|
||||||
}
|
}
|
||||||
free(seen);
|
free(seen);
|
||||||
clear_directory(&dir);
|
clear_directory(&dir);
|
||||||
path_exclude_check_clear(&check);
|
|
||||||
|
|
||||||
return num_ignored;
|
return num_ignored;
|
||||||
}
|
}
|
||||||
|
|
|
@ -201,19 +201,15 @@ static void show_ru_info(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ce_excluded(struct path_exclude_check *check, struct cache_entry *ce)
|
static int ce_excluded(struct dir_struct *dir, struct cache_entry *ce)
|
||||||
{
|
{
|
||||||
int dtype = ce_to_dtype(ce);
|
int dtype = ce_to_dtype(ce);
|
||||||
return is_path_excluded(check, ce->name, ce_namelen(ce), &dtype);
|
return is_excluded(dir, ce->name, &dtype);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void show_files(struct dir_struct *dir)
|
static void show_files(struct dir_struct *dir)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
struct path_exclude_check check;
|
|
||||||
|
|
||||||
if ((dir->flags & DIR_SHOW_IGNORED))
|
|
||||||
path_exclude_check_init(&check, dir);
|
|
||||||
|
|
||||||
/* For cached/deleted files we don't need to even do the readdir */
|
/* For cached/deleted files we don't need to even do the readdir */
|
||||||
if (show_others || show_killed) {
|
if (show_others || show_killed) {
|
||||||
|
@ -227,7 +223,7 @@ static void show_files(struct dir_struct *dir)
|
||||||
for (i = 0; i < active_nr; i++) {
|
for (i = 0; i < active_nr; i++) {
|
||||||
struct cache_entry *ce = active_cache[i];
|
struct cache_entry *ce = active_cache[i];
|
||||||
if ((dir->flags & DIR_SHOW_IGNORED) &&
|
if ((dir->flags & DIR_SHOW_IGNORED) &&
|
||||||
!ce_excluded(&check, ce))
|
!ce_excluded(dir, ce))
|
||||||
continue;
|
continue;
|
||||||
if (show_unmerged && !ce_stage(ce))
|
if (show_unmerged && !ce_stage(ce))
|
||||||
continue;
|
continue;
|
||||||
|
@ -243,7 +239,7 @@ static void show_files(struct dir_struct *dir)
|
||||||
struct stat st;
|
struct stat st;
|
||||||
int err;
|
int err;
|
||||||
if ((dir->flags & DIR_SHOW_IGNORED) &&
|
if ((dir->flags & DIR_SHOW_IGNORED) &&
|
||||||
!ce_excluded(&check, ce))
|
!ce_excluded(dir, ce))
|
||||||
continue;
|
continue;
|
||||||
if (ce->ce_flags & CE_UPDATE)
|
if (ce->ce_flags & CE_UPDATE)
|
||||||
continue;
|
continue;
|
||||||
|
@ -256,9 +252,6 @@ static void show_files(struct dir_struct *dir)
|
||||||
show_ce_entry(tag_modified, ce);
|
show_ce_entry(tag_modified, ce);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((dir->flags & DIR_SHOW_IGNORED))
|
|
||||||
path_exclude_check_clear(&check);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
525
dir.c
525
dir.c
|
@ -17,7 +17,21 @@ struct path_simplify {
|
||||||
const char *path;
|
const char *path;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int read_directory_recursive(struct dir_struct *dir, const char *path, int len,
|
/*
|
||||||
|
* Tells read_directory_recursive how a file or directory should be treated.
|
||||||
|
* Values are ordered by significance, e.g. if a directory contains both
|
||||||
|
* excluded and untracked files, it is listed as untracked because
|
||||||
|
* path_untracked > path_excluded.
|
||||||
|
*/
|
||||||
|
enum path_treatment {
|
||||||
|
path_none = 0,
|
||||||
|
path_recurse,
|
||||||
|
path_excluded,
|
||||||
|
path_untracked
|
||||||
|
};
|
||||||
|
|
||||||
|
static enum path_treatment read_directory_recursive(struct dir_struct *dir,
|
||||||
|
const char *path, int len,
|
||||||
int check_only, const struct path_simplify *simplify);
|
int check_only, const struct path_simplify *simplify);
|
||||||
static int get_dtype(struct dirent *de, const char *path, int len);
|
static int get_dtype(struct dirent *de, const char *path, int len);
|
||||||
|
|
||||||
|
@ -578,78 +592,6 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname)
|
||||||
die("cannot use %s as an exclude file", fname);
|
die("cannot use %s as an exclude file", fname);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Loads the per-directory exclude list for the substring of base
|
|
||||||
* which has a char length of baselen.
|
|
||||||
*/
|
|
||||||
static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
|
|
||||||
{
|
|
||||||
struct exclude_list_group *group;
|
|
||||||
struct exclude_list *el;
|
|
||||||
struct exclude_stack *stk = NULL;
|
|
||||||
int current;
|
|
||||||
|
|
||||||
if ((!dir->exclude_per_dir) ||
|
|
||||||
(baselen + strlen(dir->exclude_per_dir) >= PATH_MAX))
|
|
||||||
return; /* too long a path -- ignore */
|
|
||||||
|
|
||||||
group = &dir->exclude_list_group[EXC_DIRS];
|
|
||||||
|
|
||||||
/* Pop the exclude lists from the EXCL_DIRS exclude_list_group
|
|
||||||
* which originate from directories not in the prefix of the
|
|
||||||
* path being checked. */
|
|
||||||
while ((stk = dir->exclude_stack) != NULL) {
|
|
||||||
if (stk->baselen <= baselen &&
|
|
||||||
!strncmp(dir->basebuf, base, stk->baselen))
|
|
||||||
break;
|
|
||||||
el = &group->el[dir->exclude_stack->exclude_ix];
|
|
||||||
dir->exclude_stack = stk->prev;
|
|
||||||
free((char *)el->src); /* see strdup() below */
|
|
||||||
clear_exclude_list(el);
|
|
||||||
free(stk);
|
|
||||||
group->nr--;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Read from the parent directories and push them down. */
|
|
||||||
current = stk ? stk->baselen : -1;
|
|
||||||
while (current < baselen) {
|
|
||||||
struct exclude_stack *stk = xcalloc(1, sizeof(*stk));
|
|
||||||
const char *cp;
|
|
||||||
|
|
||||||
if (current < 0) {
|
|
||||||
cp = base;
|
|
||||||
current = 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
cp = strchr(base + current + 1, '/');
|
|
||||||
if (!cp)
|
|
||||||
die("oops in prep_exclude");
|
|
||||||
cp++;
|
|
||||||
}
|
|
||||||
stk->prev = dir->exclude_stack;
|
|
||||||
stk->baselen = cp - base;
|
|
||||||
memcpy(dir->basebuf + current, base + current,
|
|
||||||
stk->baselen - current);
|
|
||||||
strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);
|
|
||||||
/*
|
|
||||||
* dir->basebuf gets reused by the traversal, but we
|
|
||||||
* need fname to remain unchanged to ensure the src
|
|
||||||
* member of each struct exclude correctly
|
|
||||||
* back-references its source file. Other invocations
|
|
||||||
* of add_exclude_list provide stable strings, so we
|
|
||||||
* strdup() and free() here in the caller.
|
|
||||||
*/
|
|
||||||
el = add_exclude_list(dir, EXC_DIRS, strdup(dir->basebuf));
|
|
||||||
stk->exclude_ix = group->nr - 1;
|
|
||||||
add_excludes_from_file_to_list(dir->basebuf,
|
|
||||||
dir->basebuf, stk->baselen,
|
|
||||||
el, 1);
|
|
||||||
dir->exclude_stack = stk;
|
|
||||||
current = stk->baselen;
|
|
||||||
}
|
|
||||||
dir->basebuf[baselen] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
int match_basename(const char *basename, int basenamelen,
|
int match_basename(const char *basename, int basenamelen,
|
||||||
const char *pattern, int prefix, int patternlen,
|
const char *pattern, int prefix, int patternlen,
|
||||||
int flags)
|
int flags)
|
||||||
|
@ -795,25 +737,13 @@ int is_excluded_from_list(const char *pathname,
|
||||||
return -1; /* undecided */
|
return -1; /* undecided */
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static struct exclude *last_exclude_matching_from_lists(struct dir_struct *dir,
|
||||||
* Loads the exclude lists for the directory containing pathname, then
|
const char *pathname, int pathlen, const char *basename,
|
||||||
* scans all exclude lists to determine whether pathname is excluded.
|
int *dtype_p)
|
||||||
* Returns the exclude_list element which matched, or NULL for
|
|
||||||
* undecided.
|
|
||||||
*/
|
|
||||||
static struct exclude *last_exclude_matching(struct dir_struct *dir,
|
|
||||||
const char *pathname,
|
|
||||||
int *dtype_p)
|
|
||||||
{
|
{
|
||||||
int pathlen = strlen(pathname);
|
|
||||||
int i, j;
|
int i, j;
|
||||||
struct exclude_list_group *group;
|
struct exclude_list_group *group;
|
||||||
struct exclude *exclude;
|
struct exclude *exclude;
|
||||||
const char *basename = strrchr(pathname, '/');
|
|
||||||
basename = (basename) ? basename+1 : pathname;
|
|
||||||
|
|
||||||
prep_exclude(dir, pathname, basename-pathname);
|
|
||||||
|
|
||||||
for (i = EXC_CMDL; i <= EXC_FILE; i++) {
|
for (i = EXC_CMDL; i <= EXC_FILE; i++) {
|
||||||
group = &dir->exclude_list_group[i];
|
group = &dir->exclude_list_group[i];
|
||||||
for (j = group->nr - 1; j >= 0; j--) {
|
for (j = group->nr - 1; j >= 0; j--) {
|
||||||
|
@ -827,12 +757,129 @@ static struct exclude *last_exclude_matching(struct dir_struct *dir,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Loads the per-directory exclude list for the substring of base
|
||||||
|
* which has a char length of baselen.
|
||||||
|
*/
|
||||||
|
static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
|
||||||
|
{
|
||||||
|
struct exclude_list_group *group;
|
||||||
|
struct exclude_list *el;
|
||||||
|
struct exclude_stack *stk = NULL;
|
||||||
|
int current;
|
||||||
|
|
||||||
|
group = &dir->exclude_list_group[EXC_DIRS];
|
||||||
|
|
||||||
|
/* Pop the exclude lists from the EXCL_DIRS exclude_list_group
|
||||||
|
* which originate from directories not in the prefix of the
|
||||||
|
* path being checked. */
|
||||||
|
while ((stk = dir->exclude_stack) != NULL) {
|
||||||
|
if (stk->baselen <= baselen &&
|
||||||
|
!strncmp(dir->basebuf, base, stk->baselen))
|
||||||
|
break;
|
||||||
|
el = &group->el[dir->exclude_stack->exclude_ix];
|
||||||
|
dir->exclude_stack = stk->prev;
|
||||||
|
dir->exclude = NULL;
|
||||||
|
free((char *)el->src); /* see strdup() below */
|
||||||
|
clear_exclude_list(el);
|
||||||
|
free(stk);
|
||||||
|
group->nr--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skip traversing into sub directories if the parent is excluded */
|
||||||
|
if (dir->exclude)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Read from the parent directories and push them down. */
|
||||||
|
current = stk ? stk->baselen : -1;
|
||||||
|
while (current < baselen) {
|
||||||
|
struct exclude_stack *stk = xcalloc(1, sizeof(*stk));
|
||||||
|
const char *cp;
|
||||||
|
|
||||||
|
if (current < 0) {
|
||||||
|
cp = base;
|
||||||
|
current = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cp = strchr(base + current + 1, '/');
|
||||||
|
if (!cp)
|
||||||
|
die("oops in prep_exclude");
|
||||||
|
cp++;
|
||||||
|
}
|
||||||
|
stk->prev = dir->exclude_stack;
|
||||||
|
stk->baselen = cp - base;
|
||||||
|
stk->exclude_ix = group->nr;
|
||||||
|
el = add_exclude_list(dir, EXC_DIRS, NULL);
|
||||||
|
memcpy(dir->basebuf + current, base + current,
|
||||||
|
stk->baselen - current);
|
||||||
|
|
||||||
|
/* Abort if the directory is excluded */
|
||||||
|
if (stk->baselen) {
|
||||||
|
int dt = DT_DIR;
|
||||||
|
dir->basebuf[stk->baselen - 1] = 0;
|
||||||
|
dir->exclude = last_exclude_matching_from_lists(dir,
|
||||||
|
dir->basebuf, stk->baselen - 1,
|
||||||
|
dir->basebuf + current, &dt);
|
||||||
|
dir->basebuf[stk->baselen - 1] = '/';
|
||||||
|
if (dir->exclude) {
|
||||||
|
dir->basebuf[stk->baselen] = 0;
|
||||||
|
dir->exclude_stack = stk;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Try to read per-directory file unless path is too long */
|
||||||
|
if (dir->exclude_per_dir &&
|
||||||
|
stk->baselen + strlen(dir->exclude_per_dir) < PATH_MAX) {
|
||||||
|
strcpy(dir->basebuf + stk->baselen,
|
||||||
|
dir->exclude_per_dir);
|
||||||
|
/*
|
||||||
|
* dir->basebuf gets reused by the traversal, but we
|
||||||
|
* need fname to remain unchanged to ensure the src
|
||||||
|
* member of each struct exclude correctly
|
||||||
|
* back-references its source file. Other invocations
|
||||||
|
* of add_exclude_list provide stable strings, so we
|
||||||
|
* strdup() and free() here in the caller.
|
||||||
|
*/
|
||||||
|
el->src = strdup(dir->basebuf);
|
||||||
|
add_excludes_from_file_to_list(dir->basebuf,
|
||||||
|
dir->basebuf, stk->baselen, el, 1);
|
||||||
|
}
|
||||||
|
dir->exclude_stack = stk;
|
||||||
|
current = stk->baselen;
|
||||||
|
}
|
||||||
|
dir->basebuf[baselen] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Loads the exclude lists for the directory containing pathname, then
|
||||||
|
* scans all exclude lists to determine whether pathname is excluded.
|
||||||
|
* Returns the exclude_list element which matched, or NULL for
|
||||||
|
* undecided.
|
||||||
|
*/
|
||||||
|
struct exclude *last_exclude_matching(struct dir_struct *dir,
|
||||||
|
const char *pathname,
|
||||||
|
int *dtype_p)
|
||||||
|
{
|
||||||
|
int pathlen = strlen(pathname);
|
||||||
|
const char *basename = strrchr(pathname, '/');
|
||||||
|
basename = (basename) ? basename+1 : pathname;
|
||||||
|
|
||||||
|
prep_exclude(dir, pathname, basename-pathname);
|
||||||
|
|
||||||
|
if (dir->exclude)
|
||||||
|
return dir->exclude;
|
||||||
|
|
||||||
|
return last_exclude_matching_from_lists(dir, pathname, pathlen,
|
||||||
|
basename, dtype_p);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Loads the exclude lists for the directory containing pathname, then
|
* Loads the exclude lists for the directory containing pathname, then
|
||||||
* scans all exclude lists to determine whether pathname is excluded.
|
* scans all exclude lists to determine whether pathname is excluded.
|
||||||
* Returns 1 if true, otherwise 0.
|
* Returns 1 if true, otherwise 0.
|
||||||
*/
|
*/
|
||||||
static int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
|
int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
|
||||||
{
|
{
|
||||||
struct exclude *exclude =
|
struct exclude *exclude =
|
||||||
last_exclude_matching(dir, pathname, dtype_p);
|
last_exclude_matching(dir, pathname, dtype_p);
|
||||||
|
@ -841,93 +888,6 @@ static int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void path_exclude_check_init(struct path_exclude_check *check,
|
|
||||||
struct dir_struct *dir)
|
|
||||||
{
|
|
||||||
check->dir = dir;
|
|
||||||
check->exclude = NULL;
|
|
||||||
strbuf_init(&check->path, 256);
|
|
||||||
}
|
|
||||||
|
|
||||||
void path_exclude_check_clear(struct path_exclude_check *check)
|
|
||||||
{
|
|
||||||
strbuf_release(&check->path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* For each subdirectory in name, starting with the top-most, checks
|
|
||||||
* to see if that subdirectory is excluded, and if so, returns the
|
|
||||||
* corresponding exclude structure. Otherwise, checks whether name
|
|
||||||
* itself (which is presumably a file) is excluded.
|
|
||||||
*
|
|
||||||
* A path to a directory known to be excluded is left in check->path to
|
|
||||||
* optimize for repeated checks for files in the same excluded directory.
|
|
||||||
*/
|
|
||||||
struct exclude *last_exclude_matching_path(struct path_exclude_check *check,
|
|
||||||
const char *name, int namelen,
|
|
||||||
int *dtype)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
struct strbuf *path = &check->path;
|
|
||||||
struct exclude *exclude;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* we allow the caller to pass namelen as an optimization; it
|
|
||||||
* must match the length of the name, as we eventually call
|
|
||||||
* is_excluded() on the whole name string.
|
|
||||||
*/
|
|
||||||
if (namelen < 0)
|
|
||||||
namelen = strlen(name);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If path is non-empty, and name is equal to path or a
|
|
||||||
* subdirectory of path, name should be excluded, because
|
|
||||||
* it's inside a directory which is already known to be
|
|
||||||
* excluded and was previously left in check->path.
|
|
||||||
*/
|
|
||||||
if (path->len &&
|
|
||||||
path->len <= namelen &&
|
|
||||||
!memcmp(name, path->buf, path->len) &&
|
|
||||||
(!name[path->len] || name[path->len] == '/'))
|
|
||||||
return check->exclude;
|
|
||||||
|
|
||||||
strbuf_setlen(path, 0);
|
|
||||||
for (i = 0; name[i]; i++) {
|
|
||||||
int ch = name[i];
|
|
||||||
|
|
||||||
if (ch == '/') {
|
|
||||||
int dt = DT_DIR;
|
|
||||||
exclude = last_exclude_matching(check->dir,
|
|
||||||
path->buf, &dt);
|
|
||||||
if (exclude) {
|
|
||||||
check->exclude = exclude;
|
|
||||||
return exclude;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
strbuf_addch(path, ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* An entry in the index; cannot be a directory with subentries */
|
|
||||||
strbuf_setlen(path, 0);
|
|
||||||
|
|
||||||
return last_exclude_matching(check->dir, name, dtype);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Is this name excluded? This is for a caller like show_files() that
|
|
||||||
* do not honor directory hierarchy and iterate through paths that are
|
|
||||||
* possibly in an ignored directory.
|
|
||||||
*/
|
|
||||||
int is_path_excluded(struct path_exclude_check *check,
|
|
||||||
const char *name, int namelen, int *dtype)
|
|
||||||
{
|
|
||||||
struct exclude *exclude =
|
|
||||||
last_exclude_matching_path(check, name, namelen, dtype);
|
|
||||||
if (exclude)
|
|
||||||
return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct dir_entry *dir_entry_new(const char *pathname, int len)
|
static struct dir_entry *dir_entry_new(const char *pathname, int len)
|
||||||
{
|
{
|
||||||
struct dir_entry *ent;
|
struct dir_entry *ent;
|
||||||
|
@ -941,8 +901,7 @@ static struct dir_entry *dir_entry_new(const char *pathname, int len)
|
||||||
|
|
||||||
static struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)
|
static struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)
|
||||||
{
|
{
|
||||||
if (!(dir->flags & DIR_SHOW_IGNORED) &&
|
if (cache_name_exists(pathname, len, ignore_case))
|
||||||
cache_name_exists(pathname, len, ignore_case))
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc);
|
ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc);
|
||||||
|
@ -1044,9 +1003,8 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
|
||||||
* traversal routine.
|
* traversal routine.
|
||||||
*
|
*
|
||||||
* Case 1: If we *already* have entries in the index under that
|
* Case 1: If we *already* have entries in the index under that
|
||||||
* directory name, we recurse into the directory to see all the files,
|
* directory name, we always recurse into the directory to see
|
||||||
* unless the directory is excluded and we want to show ignored
|
* all the files.
|
||||||
* directories
|
|
||||||
*
|
*
|
||||||
* Case 2: If we *already* have that directory name as a gitlink,
|
* Case 2: If we *already* have that directory name as a gitlink,
|
||||||
* we always continue to see it as a gitlink, regardless of whether
|
* we always continue to see it as a gitlink, regardless of whether
|
||||||
|
@ -1058,38 +1016,26 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
|
||||||
*
|
*
|
||||||
* (a) if "show_other_directories" is true, we show it as
|
* (a) if "show_other_directories" is true, we show it as
|
||||||
* just a directory, unless "hide_empty_directories" is
|
* just a directory, unless "hide_empty_directories" is
|
||||||
* also true and the directory is empty, in which case
|
* also true, in which case we need to check if it contains any
|
||||||
* we just ignore it entirely.
|
* untracked and / or ignored files.
|
||||||
* if we are looking for ignored directories, look if it
|
|
||||||
* contains only ignored files to decide if it must be shown as
|
|
||||||
* ignored or not.
|
|
||||||
* (b) if it looks like a git directory, and we don't have
|
* (b) if it looks like a git directory, and we don't have
|
||||||
* 'no_gitlinks' set we treat it as a gitlink, and show it
|
* 'no_gitlinks' set we treat it as a gitlink, and show it
|
||||||
* as a directory.
|
* as a directory.
|
||||||
* (c) otherwise, we recurse into it.
|
* (c) otherwise, we recurse into it.
|
||||||
*/
|
*/
|
||||||
enum directory_treatment {
|
static enum path_treatment treat_directory(struct dir_struct *dir,
|
||||||
show_directory,
|
|
||||||
ignore_directory,
|
|
||||||
recurse_into_directory
|
|
||||||
};
|
|
||||||
|
|
||||||
static enum directory_treatment treat_directory(struct dir_struct *dir,
|
|
||||||
const char *dirname, int len, int exclude,
|
const char *dirname, int len, 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 '/' */
|
||||||
switch (directory_exists_in_index(dirname, len-1)) {
|
switch (directory_exists_in_index(dirname, len-1)) {
|
||||||
case index_directory:
|
case index_directory:
|
||||||
if ((dir->flags & DIR_SHOW_OTHER_DIRECTORIES) && exclude)
|
return path_recurse;
|
||||||
break;
|
|
||||||
|
|
||||||
return recurse_into_directory;
|
|
||||||
|
|
||||||
case index_gitdir:
|
case index_gitdir:
|
||||||
if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
|
if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
|
||||||
return ignore_directory;
|
return path_none;
|
||||||
return show_directory;
|
return path_untracked;
|
||||||
|
|
||||||
case index_nonexistent:
|
case index_nonexistent:
|
||||||
if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
|
if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
|
||||||
|
@ -1097,72 +1043,17 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,
|
||||||
if (!(dir->flags & DIR_NO_GITLINKS)) {
|
if (!(dir->flags & DIR_NO_GITLINKS)) {
|
||||||
unsigned char sha1[20];
|
unsigned char sha1[20];
|
||||||
if (resolve_gitlink_ref(dirname, "HEAD", sha1) == 0)
|
if (resolve_gitlink_ref(dirname, "HEAD", sha1) == 0)
|
||||||
return show_directory;
|
return path_untracked;
|
||||||
}
|
}
|
||||||
return recurse_into_directory;
|
return path_recurse;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This is the "show_other_directories" case */
|
/* This is the "show_other_directories" case */
|
||||||
|
|
||||||
/*
|
if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
|
||||||
* We are looking for ignored files and our directory is not ignored,
|
return exclude ? path_excluded : path_untracked;
|
||||||
* check if it contains only ignored files
|
|
||||||
*/
|
|
||||||
if ((dir->flags & DIR_SHOW_IGNORED) && !exclude) {
|
|
||||||
int ignored;
|
|
||||||
dir->flags &= ~DIR_SHOW_IGNORED;
|
|
||||||
dir->flags |= DIR_HIDE_EMPTY_DIRECTORIES;
|
|
||||||
ignored = read_directory_recursive(dir, dirname, len, 1, simplify);
|
|
||||||
dir->flags &= ~DIR_HIDE_EMPTY_DIRECTORIES;
|
|
||||||
dir->flags |= DIR_SHOW_IGNORED;
|
|
||||||
|
|
||||||
return ignored ? ignore_directory : show_directory;
|
return read_directory_recursive(dir, dirname, len, 1, simplify);
|
||||||
}
|
|
||||||
if (!(dir->flags & DIR_SHOW_IGNORED) &&
|
|
||||||
!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
|
|
||||||
return show_directory;
|
|
||||||
if (!read_directory_recursive(dir, dirname, len, 1, simplify))
|
|
||||||
return ignore_directory;
|
|
||||||
return show_directory;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Decide what to do when we find a file while traversing the
|
|
||||||
* filesystem. Mostly two cases:
|
|
||||||
*
|
|
||||||
* 1. We are looking for ignored files
|
|
||||||
* (a) File is ignored, include it
|
|
||||||
* (b) File is in ignored path, include it
|
|
||||||
* (c) File is not ignored, exclude it
|
|
||||||
*
|
|
||||||
* 2. Other scenarios, include the file if not excluded
|
|
||||||
*
|
|
||||||
* Return 1 for exclude, 0 for include.
|
|
||||||
*/
|
|
||||||
static int treat_file(struct dir_struct *dir, struct strbuf *path, int exclude, int *dtype)
|
|
||||||
{
|
|
||||||
struct path_exclude_check check;
|
|
||||||
int exclude_file = 0;
|
|
||||||
|
|
||||||
if (exclude)
|
|
||||||
exclude_file = !(dir->flags & DIR_SHOW_IGNORED);
|
|
||||||
else if (dir->flags & DIR_SHOW_IGNORED) {
|
|
||||||
/* Always exclude indexed files */
|
|
||||||
struct cache_entry *ce = index_name_exists(&the_index,
|
|
||||||
path->buf, path->len, ignore_case);
|
|
||||||
|
|
||||||
if (ce)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
path_exclude_check_init(&check, dir);
|
|
||||||
|
|
||||||
if (!is_path_excluded(&check, path->buf, path->len, dtype))
|
|
||||||
exclude_file = 1;
|
|
||||||
|
|
||||||
path_exclude_check_clear(&check);
|
|
||||||
}
|
|
||||||
|
|
||||||
return exclude_file;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1277,57 +1168,40 @@ static int get_dtype(struct dirent *de, const char *path, int len)
|
||||||
return dtype;
|
return dtype;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum path_treatment {
|
|
||||||
path_ignored,
|
|
||||||
path_handled,
|
|
||||||
path_recurse
|
|
||||||
};
|
|
||||||
|
|
||||||
static enum path_treatment treat_one_path(struct dir_struct *dir,
|
static enum path_treatment treat_one_path(struct dir_struct *dir,
|
||||||
struct strbuf *path,
|
struct strbuf *path,
|
||||||
const struct path_simplify *simplify,
|
const struct path_simplify *simplify,
|
||||||
int dtype, struct dirent *de)
|
int dtype, struct dirent *de)
|
||||||
{
|
{
|
||||||
int exclude = is_excluded(dir, path->buf, &dtype);
|
int exclude;
|
||||||
if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
|
if (dtype == DT_UNKNOWN)
|
||||||
&& exclude_matches_pathspec(path->buf, path->len, simplify))
|
dtype = get_dtype(de, path->buf, path->len);
|
||||||
dir_add_ignored(dir, path->buf, path->len);
|
|
||||||
|
/* Always exclude indexed files */
|
||||||
|
if (dtype != DT_DIR &&
|
||||||
|
cache_name_exists(path->buf, path->len, ignore_case))
|
||||||
|
return path_none;
|
||||||
|
|
||||||
|
exclude = is_excluded(dir, path->buf, &dtype);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Excluded? If we don't explicitly want to show
|
* Excluded? If we don't explicitly want to show
|
||||||
* ignored files, ignore it
|
* ignored files, ignore it
|
||||||
*/
|
*/
|
||||||
if (exclude && !(dir->flags & DIR_SHOW_IGNORED))
|
if (exclude && !(dir->flags & (DIR_SHOW_IGNORED|DIR_SHOW_IGNORED_TOO)))
|
||||||
return path_ignored;
|
return path_excluded;
|
||||||
|
|
||||||
if (dtype == DT_UNKNOWN)
|
|
||||||
dtype = get_dtype(de, path->buf, path->len);
|
|
||||||
|
|
||||||
switch (dtype) {
|
switch (dtype) {
|
||||||
default:
|
default:
|
||||||
return path_ignored;
|
return path_none;
|
||||||
case DT_DIR:
|
case DT_DIR:
|
||||||
strbuf_addch(path, '/');
|
strbuf_addch(path, '/');
|
||||||
|
return treat_directory(dir, path->buf, path->len, exclude,
|
||||||
switch (treat_directory(dir, path->buf, path->len, exclude, simplify)) {
|
simplify);
|
||||||
case show_directory:
|
|
||||||
break;
|
|
||||||
case recurse_into_directory:
|
|
||||||
return path_recurse;
|
|
||||||
case ignore_directory:
|
|
||||||
return path_ignored;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case DT_REG:
|
case DT_REG:
|
||||||
case DT_LNK:
|
case DT_LNK:
|
||||||
switch (treat_file(dir, path, exclude, &dtype)) {
|
return exclude ? path_excluded : path_untracked;
|
||||||
case 1:
|
|
||||||
return path_ignored;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return path_handled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static enum path_treatment treat_path(struct dir_struct *dir,
|
static enum path_treatment treat_path(struct dir_struct *dir,
|
||||||
|
@ -1339,11 +1213,11 @@ static enum path_treatment treat_path(struct dir_struct *dir,
|
||||||
int dtype;
|
int dtype;
|
||||||
|
|
||||||
if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
|
if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
|
||||||
return path_ignored;
|
return path_none;
|
||||||
strbuf_setlen(path, baselen);
|
strbuf_setlen(path, baselen);
|
||||||
strbuf_addstr(path, de->d_name);
|
strbuf_addstr(path, de->d_name);
|
||||||
if (simplify_away(path->buf, path->len, simplify))
|
if (simplify_away(path->buf, path->len, simplify))
|
||||||
return path_ignored;
|
return path_none;
|
||||||
|
|
||||||
dtype = DTYPE(de);
|
dtype = DTYPE(de);
|
||||||
return treat_one_path(dir, path, simplify, dtype, de);
|
return treat_one_path(dir, path, simplify, dtype, de);
|
||||||
|
@ -1357,14 +1231,16 @@ static enum path_treatment treat_path(struct dir_struct *dir,
|
||||||
*
|
*
|
||||||
* Also, we ignore the name ".git" (even if it is not a directory).
|
* Also, we ignore the name ".git" (even if it is not a directory).
|
||||||
* That likely will not change.
|
* That likely will not change.
|
||||||
|
*
|
||||||
|
* Returns the most significant path_treatment value encountered in the scan.
|
||||||
*/
|
*/
|
||||||
static int read_directory_recursive(struct dir_struct *dir,
|
static enum path_treatment read_directory_recursive(struct dir_struct *dir,
|
||||||
const char *base, int baselen,
|
const char *base, int baselen,
|
||||||
int check_only,
|
int check_only,
|
||||||
const struct path_simplify *simplify)
|
const struct path_simplify *simplify)
|
||||||
{
|
{
|
||||||
DIR *fdir;
|
DIR *fdir;
|
||||||
int contents = 0;
|
enum path_treatment state, subdir_state, dir_state = path_none;
|
||||||
struct dirent *de;
|
struct dirent *de;
|
||||||
struct strbuf path = STRBUF_INIT;
|
struct strbuf path = STRBUF_INIT;
|
||||||
|
|
||||||
|
@ -1375,27 +1251,53 @@ static int read_directory_recursive(struct dir_struct *dir,
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
while ((de = readdir(fdir)) != NULL) {
|
while ((de = readdir(fdir)) != NULL) {
|
||||||
switch (treat_path(dir, de, &path, baselen, simplify)) {
|
/* check how the file or directory should be treated */
|
||||||
case path_recurse:
|
state = treat_path(dir, de, &path, baselen, simplify);
|
||||||
contents += read_directory_recursive(dir, path.buf,
|
if (state > dir_state)
|
||||||
path.len, 0,
|
dir_state = state;
|
||||||
simplify);
|
|
||||||
|
/* recurse into subdir if instructed by treat_path */
|
||||||
|
if (state == path_recurse) {
|
||||||
|
subdir_state = read_directory_recursive(dir, path.buf,
|
||||||
|
path.len, check_only, simplify);
|
||||||
|
if (subdir_state > dir_state)
|
||||||
|
dir_state = subdir_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_only) {
|
||||||
|
/* abort early if maximum state has been reached */
|
||||||
|
if (dir_state == path_untracked)
|
||||||
|
break;
|
||||||
|
/* skip the dir_add_* part */
|
||||||
continue;
|
continue;
|
||||||
case path_ignored:
|
}
|
||||||
continue;
|
|
||||||
case path_handled:
|
/* add the path to the appropriate result list */
|
||||||
|
switch (state) {
|
||||||
|
case path_excluded:
|
||||||
|
if (dir->flags & DIR_SHOW_IGNORED)
|
||||||
|
dir_add_name(dir, path.buf, path.len);
|
||||||
|
else if ((dir->flags & DIR_SHOW_IGNORED_TOO) ||
|
||||||
|
((dir->flags & DIR_COLLECT_IGNORED) &&
|
||||||
|
exclude_matches_pathspec(path.buf, path.len,
|
||||||
|
simplify)))
|
||||||
|
dir_add_ignored(dir, path.buf, path.len);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case path_untracked:
|
||||||
|
if (!(dir->flags & DIR_SHOW_IGNORED))
|
||||||
|
dir_add_name(dir, path.buf, path.len);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
contents++;
|
|
||||||
if (check_only)
|
|
||||||
break;
|
|
||||||
dir_add_name(dir, path.buf, path.len);
|
|
||||||
}
|
}
|
||||||
closedir(fdir);
|
closedir(fdir);
|
||||||
out:
|
out:
|
||||||
strbuf_release(&path);
|
strbuf_release(&path);
|
||||||
|
|
||||||
return contents;
|
return dir_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int cmp_name(const void *p1, const void *p2)
|
static int cmp_name(const void *p1, const void *p2)
|
||||||
|
@ -1444,12 +1346,14 @@ static int treat_leading_path(struct dir_struct *dir,
|
||||||
struct strbuf sb = STRBUF_INIT;
|
struct strbuf sb = STRBUF_INIT;
|
||||||
int baselen, rc = 0;
|
int baselen, rc = 0;
|
||||||
const char *cp;
|
const char *cp;
|
||||||
|
int old_flags = dir->flags;
|
||||||
|
|
||||||
while (len && path[len - 1] == '/')
|
while (len && path[len - 1] == '/')
|
||||||
len--;
|
len--;
|
||||||
if (!len)
|
if (!len)
|
||||||
return 1;
|
return 1;
|
||||||
baselen = 0;
|
baselen = 0;
|
||||||
|
dir->flags &= ~DIR_SHOW_OTHER_DIRECTORIES;
|
||||||
while (1) {
|
while (1) {
|
||||||
cp = path + baselen + !!baselen;
|
cp = path + baselen + !!baselen;
|
||||||
cp = memchr(cp, '/', path + len - cp);
|
cp = memchr(cp, '/', path + len - cp);
|
||||||
|
@ -1464,7 +1368,7 @@ static int treat_leading_path(struct dir_struct *dir,
|
||||||
if (simplify_away(sb.buf, sb.len, simplify))
|
if (simplify_away(sb.buf, sb.len, simplify))
|
||||||
break;
|
break;
|
||||||
if (treat_one_path(dir, &sb, simplify,
|
if (treat_one_path(dir, &sb, simplify,
|
||||||
DT_DIR, NULL) == path_ignored)
|
DT_DIR, NULL) == path_none)
|
||||||
break; /* do not recurse into it */
|
break; /* do not recurse into it */
|
||||||
if (len <= baselen) {
|
if (len <= baselen) {
|
||||||
rc = 1;
|
rc = 1;
|
||||||
|
@ -1472,6 +1376,7 @@ static int treat_leading_path(struct dir_struct *dir,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
strbuf_release(&sb);
|
strbuf_release(&sb);
|
||||||
|
dir->flags = old_flags;
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
25
dir.h
25
dir.h
|
@ -79,7 +79,8 @@ struct dir_struct {
|
||||||
DIR_SHOW_OTHER_DIRECTORIES = 1<<1,
|
DIR_SHOW_OTHER_DIRECTORIES = 1<<1,
|
||||||
DIR_HIDE_EMPTY_DIRECTORIES = 1<<2,
|
DIR_HIDE_EMPTY_DIRECTORIES = 1<<2,
|
||||||
DIR_NO_GITLINKS = 1<<3,
|
DIR_NO_GITLINKS = 1<<3,
|
||||||
DIR_COLLECT_IGNORED = 1<<4
|
DIR_COLLECT_IGNORED = 1<<4,
|
||||||
|
DIR_SHOW_IGNORED_TOO = 1<<5
|
||||||
} flags;
|
} flags;
|
||||||
struct dir_entry **entries;
|
struct dir_entry **entries;
|
||||||
struct dir_entry **ignored;
|
struct dir_entry **ignored;
|
||||||
|
@ -110,9 +111,11 @@ struct dir_struct {
|
||||||
*
|
*
|
||||||
* exclude_stack points to the top of the exclude_stack, and
|
* exclude_stack points to the top of the exclude_stack, and
|
||||||
* basebuf contains the full path to the current
|
* basebuf contains the full path to the current
|
||||||
* (sub)directory in the traversal.
|
* (sub)directory in the traversal. Exclude points to the
|
||||||
|
* matching exclude struct if the directory is excluded.
|
||||||
*/
|
*/
|
||||||
struct exclude_stack *exclude_stack;
|
struct exclude_stack *exclude_stack;
|
||||||
|
struct exclude *exclude;
|
||||||
char basebuf[PATH_MAX];
|
char basebuf[PATH_MAX];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -149,22 +152,10 @@ extern int match_pathname(const char *, int,
|
||||||
const char *, int,
|
const char *, int,
|
||||||
const char *, int, int, int);
|
const char *, int, int, int);
|
||||||
|
|
||||||
/*
|
extern struct exclude *last_exclude_matching(struct dir_struct *dir,
|
||||||
* The is_excluded() API is meant for callers that check each level of leading
|
const char *name, int *dtype);
|
||||||
* directory hierarchies with is_excluded() to avoid recursing into excluded
|
|
||||||
* directories. Callers that do not do so should use this API instead.
|
|
||||||
*/
|
|
||||||
struct path_exclude_check {
|
|
||||||
struct dir_struct *dir;
|
|
||||||
struct exclude *exclude;
|
|
||||||
struct strbuf path;
|
|
||||||
};
|
|
||||||
extern void path_exclude_check_init(struct path_exclude_check *, struct dir_struct *);
|
|
||||||
extern void path_exclude_check_clear(struct path_exclude_check *);
|
|
||||||
extern struct exclude *last_exclude_matching_path(struct path_exclude_check *, const char *,
|
|
||||||
int namelen, int *dtype);
|
|
||||||
extern int is_path_excluded(struct path_exclude_check *, const char *, int namelen, int *dtype);
|
|
||||||
|
|
||||||
|
extern int is_excluded(struct dir_struct *dir, const char *name, int *dtype);
|
||||||
|
|
||||||
extern struct exclude_list *add_exclude_list(struct dir_struct *dir,
|
extern struct exclude_list *add_exclude_list(struct dir_struct *dir,
|
||||||
int group_type, const char *src);
|
int group_type, const char *src);
|
||||||
|
|
|
@ -214,6 +214,55 @@ test_expect_success 'subdirectory ignore (l1)' '
|
||||||
test_cmp expect actual
|
test_cmp expect actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'show/hide empty ignored directory (setup)' '
|
||||||
|
rm top/l1/l2/l1 &&
|
||||||
|
rm top/l1/.gitignore
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'show empty ignored directory with --directory' '
|
||||||
|
(
|
||||||
|
cd top &&
|
||||||
|
git ls-files -o -i --exclude l1 --directory
|
||||||
|
) >actual &&
|
||||||
|
echo l1/ >expect &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'hide empty ignored directory with --no-empty-directory' '
|
||||||
|
(
|
||||||
|
cd top &&
|
||||||
|
git ls-files -o -i --exclude l1 --directory --no-empty-directory
|
||||||
|
) >actual &&
|
||||||
|
>expect &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'show/hide empty ignored sub-directory (setup)' '
|
||||||
|
> top/l1/tracked &&
|
||||||
|
(
|
||||||
|
cd top &&
|
||||||
|
git add -f l1/tracked
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'show empty ignored sub-directory with --directory' '
|
||||||
|
(
|
||||||
|
cd top &&
|
||||||
|
git ls-files -o -i --exclude l1 --directory
|
||||||
|
) >actual &&
|
||||||
|
echo l1/l2/ >expect &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'hide empty ignored sub-directory with --no-empty-directory' '
|
||||||
|
(
|
||||||
|
cd top &&
|
||||||
|
git ls-files -o -i --exclude l1 --directory --no-empty-directory
|
||||||
|
) >actual &&
|
||||||
|
>expect &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'pattern matches prefix completely' '
|
test_expect_success 'pattern matches prefix completely' '
|
||||||
: >expect &&
|
: >expect &&
|
||||||
git ls-files -i -o --exclude "/three/a.3[abc]" >actual &&
|
git ls-files -i -o --exclude "/three/a.3[abc]" >actual &&
|
||||||
|
|
|
@ -32,6 +32,25 @@ test_expect_success 'status untracked directory with --ignored -u' '
|
||||||
git status --porcelain --ignored -u >actual &&
|
git status --porcelain --ignored -u >actual &&
|
||||||
test_cmp expected actual
|
test_cmp expected actual
|
||||||
'
|
'
|
||||||
|
cat >expected <<\EOF
|
||||||
|
?? untracked/uncommitted
|
||||||
|
!! untracked/ignored
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_expect_success 'status prefixed untracked directory with --ignored' '
|
||||||
|
git status --porcelain --ignored untracked/ >actual &&
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
|
cat >expected <<\EOF
|
||||||
|
?? untracked/uncommitted
|
||||||
|
!! untracked/ignored
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_expect_success 'status prefixed untracked sub-directory with --ignored -u' '
|
||||||
|
git status --porcelain --ignored -u untracked/ >actual &&
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
cat >expected <<\EOF
|
cat >expected <<\EOF
|
||||||
?? .gitignore
|
?? .gitignore
|
||||||
|
@ -60,6 +79,31 @@ test_expect_success 'status ignored directory with --ignore -u' '
|
||||||
test_cmp expected actual
|
test_cmp expected actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
cat >expected <<\EOF
|
||||||
|
?? .gitignore
|
||||||
|
?? actual
|
||||||
|
?? expected
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_expect_success 'status empty untracked directory with --ignore' '
|
||||||
|
rm -rf ignored &&
|
||||||
|
mkdir untracked-ignored &&
|
||||||
|
mkdir untracked-ignored/test &&
|
||||||
|
git status --porcelain --ignored >actual &&
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
|
cat >expected <<\EOF
|
||||||
|
?? .gitignore
|
||||||
|
?? actual
|
||||||
|
?? expected
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_expect_success 'status empty untracked directory with --ignore -u' '
|
||||||
|
git status --porcelain --ignored -u >actual &&
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
cat >expected <<\EOF
|
cat >expected <<\EOF
|
||||||
?? .gitignore
|
?? .gitignore
|
||||||
?? actual
|
?? actual
|
||||||
|
@ -68,9 +112,6 @@ cat >expected <<\EOF
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
test_expect_success 'status untracked directory with ignored files with --ignore' '
|
test_expect_success 'status untracked directory with ignored files with --ignore' '
|
||||||
rm -rf ignored &&
|
|
||||||
mkdir untracked-ignored &&
|
|
||||||
mkdir untracked-ignored/test &&
|
|
||||||
: >untracked-ignored/ignored &&
|
: >untracked-ignored/ignored &&
|
||||||
: >untracked-ignored/test/ignored &&
|
: >untracked-ignored/test/ignored &&
|
||||||
git status --porcelain --ignored >actual &&
|
git status --porcelain --ignored >actual &&
|
||||||
|
@ -122,10 +163,34 @@ cat >expected <<\EOF
|
||||||
?? .gitignore
|
?? .gitignore
|
||||||
?? actual
|
?? actual
|
||||||
?? expected
|
?? expected
|
||||||
!! tracked/
|
EOF
|
||||||
|
|
||||||
|
test_expect_success 'status ignored tracked directory and ignored file with --ignore' '
|
||||||
|
echo "committed" >>.gitignore &&
|
||||||
|
git status --porcelain --ignored >actual &&
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
|
cat >expected <<\EOF
|
||||||
|
?? .gitignore
|
||||||
|
?? actual
|
||||||
|
?? expected
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_expect_success 'status ignored tracked directory and ignored file with --ignore -u' '
|
||||||
|
git status --porcelain --ignored -u >actual &&
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
|
cat >expected <<\EOF
|
||||||
|
?? .gitignore
|
||||||
|
?? actual
|
||||||
|
?? expected
|
||||||
|
!! tracked/uncommitted
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
test_expect_success 'status ignored tracked directory and uncommitted file with --ignore' '
|
test_expect_success 'status ignored tracked directory and uncommitted file with --ignore' '
|
||||||
|
echo "tracked" >.gitignore &&
|
||||||
: >tracked/uncommitted &&
|
: >tracked/uncommitted &&
|
||||||
git status --porcelain --ignored >actual &&
|
git status --porcelain --ignored >actual &&
|
||||||
test_cmp expected actual
|
test_cmp expected actual
|
||||||
|
@ -143,4 +208,58 @@ test_expect_success 'status ignored tracked directory and uncommitted file with
|
||||||
test_cmp expected actual
|
test_cmp expected actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
cat >expected <<\EOF
|
||||||
|
?? .gitignore
|
||||||
|
?? actual
|
||||||
|
?? expected
|
||||||
|
!! tracked/ignored/
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_expect_success 'status ignored tracked directory with uncommitted file in untracked subdir with --ignore' '
|
||||||
|
rm -rf tracked/uncommitted &&
|
||||||
|
mkdir tracked/ignored &&
|
||||||
|
: >tracked/ignored/uncommitted &&
|
||||||
|
git status --porcelain --ignored >actual &&
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
|
cat >expected <<\EOF
|
||||||
|
?? .gitignore
|
||||||
|
?? actual
|
||||||
|
?? expected
|
||||||
|
!! tracked/ignored/uncommitted
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_expect_success 'status ignored tracked directory with uncommitted file in untracked subdir with --ignore -u' '
|
||||||
|
git status --porcelain --ignored -u >actual &&
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
|
cat >expected <<\EOF
|
||||||
|
?? .gitignore
|
||||||
|
?? actual
|
||||||
|
?? expected
|
||||||
|
!! tracked/ignored/uncommitted
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_expect_success 'status ignored tracked directory with uncommitted file in tracked subdir with --ignore' '
|
||||||
|
: >tracked/ignored/committed &&
|
||||||
|
git add -f tracked/ignored/committed &&
|
||||||
|
git commit -m. &&
|
||||||
|
git status --porcelain --ignored >actual &&
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
|
cat >expected <<\EOF
|
||||||
|
?? .gitignore
|
||||||
|
?? actual
|
||||||
|
?? expected
|
||||||
|
!! tracked/ignored/uncommitted
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_expect_success 'status ignored tracked directory with uncommitted file in tracked subdir with --ignore -u' '
|
||||||
|
git status --porcelain --ignored -u >actual &&
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|
|
@ -298,6 +298,23 @@ test_expect_success 'git clean -d -x' '
|
||||||
|
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git clean -d -x with ignored tracked directory' '
|
||||||
|
|
||||||
|
mkdir -p build docs &&
|
||||||
|
touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
|
||||||
|
git clean -d -x -e src &&
|
||||||
|
test -f Makefile &&
|
||||||
|
test -f README &&
|
||||||
|
test -f src/part1.c &&
|
||||||
|
test -f src/part2.c &&
|
||||||
|
test ! -f a.out &&
|
||||||
|
test -f src/part3.c &&
|
||||||
|
test ! -d docs &&
|
||||||
|
test ! -f obj.o &&
|
||||||
|
test ! -d build
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'git clean -X' '
|
test_expect_success 'git clean -X' '
|
||||||
|
|
||||||
mkdir -p build docs &&
|
mkdir -p build docs &&
|
||||||
|
@ -332,6 +349,23 @@ test_expect_success 'git clean -d -X' '
|
||||||
|
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git clean -d -X with ignored tracked directory' '
|
||||||
|
|
||||||
|
mkdir -p build docs &&
|
||||||
|
touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
|
||||||
|
git clean -d -X -e src &&
|
||||||
|
test -f Makefile &&
|
||||||
|
test -f README &&
|
||||||
|
test -f src/part1.c &&
|
||||||
|
test -f src/part2.c &&
|
||||||
|
test -f a.out &&
|
||||||
|
test ! -f src/part3.c &&
|
||||||
|
test -f docs/manual.txt &&
|
||||||
|
test ! -f obj.o &&
|
||||||
|
test ! -d build
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'clean.requireForce defaults to true' '
|
test_expect_success 'clean.requireForce defaults to true' '
|
||||||
|
|
||||||
git config --unset clean.requireForce &&
|
git config --unset clean.requireForce &&
|
||||||
|
|
|
@ -1026,10 +1026,6 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
|
||||||
o->el = ⪙
|
o->el = ⪙
|
||||||
}
|
}
|
||||||
|
|
||||||
if (o->dir) {
|
|
||||||
o->path_exclude_check = xmalloc(sizeof(struct path_exclude_check));
|
|
||||||
path_exclude_check_init(o->path_exclude_check, o->dir);
|
|
||||||
}
|
|
||||||
memset(&o->result, 0, sizeof(o->result));
|
memset(&o->result, 0, sizeof(o->result));
|
||||||
o->result.initialized = 1;
|
o->result.initialized = 1;
|
||||||
o->result.timestamp.sec = o->src_index->timestamp.sec;
|
o->result.timestamp.sec = o->src_index->timestamp.sec;
|
||||||
|
@ -1155,10 +1151,6 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
|
||||||
|
|
||||||
done:
|
done:
|
||||||
clear_exclude_list(&el);
|
clear_exclude_list(&el);
|
||||||
if (o->path_exclude_check) {
|
|
||||||
path_exclude_check_clear(o->path_exclude_check);
|
|
||||||
free(o->path_exclude_check);
|
|
||||||
}
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
return_failed:
|
return_failed:
|
||||||
|
@ -1375,7 +1367,7 @@ static int check_ok_to_remove(const char *name, int len, int dtype,
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (o->dir &&
|
if (o->dir &&
|
||||||
is_path_excluded(o->path_exclude_check, name, -1, &dtype))
|
is_excluded(o->dir, name, &dtype))
|
||||||
/*
|
/*
|
||||||
* ce->name is explicitly excluded, so it is Ok to
|
* ce->name is explicitly excluded, so it is Ok to
|
||||||
* overwrite it.
|
* overwrite it.
|
||||||
|
|
|
@ -52,7 +52,6 @@ struct unpack_trees_options {
|
||||||
const char *prefix;
|
const char *prefix;
|
||||||
int cache_bottom;
|
int cache_bottom;
|
||||||
struct dir_struct *dir;
|
struct dir_struct *dir;
|
||||||
struct path_exclude_check *path_exclude_check;
|
|
||||||
struct pathspec *pathspec;
|
struct pathspec *pathspec;
|
||||||
merge_fn_t fn;
|
merge_fn_t fn;
|
||||||
const char *msgs[NB_UNPACK_TREES_ERROR_TYPES];
|
const char *msgs[NB_UNPACK_TREES_ERROR_TYPES];
|
||||||
|
|
24
wt-status.c
24
wt-status.c
|
@ -511,9 +511,12 @@ static void wt_status_collect_untracked(struct wt_status *s)
|
||||||
if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
|
if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
|
||||||
dir.flags |=
|
dir.flags |=
|
||||||
DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
|
DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
|
||||||
|
if (s->show_ignored_files)
|
||||||
|
dir.flags |= DIR_SHOW_IGNORED_TOO;
|
||||||
setup_standard_excludes(&dir);
|
setup_standard_excludes(&dir);
|
||||||
|
|
||||||
fill_directory(&dir, s->pathspec);
|
fill_directory(&dir, s->pathspec);
|
||||||
|
|
||||||
for (i = 0; i < dir.nr; i++) {
|
for (i = 0; i < dir.nr; i++) {
|
||||||
struct dir_entry *ent = dir.entries[i];
|
struct dir_entry *ent = dir.entries[i];
|
||||||
if (cache_name_is_other(ent->name, ent->len) &&
|
if (cache_name_is_other(ent->name, ent->len) &&
|
||||||
|
@ -522,22 +525,17 @@ static void wt_status_collect_untracked(struct wt_status *s)
|
||||||
free(ent);
|
free(ent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s->show_ignored_files) {
|
for (i = 0; i < dir.ignored_nr; i++) {
|
||||||
dir.nr = 0;
|
struct dir_entry *ent = dir.ignored[i];
|
||||||
dir.flags = DIR_SHOW_IGNORED;
|
if (cache_name_is_other(ent->name, ent->len) &&
|
||||||
if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
|
match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
|
||||||
dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
|
string_list_insert(&s->ignored, ent->name);
|
||||||
fill_directory(&dir, s->pathspec);
|
free(ent);
|
||||||
for (i = 0; i < dir.nr; i++) {
|
|
||||||
struct dir_entry *ent = dir.entries[i];
|
|
||||||
if (cache_name_is_other(ent->name, ent->len) &&
|
|
||||||
match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
|
|
||||||
string_list_insert(&s->ignored, ent->name);
|
|
||||||
free(ent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
free(dir.entries);
|
free(dir.entries);
|
||||||
|
free(dir.ignored);
|
||||||
|
clear_directory(&dir);
|
||||||
|
|
||||||
if (advice_status_u_option) {
|
if (advice_status_u_option) {
|
||||||
struct timeval t_end;
|
struct timeval t_end;
|
||||||
|
|
Загрузка…
Ссылка в новой задаче