docs: drop asciidoc7compatible flag
[gitweb.git] / dir.c
diff --git a/dir.c b/dir.c
index 133c333df61be37e7908f77367f63c85cfc9c548..ed1510fbc808f4c9799eb84a3c38030b7565f3bc 100644 (file)
--- 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;
                                }
@@ -453,7 +593,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,9 +605,42 @@ 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
@@ -477,7 +650,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 +711,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,
@@ -594,13 +772,29 @@ static int simplify_away(const char *path, int pathlen, const struct path_simpli
        return 0;
 }
 
-static int in_pathspec(const char *path, int len, const struct path_simplify *simplify)
+/*
+ * This function tells us whether an excluded path matches a
+ * list of "interesting" pathspecs. That is, whether a path matched
+ * by any of the pathspecs could possibly be ignored by excluding
+ * the specified path. This can happen if:
+ *
+ *   1. the path is mentioned explicitly in the pathspec
+ *
+ *   2. the path is a directory prefix of some element in the
+ *      pathspec
+ */
+static int exclude_matches_pathspec(const char *path, int len,
+               const struct path_simplify *simplify)
 {
        if (simplify) {
                for (; simplify->path; simplify++) {
                        if (len == simplify->len
                            && !memcmp(path, simplify->path, len))
                                return 1;
+                       if (len < simplify->len
+                           && simplify->path[len] == '/'
+                           && !memcmp(path, simplify->path, len))
+                               return 1;
                }
        }
        return 0;
@@ -668,18 +862,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)
-           && in_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
@@ -689,7 +883,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?
@@ -706,9 +900,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))
@@ -729,26 +922,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);
 }
 
 /*
@@ -765,36 +953,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;
 }
@@ -857,8 +1046,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] == '/')
@@ -873,19 +1062,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)
@@ -911,46 +1103,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)
@@ -972,22 +1163,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, '/');
 
@@ -1002,7 +1203,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 */
@@ -1014,11 +1215,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;
@@ -1050,3 +1262,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;
+}