summaryrefslogtreecommitdiff
path: root/dir.c
diff options
context:
space:
mode:
Diffstat (limited to 'dir.c')
-rw-r--r--dir.c619
1 files changed, 460 insertions, 159 deletions
diff --git a/dir.c b/dir.c
index cb83332a26..2c02b312b7 100644
--- a/dir.c
+++ b/dir.c
@@ -18,59 +18,90 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, in
int check_only, const struct path_simplify *simplify);
static int get_dtype(struct dirent *de, const char *path, int len);
-static int common_prefix(const char **pathspec)
+/* helper string functions with support for the ignore_case flag */
+int strcmp_icase(const char *a, const char *b)
{
- const char *path, *slash, *next;
- int prefix;
+ return ignore_case ? strcasecmp(a, b) : strcmp(a, b);
+}
- if (!pathspec)
- return 0;
+int strncmp_icase(const char *a, const char *b, size_t count)
+{
+ return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);
+}
- path = *pathspec;
- slash = strrchr(path, '/');
- if (!slash)
- return 0;
+int fnmatch_icase(const char *pattern, const char *string, int flags)
+{
+ return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0));
+}
- prefix = slash - path + 1;
- while ((next = *++pathspec) != NULL) {
- int len = strlen(next);
- if (len >= prefix && !memcmp(path, next, prefix))
- continue;
- len = prefix - 1;
- for (;;) {
- if (!len)
- return 0;
- if (next[--len] != '/')
- continue;
- if (memcmp(path, next, len+1))
- continue;
- prefix = len + 1;
- break;
+static size_t common_prefix_len(const char **pathspec)
+{
+ const char *n, *first;
+ size_t max = 0;
+
+ if (!pathspec)
+ 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] || is_glob_special(c))
+ break;
+ if (c == '/')
+ len = i + 1;
+ }
+ if (first == n || len < max) {
+ max = len;
+ if (!max)
+ break;
}
}
- return prefix;
+ return max;
+}
+
+/*
+ * 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);
+
+ 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;
}
+int within_depth(const char *name, int namelen,
+ int depth, int max_depth)
+{
+ const char *cp = name, *cpe = name + namelen;
+
+ while (cp < cpe) {
+ if (*cp++ != '/')
+ continue;
+ depth++;
+ if (depth > max_depth)
+ return 0;
+ }
+ return 1;
+}
+
/*
* Does 'match' match the given name?
* A match is found if
@@ -91,16 +122,30 @@ static int match_one(const char *match, const char *name, int namelen)
if (!*match)
return MATCHED_RECURSIVELY;
- for (;;) {
- unsigned char c1 = *match;
- unsigned char c2 = *name;
- if (c1 == '\0' || is_glob_special(c1))
- break;
- if (c1 != c2)
- return 0;
- match++;
- name++;
- namelen--;
+ if (ignore_case) {
+ for (;;) {
+ unsigned char c1 = tolower(*match);
+ unsigned char c2 = tolower(*name);
+ if (c1 == '\0' || is_glob_special(c1))
+ break;
+ if (c1 != c2)
+ return 0;
+ match++;
+ name++;
+ namelen--;
+ }
+ } else {
+ for (;;) {
+ unsigned char c1 = *match;
+ unsigned char c2 = *name;
+ if (c1 == '\0' || is_glob_special(c1))
+ break;
+ if (c1 != c2)
+ return 0;
+ match++;
+ name++;
+ namelen--;
+ }
}
@@ -109,8 +154,8 @@ static int match_one(const char *match, const char *name, int namelen)
* we need to match by fnmatch
*/
matchlen = strlen(match);
- if (strncmp(match, name, matchlen))
- return !fnmatch(match, name, 0) ? MATCHED_FNMATCH : 0;
+ if (strncmp_icase(match, name, matchlen))
+ return !fnmatch_icase(match, name, 0) ? MATCHED_FNMATCH : 0;
if (namelen == matchlen)
return MATCHED_EXACTLY;
@@ -154,6 +199,95 @@ int match_pathspec(const char **pathspec, const char *name, int namelen,
return retval;
}
+/*
+ * Does 'match' match the given name?
+ * A match is found if
+ *
+ * (1) the 'match' string is leading directory of 'name', or
+ * (2) the 'match' string is a wildcard and matches 'name', or
+ * (3) the 'match' string is exactly the same as 'name'.
+ *
+ * and the return value tells which case it was.
+ *
+ * It returns 0 when there is no match.
+ */
+static int match_pathspec_item(const struct pathspec_item *item, int prefix,
+ const char *name, int namelen)
+{
+ /* name/namelen has prefix cut off by caller */
+ const char *match = item->match + prefix;
+ int matchlen = item->len - prefix;
+
+ /* If the match was just the prefix, we matched */
+ if (!*match)
+ return MATCHED_RECURSIVELY;
+
+ if (matchlen <= namelen && !strncmp(match, name, matchlen)) {
+ if (matchlen == namelen)
+ return MATCHED_EXACTLY;
+
+ if (match[matchlen-1] == '/' || name[matchlen] == '/')
+ return MATCHED_RECURSIVELY;
+ }
+
+ if (item->use_wildcard && !fnmatch(match, name, 0))
+ 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.
+ */
+int match_pathspec_depth(const struct pathspec *ps,
+ const char *name, int namelen,
+ int prefix, char *seen)
+{
+ int i, retval = 0;
+
+ if (!ps->nr) {
+ if (!ps->recursive || ps->max_depth == -1)
+ return MATCHED_RECURSIVELY;
+
+ if (within_depth(name, namelen, 0, ps->max_depth))
+ return MATCHED_EXACTLY;
+ else
+ return 0;
+ }
+
+ name += prefix;
+ namelen -= prefix;
+
+ for (i = ps->nr - 1; i >= 0; i--) {
+ int how;
+ if (seen && seen[i] == MATCHED_EXACTLY)
+ continue;
+ how = match_pathspec_item(ps->items+i, prefix, name, namelen);
+ if (ps->recursive && ps->max_depth != -1 &&
+ how && how != MATCHED_FNMATCH) {
+ int len = ps->items[i].len;
+ if (name[len] == '/')
+ len++;
+ if (within_depth(name+len, namelen-len, 0, ps->max_depth))
+ how = MATCHED_EXACTLY;
+ else
+ how = 0;
+ }
+ if (how) {
+ if (retval < how)
+ retval = how;
+ if (seen && seen[i] < how)
+ seen[i] = how;
+ }
+ }
+ return retval;
+}
+
static int no_wildcard(const char *string)
{
return string[strcspn(string, "*?[{\\")] == '\0';
@@ -223,6 +357,18 @@ static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
return data;
}
+void free_excludes(struct exclude_list *el)
+{
+ int i;
+
+ for (i = 0; i < el->nr; i++)
+ free(el->excludes[i]);
+ free(el->excludes);
+
+ el->nr = 0;
+ el->excludes = NULL;
+}
+
int add_excludes_from_file_to_list(const char *fname,
const char *base,
int baselen,
@@ -232,7 +378,7 @@ int add_excludes_from_file_to_list(const char *fname,
{
struct stat st;
int fd, i;
- size_t size;
+ size_t size = 0;
char *buf, *entry;
fd = open(fname, O_RDONLY);
@@ -359,12 +505,6 @@ int excluded_from_list(const char *pathname,
int to_exclude = x->to_exclude;
if (x->flags & EXC_FLAG_MUSTBEDIR) {
- if (!dtype) {
- if (!prefixcmp(pathname, exclude))
- return to_exclude;
- else
- continue;
- }
if (*dtype == DT_UNKNOWN)
*dtype = get_dtype(NULL, pathname, pathlen);
if (*dtype != DT_DIR)
@@ -374,14 +514,14 @@ int excluded_from_list(const char *pathname,
if (x->flags & EXC_FLAG_NODIR) {
/* match basename */
if (x->flags & EXC_FLAG_NOWILDCARD) {
- if (!strcmp(exclude, basename))
+ if (!strcmp_icase(exclude, basename))
return to_exclude;
} else if (x->flags & EXC_FLAG_ENDSWITH) {
if (x->patternlen - 1 <= pathlen &&
- !strcmp(exclude + 1, pathname + pathlen - x->patternlen + 1))
+ !strcmp_icase(exclude + 1, pathname + pathlen - x->patternlen + 1))
return to_exclude;
} else {
- if (fnmatch(exclude, basename, 0) == 0)
+ if (fnmatch_icase(exclude, basename, 0) == 0)
return to_exclude;
}
}
@@ -396,14 +536,14 @@ int excluded_from_list(const char *pathname,
if (pathlen < baselen ||
(baselen && pathname[baselen-1] != '/') ||
- strncmp(pathname, x->base, baselen))
+ strncmp_icase(pathname, x->base, baselen))
continue;
if (x->flags & EXC_FLAG_NOWILDCARD) {
- if (!strcmp(exclude, pathname + baselen))
+ if (!strcmp_icase(exclude, pathname + baselen))
return to_exclude;
} else {
- if (fnmatch(exclude, pathname+baselen,
+ if (fnmatch_icase(exclude, pathname+baselen,
FNM_PATHNAME) == 0)
return to_exclude;
}
@@ -413,7 +553,7 @@ int excluded_from_list(const char *pathname,
return -1; /* undecided */
}
-int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
+static int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
{
int pathlen = strlen(pathname);
int st;
@@ -433,6 +573,64 @@ int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
return 0;
}
+void path_exclude_check_init(struct path_exclude_check *check,
+ struct dir_struct *dir)
+{
+ check->dir = dir;
+ strbuf_init(&check->path, 256);
+}
+
+void path_exclude_check_clear(struct path_exclude_check *check)
+{
+ strbuf_release(&check->path);
+}
+
+/*
+ * 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.
+ */
+int path_excluded(struct path_exclude_check *check,
+ const char *name, int namelen, int *dtype)
+{
+ int i;
+ struct strbuf *path = &check->path;
+
+ /*
+ * 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);
+
+ if (path->len &&
+ path->len <= namelen &&
+ !memcmp(name, path->buf, path->len) &&
+ (!name[path->len] || name[path->len] == '/'))
+ return 1;
+
+ strbuf_setlen(path, 0);
+ for (i = 0; name[i]; i++) {
+ int ch = name[i];
+
+ if (ch == '/') {
+ int dt = DT_DIR;
+ if (excluded(check->dir, path->buf, &dt))
+ return 1;
+ }
+ strbuf_addch(path, ch);
+ }
+
+ /* An entry in the index; cannot be a directory with subentries */
+ strbuf_setlen(path, 0);
+
+ return excluded(check->dir, name, dtype);
+}
+
static struct dir_entry *dir_entry_new(const char *pathname, int len)
{
struct dir_entry *ent;
@@ -453,7 +651,7 @@ static struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathna
return dir->entries[dir->nr++] = dir_entry_new(pathname, len);
}
-static struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len)
+struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len)
{
if (!cache_name_is_other(pathname, len))
return NULL;
@@ -465,10 +663,43 @@ static struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pat
enum exist_status {
index_nonexistent = 0,
index_directory,
- index_gitdir,
+ index_gitdir
};
/*
+ * Do not use the alphabetically stored index to look up
+ * the directory name; instead, use the case insensitive
+ * name hash.
+ */
+static enum exist_status directory_exists_in_index_icase(const char *dirname, int len)
+{
+ struct cache_entry *ce = index_name_exists(&the_index, dirname, len + 1, ignore_case);
+ unsigned char endchar;
+
+ if (!ce)
+ return index_nonexistent;
+ endchar = ce->name[len];
+
+ /*
+ * The cache_entry structure returned will contain this dirname
+ * and possibly additional path components.
+ */
+ if (endchar == '/')
+ return index_directory;
+
+ /*
+ * If there are no additional path components, then this cache_entry
+ * represents a submodule. Submodules, despite being directories,
+ * are stored in the cache without a closing slash.
+ */
+ if (!endchar && S_ISGITLINK(ce->ce_mode))
+ return index_gitdir;
+
+ /* This should never be hit, but it exists just in case. */
+ return index_nonexistent;
+}
+
+/*
* The index sorts alphabetically by entry name, which
* means that a gitlink sorts as '\0' at the end, while
* a directory (which is defined not as an entry, but as
@@ -477,7 +708,12 @@ enum exist_status {
*/
static enum exist_status directory_exists_in_index(const char *dirname, int len)
{
- int pos = cache_name_pos(dirname, len);
+ int pos;
+
+ if (ignore_case)
+ return directory_exists_in_index_icase(dirname, len);
+
+ pos = cache_name_pos(dirname, len);
if (pos < 0)
pos = -pos-1;
while (pos < active_nr) {
@@ -533,7 +769,7 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
enum directory_treatment {
show_directory,
ignore_directory,
- recurse_into_directory,
+ recurse_into_directory
};
static enum directory_treatment treat_directory(struct dir_struct *dir,
@@ -684,18 +920,18 @@ static int get_dtype(struct dirent *de, const char *path, int len)
enum path_treatment {
path_ignored,
path_handled,
- path_recurse,
+ path_recurse
};
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 = 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
@@ -705,7 +941,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?
@@ -722,9 +958,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))
@@ -745,26 +980,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);
}
/*
@@ -781,36 +1011,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;
}
@@ -873,8 +1104,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] == '/')
@@ -889,19 +1120,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)
@@ -927,46 +1161,45 @@ int file_exists(const char *f)
}
/*
- * get_relative_cwd() gets the prefix of the current working directory
- * relative to 'dir'. If we are not inside 'dir', it returns NULL.
- *
- * As a convenience, it also returns NULL if 'dir' is already NULL. The
- * reason for this behaviour is that it is natural for functions returning
- * directory names to return NULL to say "this directory does not exist"
- * or "this directory is invalid". These cases are usually handled the
- * same as if the cwd is not inside 'dir' at all, so get_relative_cwd()
- * returns NULL for both of them.
- *
- * Most notably, get_relative_cwd(buffer, size, get_git_work_tree())
- * unifies the handling of "outside work tree" with "no work tree at all".
+ * Given two normalized paths (a trailing slash is ok), if subdir is
+ * outside dir, return -1. Otherwise return the offset in subdir that
+ * can be used as relative path to dir.
*/
-char *get_relative_cwd(char *buffer, int size, const char *dir)
+int dir_inside_of(const char *subdir, const char *dir)
{
- char *cwd = buffer;
+ int offset = 0;
- if (!dir)
- return NULL;
- if (!getcwd(buffer, size))
- die_errno("can't find the current directory");
-
- if (!is_absolute_path(dir))
- dir = make_absolute_path(dir);
+ assert(dir && subdir && *dir && *subdir);
- while (*dir && *dir == *cwd) {
+ while (*dir && *subdir && *dir == *subdir) {
dir++;
- cwd++;
+ subdir++;
+ offset++;
}
- if (*dir)
- return NULL;
- if (*cwd == '/')
- return cwd + 1;
- return cwd;
+
+ /* hel[p]/me vs hel[l]/yeah */
+ if (*dir && *subdir)
+ return -1;
+
+ if (!*subdir)
+ return !*dir ? offset : -1; /* same dir */
+
+ /* foo/[b]ar vs foo/[] */
+ if (is_dir_sep(dir[-1]))
+ return is_dir_sep(subdir[-1]) ? offset : -1;
+
+ /* foo[/]bar vs foo[] */
+ return is_dir_sep(*subdir) ? offset + 1 : -1;
}
int is_inside_dir(const char *dir)
{
- char buffer[PATH_MAX];
- return get_relative_cwd(buffer, sizeof(buffer), dir) != NULL;
+ char cwd[PATH_MAX];
+ if (!dir)
+ return 0;
+ if (!getcwd(cwd, sizeof(cwd)))
+ die_errno("can't find the current directory");
+ return dir_inside_of(cwd, dir) >= 0;
}
int is_empty_dir(const char *path)
@@ -988,22 +1221,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 -1;
+ 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, '/');
@@ -1018,7 +1261,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 */
@@ -1030,11 +1273,22 @@ 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;
@@ -1066,3 +1320,50 @@ int remove_path(const char *name)
return 0;
}
+static int pathspec_item_cmp(const void *a_, const void *b_)
+{
+ struct pathspec_item *a, *b;
+
+ a = (struct pathspec_item *)a_;
+ b = (struct pathspec_item *)b_;
+ return strcmp(a->match, b->match);
+}
+
+int init_pathspec(struct pathspec *pathspec, const char **paths)
+{
+ const char **p = paths;
+ int i;
+
+ memset(pathspec, 0, sizeof(*pathspec));
+ if (!p)
+ return 0;
+ while (*p)
+ p++;
+ pathspec->raw = paths;
+ pathspec->nr = p - paths;
+ if (!pathspec->nr)
+ return 0;
+
+ pathspec->items = xmalloc(sizeof(struct pathspec_item)*pathspec->nr);
+ for (i = 0; i < pathspec->nr; i++) {
+ struct pathspec_item *item = pathspec->items+i;
+ const char *path = paths[i];
+
+ item->match = path;
+ item->len = strlen(path);
+ item->use_wildcard = !no_wildcard(path);
+ if (item->use_wildcard)
+ pathspec->has_wildcard = 1;
+ }
+
+ qsort(pathspec->items, pathspec->nr,
+ sizeof(struct pathspec_item), pathspec_item_cmp);
+
+ return 0;
+}
+
+void free_pathspec(struct pathspec *pathspec)
+{
+ free(pathspec->items);
+ pathspec->items = NULL;
+}