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:
Junio C Hamano 2013-04-23 11:21:23 -07:00
Родитель 9e94f9ba9e 0aaf62b6e0
Коммит 7093d2c0dd
12 изменённых файлов: 469 добавлений и 378 удалений

Просмотреть файл

@ -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
Просмотреть файл

@ -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
Просмотреть файл

@ -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 = &el; o->el = &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];

Просмотреть файл

@ -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;