* This handles recursive filename detection with exclude
* files, index knowledge etc..
*
+ * See Documentation/technical/api-directory-listing.txt
+ *
* Copyright (C) Linus Torvalds, 2005-2006
* Junio Hamano, 2005-2006
*/
#include "cache.h"
#include "dir.h"
#include "refs.h"
+#include "wildmatch.h"
struct path_simplify {
int len;
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);
static int get_dtype(struct dirent *de, const char *path, int len);
return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0));
}
+inline int git_fnmatch(const char *pattern, const char *string,
+ int flags, int prefix)
+{
+ int fnm_flags = 0;
+ if (flags & GFNM_PATHNAME)
+ fnm_flags |= FNM_PATHNAME;
+ if (prefix > 0) {
+ if (strncmp(pattern, string, prefix))
+ return FNM_NOMATCH;
+ pattern += prefix;
+ string += prefix;
+ }
+ if (flags & GFNM_ONESTAR) {
+ int pattern_len = strlen(++pattern);
+ int string_len = strlen(string);
+ return string_len < pattern_len ||
+ strcmp(pattern,
+ string + string_len - pattern_len);
+ }
+ return fnmatch(pattern, string, fnm_flags);
+}
+
static int fnmatch_icase_mem(const char *pattern, int patternlen,
const char *string, int stringlen,
int flags)
use_str = str_buf.buf;
}
- match_status = fnmatch_icase(use_pat, use_str, flags);
+ if (ignore_case)
+ flags |= WM_CASEFOLD;
+ match_status = wildmatch(use_pat, use_str, flags, NULL);
strbuf_release(&pat_buf);
strbuf_release(&str_buf);
{
const char *n, *first;
size_t max = 0;
+ int literal = limit_pathspec_to_literal();
if (!pathspec)
return max;
size_t i, len = 0;
for (i = 0; first == n || i < max; i++) {
char c = n[i];
- if (!c || c != first[i] || is_glob_special(c))
+ if (!c || c != first[i] || (!literal && is_glob_special(c)))
break;
if (c == '/')
len = i + 1;
static int match_one(const char *match, const char *name, int namelen)
{
int matchlen;
+ int literal = limit_pathspec_to_literal();
/* If the match was just the prefix, we matched */
if (!*match)
for (;;) {
unsigned char c1 = tolower(*match);
unsigned char c2 = tolower(*name);
- if (c1 == '\0' || is_glob_special(c1))
+ if (c1 == '\0' || (!literal && is_glob_special(c1)))
break;
if (c1 != c2)
return 0;
for (;;) {
unsigned char c1 = *match;
unsigned char c2 = *name;
- if (c1 == '\0' || is_glob_special(c1))
+ if (c1 == '\0' || (!literal && is_glob_special(c1)))
break;
if (c1 != c2)
return 0;
}
}
-
/*
* If we don't match the matchstring exactly,
* we need to match by fnmatch
*/
matchlen = strlen(match);
- if (strncmp_icase(match, name, matchlen))
+ if (strncmp_icase(match, name, matchlen)) {
+ if (literal)
+ return 0;
return !fnmatch_icase(match, name, 0) ? MATCHED_FNMATCH : 0;
+ }
if (namelen == matchlen)
return MATCHED_EXACTLY;
}
/*
- * Given a name and a list of pathspecs, see if the name matches
- * any of the pathspecs. The caller is also interested in seeing
- * all pathspec matches some names it calls this function with
- * (otherwise the user could have mistyped the unmatched pathspec),
- * and a mark is left in seen[] array for pathspec element that
- * actually matched anything.
+ * Given a name and a list of pathspecs, returns the nature of the
+ * closest (i.e. most specific) match of the name to any of the
+ * pathspecs.
+ *
+ * The caller typically calls this multiple times with the same
+ * pathspec and seen[] array but with different name/namelen
+ * (e.g. entries from the index) and is interested in seeing if and
+ * how each pathspec matches all the names it calls this function
+ * with. A mark is left in the seen[] array for each pathspec element
+ * indicating the closest type of match that element achieved, so if
+ * seen[n] remains zero after multiple invocations, that means the nth
+ * pathspec did not match any names, which could indicate that the
+ * user mistyped the nth pathspec.
*/
int match_pathspec(const char **pathspec, const char *name, int namelen,
int prefix, char *seen)
return MATCHED_RECURSIVELY;
}
- if (item->use_wildcard && !fnmatch(match, name, 0))
+ if (item->nowildcard_len < item->len &&
+ !git_fnmatch(match, name,
+ item->flags & PATHSPEC_ONESTAR ? GFNM_ONESTAR : 0,
+ item->nowildcard_len - prefix))
return MATCHED_FNMATCH;
return 0;
}
/*
- * Given a name and a list of pathspecs, see if the name matches
- * any of the pathspecs. The caller is also interested in seeing
- * all pathspec matches some names it calls this function with
- * (otherwise the user could have mistyped the unmatched pathspec),
- * and a mark is left in seen[] array for pathspec element that
- * actually matched anything.
+ * Given a name and a list of pathspecs, returns the nature of the
+ * closest (i.e. most specific) match of the name to any of the
+ * pathspecs.
+ *
+ * The caller typically calls this multiple times with the same
+ * pathspec and seen[] array but with different name/namelen
+ * (e.g. entries from the index) and is interested in seeing if and
+ * how each pathspec matches all the names it calls this function
+ * with. A mark is left in the seen[] array for each pathspec element
+ * indicating the closest type of match that element achieved, so if
+ * seen[n] remains zero after multiple invocations, that means the nth
+ * pathspec did not match any names, which could indicate that the
+ * user mistyped the nth pathspec.
*/
int match_pathspec_depth(const struct pathspec *ps,
const char *name, int namelen,
}
void add_exclude(const char *string, const char *base,
- int baselen, struct exclude_list *which)
+ int baselen, struct exclude_list *el, int srcpos)
{
struct exclude *x;
int patternlen;
x->base = base;
x->baselen = baselen;
x->flags = flags;
- ALLOC_GROW(which->excludes, which->nr + 1, which->alloc);
- which->excludes[which->nr++] = x;
+ x->srcpos = srcpos;
+ ALLOC_GROW(el->excludes, el->nr + 1, el->alloc);
+ el->excludes[el->nr++] = x;
+ x->el = el;
}
static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
return data;
}
-void free_excludes(struct exclude_list *el)
+/*
+ * Frees memory within el which was allocated for exclude patterns and
+ * the file buffer. Does not free el itself.
+ */
+void clear_exclude_list(struct exclude_list *el)
{
int i;
for (i = 0; i < el->nr; i++)
free(el->excludes[i]);
free(el->excludes);
+ free(el->filebuf);
el->nr = 0;
el->excludes = NULL;
+ el->filebuf = NULL;
}
int add_excludes_from_file_to_list(const char *fname,
const char *base,
int baselen,
- char **buf_p,
- struct exclude_list *which,
+ struct exclude_list *el,
int check_index)
{
struct stat st;
- int fd, i;
+ int fd, i, lineno = 1;
size_t size = 0;
char *buf, *entry;
close(fd);
}
- if (buf_p)
- *buf_p = buf;
+ el->filebuf = buf;
entry = buf;
for (i = 0; i < size; i++) {
if (buf[i] == '\n') {
if (entry != buf + i && entry[0] != '#') {
buf[i - (i && buf[i-1] == '\r')] = 0;
- add_exclude(entry, base, baselen, which);
+ add_exclude(entry, base, baselen, el, lineno);
}
+ lineno++;
entry = buf + i + 1;
}
}
return 0;
}
-void add_excludes_from_file(struct dir_struct *dir, const char *fname)
+struct exclude_list *add_exclude_list(struct dir_struct *dir,
+ int group_type, const char *src)
{
- if (add_excludes_from_file_to_list(fname, "", 0, NULL,
- &dir->exclude_list[EXC_FILE], 0) < 0)
- die("cannot use %s as an exclude file", fname);
+ struct exclude_list *el;
+ struct exclude_list_group *group;
+
+ group = &dir->exclude_list_group[group_type];
+ ALLOC_GROW(group->el, group->nr + 1, group->alloc);
+ el = &group->el[group->nr++];
+ memset(el, 0, sizeof(*el));
+ el->src = src;
+ return el;
}
-static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
+/*
+ * Used to set up core.excludesfile and .git/info/exclude lists.
+ */
+void add_excludes_from_file(struct dir_struct *dir, const char *fname)
{
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 */
-
- /* Pop the ones that are not the prefix of the path being checked. */
- el = &dir->exclude_list[EXC_DIRS];
- while ((stk = dir->exclude_stack) != NULL) {
- if (stk->baselen <= baselen &&
- !strncmp(dir->basebuf, base, stk->baselen))
- break;
- dir->exclude_stack = stk->prev;
- while (stk->exclude_ix < el->nr)
- free(el->excludes[--el->nr]);
- free(stk->filebuf);
- free(stk);
- }
-
- /* 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 = el->nr;
- memcpy(dir->basebuf + current, base + current,
- stk->baselen - current);
- strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);
- add_excludes_from_file_to_list(dir->basebuf,
- dir->basebuf, stk->baselen,
- &stk->filebuf, el, 1);
- dir->exclude_stack = stk;
- current = stk->baselen;
- }
- dir->basebuf[baselen] = '\0';
+ el = add_exclude_list(dir, EXC_FILE, fname);
+ if (add_excludes_from_file_to_list(fname, "", 0, el, 0) < 0)
+ die("cannot use %s as an exclude file", fname);
}
int match_basename(const char *basename, int basenamelen,
if (strncmp_icase(pattern, name, prefix))
return 0;
pattern += prefix;
+ patternlen -= prefix;
name += prefix;
namelen -= prefix;
+
+ /*
+ * If the whole pattern did not have a wildcard,
+ * then our prefix match is all we need; we
+ * do not need to call fnmatch at all.
+ */
+ if (!patternlen && !namelen)
+ return 1;
}
- return fnmatch_icase(pattern, name, FNM_PATHNAME) == 0;
+ return fnmatch_icase_mem(pattern, patternlen,
+ name, namelen,
+ WM_PATHNAME) == 0;
}
-/* Scan the list and let the last match determine the fate.
- * Return 1 for exclude, 0 for include and -1 for undecided.
+/*
+ * Scan the given exclude list in reverse to see whether pathname
+ * should be ignored. The first match (i.e. the last on the list), if
+ * any, determines the fate. Returns the exclude_list element which
+ * matched, or NULL for undecided.
*/
-int excluded_from_list(const char *pathname,
- int pathlen, const char *basename, int *dtype,
- struct exclude_list *el)
+static struct exclude *last_exclude_matching_from_list(const char *pathname,
+ int pathlen,
+ const char *basename,
+ int *dtype,
+ struct exclude_list *el)
{
int i;
if (!el->nr)
- return -1; /* undefined */
+ return NULL; /* undefined */
for (i = el->nr - 1; 0 <= i; i--) {
struct exclude *x = el->excludes[i];
const char *exclude = x->pattern;
- int to_exclude = x->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
int prefix = x->nowildcardlen;
if (x->flags & EXC_FLAG_MUSTBEDIR) {
pathlen - (basename - pathname),
exclude, prefix, x->patternlen,
x->flags))
- return to_exclude;
+ return x;
continue;
}
if (match_pathname(pathname, pathlen,
x->base, x->baselen ? x->baselen - 1 : 0,
exclude, prefix, x->patternlen, x->flags))
- return to_exclude;
- }
- return -1; /* undecided */
-}
-
-static int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
-{
- int pathlen = strlen(pathname);
- int st;
- const char *basename = strrchr(pathname, '/');
- basename = (basename) ? basename+1 : pathname;
-
- prep_exclude(dir, pathname, basename-pathname);
- for (st = EXC_CMDL; st <= EXC_FILE; st++) {
- switch (excluded_from_list(pathname, pathlen, basename,
- dtype_p, &dir->exclude_list[st])) {
- case 0:
- return 0;
- case 1:
- return 1;
- }
+ return x;
}
- return 0;
+ return NULL; /* undecided */
}
-void path_exclude_check_init(struct path_exclude_check *check,
- struct dir_struct *dir)
+/*
+ * Scan the list and let the last match determine the fate.
+ * Return 1 for exclude, 0 for include and -1 for undecided.
+ */
+int is_excluded_from_list(const char *pathname,
+ int pathlen, const char *basename, int *dtype,
+ struct exclude_list *el)
{
- check->dir = dir;
- strbuf_init(&check->path, 256);
+ struct exclude *exclude;
+ exclude = last_exclude_matching_from_list(pathname, pathlen, basename, dtype, el);
+ if (exclude)
+ return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
+ return -1; /* undecided */
}
-void path_exclude_check_clear(struct path_exclude_check *check)
+static struct exclude *last_exclude_matching_from_lists(struct dir_struct *dir,
+ const char *pathname, int pathlen, const char *basename,
+ int *dtype_p)
{
- strbuf_release(&check->path);
+ int i, j;
+ struct exclude_list_group *group;
+ struct exclude *exclude;
+ for (i = EXC_CMDL; i <= EXC_FILE; i++) {
+ group = &dir->exclude_list_group[i];
+ for (j = group->nr - 1; j >= 0; j--) {
+ exclude = last_exclude_matching_from_list(
+ pathname, pathlen, basename, dtype_p,
+ &group->el[j]);
+ if (exclude)
+ return exclude;
+ }
+ }
+ return NULL;
}
/*
- * 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.
- *
- * 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.
+ * Loads the per-directory exclude list for the substring of base
+ * which has a char length of baselen.
*/
-int path_excluded(struct path_exclude_check *check,
- const char *name, int namelen, int *dtype)
+static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
{
- int i;
- struct strbuf *path = &check->path;
+ struct exclude_list_group *group;
+ struct exclude_list *el;
+ struct exclude_stack *stk = NULL;
+ int current;
- /*
- * we allow the caller to pass namelen as an optimization; it
- * must match the length of the name, as we eventually call
- * excluded() on the whole name string.
- */
- if (namelen < 0)
- namelen = strlen(name);
+ group = &dir->exclude_list_group[EXC_DIRS];
- if (path->len &&
- path->len <= namelen &&
- !memcmp(name, path->buf, path->len) &&
- (!name[path->len] || name[path->len] == '/'))
- return 1;
+ /* 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--;
+ }
- strbuf_setlen(path, 0);
- for (i = 0; name[i]; i++) {
- int ch = name[i];
+ /* 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 (ch == '/') {
+ 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;
- if (excluded(check->dir, path->buf, &dt))
- return 1;
+ 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;
+ }
}
- strbuf_addch(path, ch);
+
+ /* 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';
+}
- /* An entry in the index; cannot be a directory with subentries */
- strbuf_setlen(path, 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 excluded(check->dir, name, dtype);
+ return last_exclude_matching_from_lists(dir, pathname, pathlen,
+ basename, dtype_p);
+}
+
+/*
+ * Loads the exclude lists for the directory containing pathname, then
+ * scans all exclude lists to determine whether pathname is excluded.
+ * Returns 1 if true, otherwise 0.
+ */
+int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
+{
+ struct exclude *exclude =
+ last_exclude_matching(dir, pathname, dtype_p);
+ if (exclude)
+ return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
+ return 0;
}
static struct dir_entry *dir_entry_new(const char *pathname, int len)
*
* (a) if "show_other_directories" is true, we show it as
* just a directory, unless "hide_empty_directories" is
- * also true and the directory is empty, in which case
- * we just ignore it entirely.
+ * also true, in which case we need to check if it contains any
+ * untracked and / or ignored files.
* (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
* as a directory.
* (c) otherwise, we recurse into it.
*/
-enum directory_treatment {
- show_directory,
- ignore_directory,
- recurse_into_directory
-};
-
-static enum directory_treatment treat_directory(struct dir_struct *dir,
- const char *dirname, int len,
+static enum path_treatment treat_directory(struct dir_struct *dir,
+ const char *dirname, int len, int exclude,
const struct path_simplify *simplify)
{
/* The "len-1" is to strip the final '/' */
switch (directory_exists_in_index(dirname, len-1)) {
case index_directory:
- return recurse_into_directory;
+ return path_recurse;
case index_gitdir:
if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
- return ignore_directory;
- return show_directory;
+ return path_none;
+ return path_untracked;
case index_nonexistent:
if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
if (!(dir->flags & DIR_NO_GITLINKS)) {
unsigned char sha1[20];
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 */
+
if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
- return show_directory;
- if (!read_directory_recursive(dir, dirname, len, 1, simplify))
- return ignore_directory;
- return show_directory;
+ return exclude ? path_excluded : path_untracked;
+
+ return read_directory_recursive(dir, dirname, len, 1, simplify);
}
/*
return dtype;
}
-enum path_treatment {
- path_ignored,
- path_handled,
- path_recurse
-};
-
static enum path_treatment treat_one_path(struct dir_struct *dir,
struct strbuf *path,
const struct path_simplify *simplify,
int dtype, struct dirent *de)
{
- int exclude = excluded(dir, path->buf, &dtype);
- if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
- && exclude_matches_pathspec(path->buf, path->len, simplify))
- dir_add_ignored(dir, path->buf, path->len);
-
- /*
- * Excluded? If we don't explicitly want to show
- * ignored files, ignore it
- */
- if (exclude && !(dir->flags & DIR_SHOW_IGNORED))
- return path_ignored;
-
+ int exclude;
if (dtype == DT_UNKNOWN)
dtype = get_dtype(de, 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);
+
/*
- * Do we want to see just the ignored files?
- * We still need to recurse into directories,
- * even if we don't ignore them, since the
- * directory may contain files that we do..
+ * Excluded? If we don't explicitly want to show
+ * ignored files, ignore it
*/
- if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) {
- if (dtype != DT_DIR)
- return path_ignored;
- }
+ if (exclude && !(dir->flags & (DIR_SHOW_IGNORED|DIR_SHOW_IGNORED_TOO)))
+ return path_excluded;
switch (dtype) {
default:
- return path_ignored;
+ return path_none;
case DT_DIR:
strbuf_addch(path, '/');
- switch (treat_directory(dir, path->buf, path->len, simplify)) {
- case show_directory:
- if (exclude != !!(dir->flags
- & DIR_SHOW_IGNORED))
- return path_ignored;
- break;
- case recurse_into_directory:
- return path_recurse;
- case ignore_directory:
- return path_ignored;
- }
- break;
+ return treat_directory(dir, path->buf, path->len, exclude,
+ simplify);
case DT_REG:
case DT_LNK:
- break;
+ return exclude ? path_excluded : path_untracked;
}
- return path_handled;
}
static enum path_treatment treat_path(struct dir_struct *dir,
int dtype;
if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
- return path_ignored;
+ return path_none;
strbuf_setlen(path, baselen);
strbuf_addstr(path, de->d_name);
if (simplify_away(path->buf, path->len, simplify))
- return path_ignored;
+ return path_none;
dtype = DTYPE(de);
return treat_one_path(dir, path, simplify, dtype, de);
*
* Also, we ignore the name ".git" (even if it is not a directory).
* 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,
int check_only,
const struct path_simplify *simplify)
{
DIR *fdir;
- int contents = 0;
+ enum path_treatment state, subdir_state, dir_state = path_none;
struct dirent *de;
struct strbuf path = STRBUF_INIT;
goto out;
while ((de = readdir(fdir)) != NULL) {
- switch (treat_path(dir, de, &path, baselen, simplify)) {
- case path_recurse:
- contents += read_directory_recursive(dir, path.buf,
- path.len, 0,
- simplify);
- continue;
- case path_ignored:
+ /* check how the file or directory should be treated */
+ state = treat_path(dir, de, &path, baselen, simplify);
+ if (state > dir_state)
+ dir_state = state;
+
+ /* 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;
- case path_handled:
- break;
}
- contents++;
- if (check_only)
+
+ /* 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;
- dir_add_name(dir, path.buf, path.len);
+
+ case path_untracked:
+ if (!(dir->flags & DIR_SHOW_IGNORED))
+ dir_add_name(dir, path.buf, path.len);
+ break;
+
+ default:
+ break;
+ }
}
closedir(fdir);
out:
strbuf_release(&path);
- return contents;
+ return dir_state;
}
static int cmp_name(const void *p1, const void *p2)
struct strbuf sb = STRBUF_INIT;
int baselen, rc = 0;
const char *cp;
+ int old_flags = dir->flags;
while (len && path[len - 1] == '/')
len--;
if (!len)
return 1;
baselen = 0;
+ dir->flags &= ~DIR_SHOW_OTHER_DIRECTORIES;
while (1) {
cp = path + baselen + !!baselen;
cp = memchr(cp, '/', path + len - cp);
if (simplify_away(sb.buf, sb.len, simplify))
break;
if (treat_one_path(dir, &sb, simplify,
- DT_DIR, NULL) == path_ignored)
+ DT_DIR, NULL) == path_none)
break; /* do not recurse into it */
if (len <= baselen) {
rc = 1;
}
}
strbuf_release(&sb);
+ dir->flags = old_flags;
return rc;
}
{
char *slash;
- if (unlink(name) && errno != ENOENT)
+ if (unlink(name) && errno != ENOENT && errno != ENOTDIR)
return -1;
slash = strrchr(name, '/');
item->match = path;
item->len = strlen(path);
- item->use_wildcard = !no_wildcard(path);
- if (item->use_wildcard)
- pathspec->has_wildcard = 1;
+ item->flags = 0;
+ if (limit_pathspec_to_literal()) {
+ item->nowildcard_len = item->len;
+ } else {
+ item->nowildcard_len = simple_length(path);
+ if (item->nowildcard_len < item->len) {
+ pathspec->has_wildcard = 1;
+ if (path[item->nowildcard_len] == '*' &&
+ no_wildcard(path + item->nowildcard_len + 1))
+ item->flags |= PATHSPEC_ONESTAR;
+ }
+ }
}
qsort(pathspec->items, pathspec->nr,
free(pathspec->items);
pathspec->items = NULL;
}
+
+int limit_pathspec_to_literal(void)
+{
+ static int flag = -1;
+ if (flag < 0)
+ flag = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0);
+ return flag;
+}
+
+/*
+ * Frees memory within dir which was allocated for exclude lists and
+ * the exclude_stack. Does not free dir itself.
+ */
+void clear_directory(struct dir_struct *dir)
+{
+ int i, j;
+ struct exclude_list_group *group;
+ struct exclude_list *el;
+ struct exclude_stack *stk;
+
+ for (i = EXC_CMDL; i <= EXC_FILE; i++) {
+ group = &dir->exclude_list_group[i];
+ for (j = 0; j < group->nr; j++) {
+ el = &group->el[j];
+ if (i == EXC_DIRS)
+ free((char *)el->src);
+ clear_exclude_list(el);
+ }
+ free(group->el);
+ }
+
+ stk = dir->exclude_stack;
+ while (stk) {
+ struct exclude_stack *prev = stk->prev;
+ free(stk);
+ stk = prev;
+ }
+}