summaryrefslogtreecommitdiff
path: root/dir.c
diff options
context:
space:
mode:
Diffstat (limited to 'dir.c')
-rw-r--r--dir.c712
1 files changed, 497 insertions, 215 deletions
diff --git a/dir.c b/dir.c
index 08281d2ef7..e883a91483 100644
--- a/dir.c
+++ b/dir.c
@@ -2,12 +2,15 @@
* 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;
@@ -34,56 +37,79 @@ int fnmatch_icase(const char *pattern, const char *string, int flags)
return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0));
}
-static int common_prefix(const char **pathspec)
+inline int git_fnmatch(const char *pattern, const char *string,
+ int flags, int prefix)
{
- const char *path, *slash, *next;
- 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 size_t common_prefix_len(const char **pathspec)
+{
+ const char *n, *first;
+ size_t max = 0;
+ int literal = limit_pathspec_to_literal();
if (!pathspec)
- return 0;
+ return max;
+
+ first = *pathspec;
+ while ((n = *pathspec++)) {
+ size_t i, len = 0;
+ for (i = 0; first == n || i < max; i++) {
+ char c = n[i];
+ if (!c || c != first[i] || (!literal && is_glob_special(c)))
+ break;
+ if (c == '/')
+ len = i + 1;
+ }
+ if (first == n || len < max) {
+ max = len;
+ if (!max)
+ break;
+ }
+ }
+ return max;
+}
- path = *pathspec;
- slash = strrchr(path, '/');
- if (!slash)
- return 0;
+/*
+ * Returns a copy of the longest leading path common among all
+ * pathspecs.
+ */
+char *common_prefix(const char **pathspec)
+{
+ unsigned long len = common_prefix_len(pathspec);
- /*
- * The first 'prefix' characters of 'path' are common leading
- * path components among the pathspecs we have seen so far,
- * including the trailing slash.
- */
- prefix = slash - path + 1;
- while ((next = *++pathspec) != NULL) {
- int len, last_matching_slash = -1;
- for (len = 0; len < prefix && next[len] == path[len]; len++)
- if (next[len] == '/')
- last_matching_slash = len;
- if (len == prefix)
- continue;
- if (last_matching_slash < 0)
- return 0;
- prefix = last_matching_slash + 1;
- }
- return prefix;
+ return len ? xmemdupz(*pathspec, len) : NULL;
}
int fill_directory(struct dir_struct *dir, const char **pathspec)
{
- const char *path;
- int len;
+ size_t len;
/*
* Calculate common prefix for the pathspec, and
* use that to optimize the directory walk
*/
- len = common_prefix(pathspec);
- path = "";
-
- if (len)
- path = xmemdupz(*pathspec, len);
+ len = common_prefix_len(pathspec);
/* Read the directory and prune it */
- read_directory(dir, path, len, pathspec);
+ read_directory(dir, pathspec ? *pathspec : "", len, pathspec);
return len;
}
@@ -117,6 +143,7 @@ int within_depth(const char *name, int namelen,
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)
@@ -126,7 +153,7 @@ static int match_one(const char *match, const char *name, int namelen)
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;
@@ -138,7 +165,7 @@ static int match_one(const char *match, const char *name, int namelen)
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;
@@ -148,14 +175,16 @@ static int match_one(const char *match, const char *name, int namelen)
}
}
-
/*
* 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;
@@ -230,7 +259,10 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix,
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;
@@ -288,50 +320,91 @@ int match_pathspec_depth(const struct pathspec *ps,
return retval;
}
+/*
+ * Return the length of the "simple" part of a path match limiter.
+ */
+static int simple_length(const char *match)
+{
+ int len = -1;
+
+ for (;;) {
+ unsigned char c = *match++;
+ len++;
+ if (c == '\0' || is_glob_special(c))
+ return len;
+ }
+}
+
static int no_wildcard(const char *string)
{
- return string[strcspn(string, "*?[{\\")] == '\0';
+ return string[simple_length(string)] == '\0';
+}
+
+void parse_exclude_pattern(const char **pattern,
+ int *patternlen,
+ int *flags,
+ int *nowildcardlen)
+{
+ const char *p = *pattern;
+ size_t i, len;
+
+ *flags = 0;
+ if (*p == '!') {
+ *flags |= EXC_FLAG_NEGATIVE;
+ p++;
+ }
+ len = strlen(p);
+ if (len && p[len - 1] == '/') {
+ len--;
+ *flags |= EXC_FLAG_MUSTBEDIR;
+ }
+ for (i = 0; i < len; i++) {
+ if (p[i] == '/')
+ break;
+ }
+ if (i == len)
+ *flags |= EXC_FLAG_NODIR;
+ *nowildcardlen = simple_length(p);
+ /*
+ * we should have excluded the trailing slash from 'p' too,
+ * but that's one more allocation. Instead just make sure
+ * nowildcardlen does not exceed real patternlen
+ */
+ if (*nowildcardlen > len)
+ *nowildcardlen = len;
+ if (*p == '*' && no_wildcard(p + 1))
+ *flags |= EXC_FLAG_ENDSWITH;
+ *pattern = p;
+ *patternlen = len;
}
void add_exclude(const char *string, const char *base,
- int baselen, struct exclude_list *which)
+ int baselen, struct exclude_list *el)
{
struct exclude *x;
- size_t len;
- int to_exclude = 1;
- int flags = 0;
+ int patternlen;
+ int flags;
+ int nowildcardlen;
- if (*string == '!') {
- to_exclude = 0;
- string++;
- }
- len = strlen(string);
- if (len && string[len - 1] == '/') {
+ parse_exclude_pattern(&string, &patternlen, &flags, &nowildcardlen);
+ if (flags & EXC_FLAG_MUSTBEDIR) {
char *s;
- x = xmalloc(sizeof(*x) + len);
+ x = xmalloc(sizeof(*x) + patternlen + 1);
s = (char *)(x+1);
- memcpy(s, string, len - 1);
- s[len - 1] = '\0';
- string = s;
+ memcpy(s, string, patternlen);
+ s[patternlen] = '\0';
x->pattern = s;
- flags = EXC_FLAG_MUSTBEDIR;
} else {
x = xmalloc(sizeof(*x));
x->pattern = string;
}
- x->to_exclude = to_exclude;
- x->patternlen = strlen(string);
+ x->patternlen = patternlen;
+ x->nowildcardlen = nowildcardlen;
x->base = base;
x->baselen = baselen;
x->flags = flags;
- if (!strchr(string, '/'))
- x->flags |= EXC_FLAG_NODIR;
- if (no_wildcard(string))
- x->flags |= EXC_FLAG_NOWILDCARD;
- if (*string == '*' && no_wildcard(string+1))
- x->flags |= EXC_FLAG_ENDSWITH;
- ALLOC_GROW(which->excludes, which->nr + 1, which->alloc);
- which->excludes[which->nr++] = x;
+ ALLOC_GROW(el->excludes, el->nr + 1, el->alloc);
+ el->excludes[el->nr++] = x;
}
static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
@@ -357,7 +430,11 @@ 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;
@@ -373,7 +450,7 @@ 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;
@@ -383,6 +460,8 @@ int add_excludes_from_file_to_list(const char *fname,
fd = open(fname, O_RDONLY);
if (fd < 0 || fstat(fd, &st) < 0) {
+ if (errno != ENOENT)
+ warn_on_inaccessible(fname);
if (0 <= fd)
close(fd);
if (!check_index ||
@@ -420,7 +499,7 @@ int add_excludes_from_file_to_list(const char *fname,
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);
}
entry = buf + i + 1;
}
@@ -435,6 +514,10 @@ void add_excludes_from_file(struct dir_struct *dir, const char *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 *el;
@@ -445,7 +528,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
(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. */
+ /* Pop the directories 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 &&
@@ -489,87 +572,260 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
dir->basebuf[baselen] = '\0';
}
-/* Scan the list and let the last match determine the fate.
- * Return 1 for exclude, 0 for include and -1 for undecided.
+int match_basename(const char *basename, int basenamelen,
+ const char *pattern, int prefix, int patternlen,
+ int flags)
+{
+ if (prefix == patternlen) {
+ if (!strcmp_icase(pattern, basename))
+ return 1;
+ } else if (flags & EXC_FLAG_ENDSWITH) {
+ if (patternlen - 1 <= basenamelen &&
+ !strcmp_icase(pattern + 1,
+ basename + basenamelen - patternlen + 1))
+ return 1;
+ } else {
+ if (fnmatch_icase(pattern, basename, 0) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+int match_pathname(const char *pathname, int pathlen,
+ const char *base, int baselen,
+ const char *pattern, int prefix, int patternlen,
+ int flags)
+{
+ const char *name;
+ int namelen;
+
+ /*
+ * match with FNM_PATHNAME; the pattern has base implicitly
+ * in front of it.
+ */
+ if (*pattern == '/') {
+ pattern++;
+ prefix--;
+ }
+
+ /*
+ * baselen does not count the trailing slash. base[] may or
+ * may not end with a trailing slash though.
+ */
+ if (pathlen < baselen + 1 ||
+ (baselen && pathname[baselen] != '/') ||
+ strncmp_icase(pathname, base, baselen))
+ return 0;
+
+ namelen = baselen ? pathlen - baselen - 1 : pathlen;
+ name = pathname + pathlen - namelen;
+
+ if (prefix) {
+ /*
+ * if the non-wildcard part is longer than the
+ * remaining pathname, surely it cannot match.
+ */
+ if (prefix > namelen)
+ return 0;
+
+ if (strncmp_icase(pattern, name, prefix))
+ return 0;
+ pattern += prefix;
+ name += prefix;
+ namelen -= prefix;
+ }
+
+ return wildmatch(pattern, name,
+ ignore_case ? FNM_CASEFOLD : 0) == 0;
+}
+
+/*
+ * 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) {
- for (i = el->nr - 1; 0 <= i; i--) {
- struct exclude *x = el->excludes[i];
- const char *exclude = x->pattern;
- int to_exclude = x->to_exclude;
-
- if (x->flags & EXC_FLAG_MUSTBEDIR) {
- if (*dtype == DT_UNKNOWN)
- *dtype = get_dtype(NULL, pathname, pathlen);
- if (*dtype != DT_DIR)
- continue;
- }
+ if (!el->nr)
+ return NULL; /* undefined */
- if (x->flags & EXC_FLAG_NODIR) {
- /* match basename */
- if (x->flags & EXC_FLAG_NOWILDCARD) {
- if (!strcmp_icase(exclude, basename))
- return to_exclude;
- } else if (x->flags & EXC_FLAG_ENDSWITH) {
- if (x->patternlen - 1 <= pathlen &&
- !strcmp_icase(exclude + 1, pathname + pathlen - x->patternlen + 1))
- return to_exclude;
- } else {
- if (fnmatch_icase(exclude, basename, 0) == 0)
- return to_exclude;
- }
- }
- else {
- /* match with FNM_PATHNAME:
- * exclude has base (baselen long) implicitly
- * in front of it.
- */
- int baselen = x->baselen;
- if (*exclude == '/')
- exclude++;
-
- if (pathlen < baselen ||
- (baselen && pathname[baselen-1] != '/') ||
- strncmp_icase(pathname, x->base, baselen))
- continue;
-
- if (x->flags & EXC_FLAG_NOWILDCARD) {
- if (!strcmp_icase(exclude, pathname + baselen))
- return to_exclude;
- } else {
- if (fnmatch_icase(exclude, pathname+baselen,
- FNM_PATHNAME) == 0)
- return to_exclude;
- }
- }
+ for (i = el->nr - 1; 0 <= i; i--) {
+ struct exclude *x = el->excludes[i];
+ const char *exclude = x->pattern;
+ int prefix = x->nowildcardlen;
+
+ if (x->flags & EXC_FLAG_MUSTBEDIR) {
+ if (*dtype == DT_UNKNOWN)
+ *dtype = get_dtype(NULL, pathname, pathlen);
+ if (*dtype != DT_DIR)
+ continue;
}
+
+ if (x->flags & EXC_FLAG_NODIR) {
+ if (match_basename(basename,
+ pathlen - (basename - pathname),
+ exclude, prefix, x->patternlen,
+ x->flags))
+ return x;
+ continue;
+ }
+
+ assert(x->baselen == 0 || x->base[x->baselen - 1] == '/');
+ if (match_pathname(pathname, pathlen,
+ x->base, x->baselen ? x->baselen - 1 : 0,
+ exclude, prefix, x->patternlen, x->flags))
+ return x;
}
+ return NULL; /* undecided */
+}
+
+/*
+ * 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)
+{
+ 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 */
}
-int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
+/*
+ * 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.
+ */
+static struct exclude *last_exclude_matching(struct dir_struct *dir,
+ const char *pathname,
+ int *dtype_p)
{
int pathlen = strlen(pathname);
int st;
+ struct exclude *exclude;
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;
+ exclude = last_exclude_matching_from_list(
+ pathname, pathlen, basename, dtype_p,
+ &dir->exclude_list[st]);
+ if (exclude)
+ return exclude;
+ }
+ return NULL;
+}
+
+/*
+ * 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.
+ */
+static 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;
+}
+
+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;
}
@@ -866,14 +1122,14 @@ enum path_treatment {
};
static enum path_treatment treat_one_path(struct dir_struct *dir,
- char *path, int *len,
+ struct strbuf *path,
const struct path_simplify *simplify,
int dtype, struct dirent *de)
{
- int exclude = excluded(dir, path, &dtype);
+ int exclude = is_excluded(dir, path->buf, &dtype);
if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
- && exclude_matches_pathspec(path, *len, simplify))
- dir_add_ignored(dir, path, *len);
+ && 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
@@ -883,7 +1139,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
return path_ignored;
if (dtype == DT_UNKNOWN)
- dtype = get_dtype(de, path, *len);
+ dtype = get_dtype(de, path->buf, path->len);
/*
* Do we want to see just the ignored files?
@@ -900,9 +1156,8 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
default:
return path_ignored;
case DT_DIR:
- memcpy(path + *len, "/", 2);
- (*len)++;
- switch (treat_directory(dir, path, *len, simplify)) {
+ strbuf_addch(path, '/');
+ switch (treat_directory(dir, path->buf, path->len, simplify)) {
case show_directory:
if (exclude != !!(dir->flags
& DIR_SHOW_IGNORED))
@@ -923,26 +1178,21 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
static enum path_treatment treat_path(struct dir_struct *dir,
struct dirent *de,
- char *path, int path_max,
+ struct strbuf *path,
int baselen,
- const struct path_simplify *simplify,
- int *len)
+ const struct path_simplify *simplify)
{
int dtype;
if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
return path_ignored;
- *len = strlen(de->d_name);
- /* Ignore overly long pathnames! */
- if (*len + baselen + 8 > path_max)
- return path_ignored;
- memcpy(path + baselen, de->d_name, *len + 1);
- *len += baselen;
- if (simplify_away(path, *len, simplify))
+ strbuf_setlen(path, baselen);
+ strbuf_addstr(path, de->d_name);
+ if (simplify_away(path->buf, path->len, simplify))
return path_ignored;
dtype = DTYPE(de);
- return treat_one_path(dir, path, len, simplify, dtype, de);
+ return treat_one_path(dir, path, simplify, dtype, de);
}
/*
@@ -959,36 +1209,37 @@ static int read_directory_recursive(struct dir_struct *dir,
int check_only,
const struct path_simplify *simplify)
{
- DIR *fdir = opendir(*base ? base : ".");
+ DIR *fdir;
int contents = 0;
+ struct dirent *de;
+ struct strbuf path = STRBUF_INIT;
- if (fdir) {
- struct dirent *de;
- char path[PATH_MAX + 1];
- memcpy(path, base, baselen);
-
- while ((de = readdir(fdir)) != NULL) {
- int len;
- switch (treat_path(dir, de, path, sizeof(path),
- baselen, simplify, &len)) {
- case path_recurse:
- contents += read_directory_recursive
- (dir, path, len, 0, simplify);
- continue;
- case path_ignored:
- continue;
- case path_handled:
- break;
- }
- contents++;
- if (check_only)
- goto exit_early;
- else
- dir_add_name(dir, path, len);
+ strbuf_add(&path, base, baselen);
+
+ fdir = opendir(path.len ? path.buf : ".");
+ if (!fdir)
+ 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:
+ continue;
+ case path_handled:
+ break;
}
-exit_early:
- closedir(fdir);
+ contents++;
+ if (check_only)
+ break;
+ dir_add_name(dir, path.buf, path.len);
}
+ closedir(fdir);
+ out:
+ strbuf_release(&path);
return contents;
}
@@ -1002,21 +1253,6 @@ static int cmp_name(const void *p1, const void *p2)
e2->name, e2->len);
}
-/*
- * Return the length of the "simple" part of a path match limiter.
- */
-static int simple_length(const char *match)
-{
- int len = -1;
-
- for (;;) {
- unsigned char c = *match++;
- len++;
- if (c == '\0' || is_glob_special(c))
- return len;
- }
-}
-
static struct path_simplify *create_simplify(const char **pathspec)
{
int nr, alloc = 0;
@@ -1051,8 +1287,8 @@ static int treat_leading_path(struct dir_struct *dir,
const char *path, int len,
const struct path_simplify *simplify)
{
- char pathbuf[PATH_MAX];
- int baselen, blen;
+ struct strbuf sb = STRBUF_INIT;
+ int baselen, rc = 0;
const char *cp;
while (len && path[len - 1] == '/')
@@ -1067,19 +1303,22 @@ static int treat_leading_path(struct dir_struct *dir,
baselen = len;
else
baselen = cp - path;
- memcpy(pathbuf, path, baselen);
- pathbuf[baselen] = '\0';
- if (!is_directory(pathbuf))
- return 0;
- if (simplify_away(pathbuf, baselen, simplify))
- return 0;
- blen = baselen;
- if (treat_one_path(dir, pathbuf, &blen, simplify,
+ strbuf_setlen(&sb, 0);
+ strbuf_add(&sb, path, baselen);
+ if (!is_directory(sb.buf))
+ break;
+ if (simplify_away(sb.buf, sb.len, simplify))
+ break;
+ if (treat_one_path(dir, &sb, simplify,
DT_DIR, NULL) == path_ignored)
- return 0; /* do not recurse into it */
- if (len <= baselen)
- return 1; /* finished checking */
+ break; /* do not recurse into it */
+ if (len <= baselen) {
+ rc = 1;
+ break; /* finished checking */
+ }
}
+ strbuf_release(&sb);
+ return rc;
}
int read_directory(struct dir_struct *dir, const char *path, int len, const char **pathspec)
@@ -1165,22 +1404,32 @@ int is_empty_dir(const char *path)
return ret;
}
-int remove_dir_recursively(struct strbuf *path, int flag)
+static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
{
DIR *dir;
struct dirent *e;
- int ret = 0, original_len = path->len, len;
+ int ret = 0, original_len = path->len, len, kept_down = 0;
int only_empty = (flag & REMOVE_DIR_EMPTY_ONLY);
+ int keep_toplevel = (flag & REMOVE_DIR_KEEP_TOPLEVEL);
unsigned char submodule_head[20];
if ((flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
- !resolve_gitlink_ref(path->buf, "HEAD", submodule_head))
+ !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) {
/* Do not descend and nuke a nested git work tree. */
+ if (kept_up)
+ *kept_up = 1;
return 0;
+ }
+ flag &= ~REMOVE_DIR_KEEP_TOPLEVEL;
dir = opendir(path->buf);
- if (!dir)
- return rmdir(path->buf);
+ if (!dir) {
+ /* an empty dir could be removed even if it is unreadble */
+ if (!keep_toplevel)
+ return rmdir(path->buf);
+ else
+ return -1;
+ }
if (path->buf[original_len - 1] != '/')
strbuf_addch(path, '/');
@@ -1195,7 +1444,7 @@ int remove_dir_recursively(struct strbuf *path, int flag)
if (lstat(path->buf, &st))
; /* fall thru */
else if (S_ISDIR(st.st_mode)) {
- if (!remove_dir_recursively(path, only_empty))
+ if (!remove_dir_recurse(path, flag, &kept_down))
continue; /* happy */
} else if (!only_empty && !unlink(path->buf))
continue; /* happy, too */
@@ -1207,20 +1456,36 @@ int remove_dir_recursively(struct strbuf *path, int flag)
closedir(dir);
strbuf_setlen(path, original_len);
- if (!ret)
+ if (!ret && !keep_toplevel && !kept_down)
ret = rmdir(path->buf);
+ else if (kept_up)
+ /*
+ * report the uplevel that it is not an error that we
+ * did not rmdir() our directory.
+ */
+ *kept_up = !ret;
return ret;
}
+int remove_dir_recursively(struct strbuf *path, int flag)
+{
+ return remove_dir_recurse(path, flag, NULL);
+}
+
void setup_standard_excludes(struct dir_struct *dir)
{
const char *path;
+ char *xdg_path;
dir->exclude_per_dir = ".gitignore";
path = git_path("info/exclude");
- if (!access(path, R_OK))
+ if (!excludes_file) {
+ home_config_paths(NULL, &xdg_path, "ignore");
+ excludes_file = xdg_path;
+ }
+ if (!access_or_warn(path, R_OK))
add_excludes_from_file(dir, path);
- if (excludes_file && !access(excludes_file, R_OK))
+ if (excludes_file && !access_or_warn(excludes_file, R_OK))
add_excludes_from_file(dir, excludes_file);
}
@@ -1274,9 +1539,18 @@ int init_pathspec(struct pathspec *pathspec, const char **paths)
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,
@@ -1290,3 +1564,11 @@ void free_pathspec(struct pathspec *pathspec)
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;
+}