Allow the test suite to pass in a directory whose name contains spaces
[gitweb.git] / dir.c
diff --git a/dir.c b/dir.c
index bfa8c8a9a51297c3a969c8706467504cb11ce51b..31f9343f9fcf57a89cab519f284cf2e0a767744d 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -9,6 +9,7 @@
  */
 #include "cache.h"
 #include "dir.h"
+#include "attr.h"
 #include "refs.h"
 #include "wildmatch.h"
 #include "pathspec.h"
 #include "varint.h"
 #include "ewah/ewok.h"
 
-struct path_simplify {
-       int len;
-       const char *path;
-};
-
 /*
  * Tells read_directory_recursive how a file or directory should be treated.
  * Values are ordered by significance, e.g. if a directory contains both
@@ -50,7 +46,7 @@ struct cached_dir {
 
 static enum path_treatment read_directory_recursive(struct dir_struct *dir,
        const char *path, int len, struct untracked_cache_dir *untracked,
-       int check_only, const struct path_simplify *simplify);
+       int check_only, const struct pathspec *pathspec);
 static int get_dtype(struct dirent *de, const char *path, int len);
 
 int fspathcmp(const char *a, const char *b)
@@ -139,7 +135,8 @@ static size_t common_prefix_len(const struct pathspec *pathspec)
                       PATHSPEC_LITERAL |
                       PATHSPEC_GLOB |
                       PATHSPEC_ICASE |
-                      PATHSPEC_EXCLUDE);
+                      PATHSPEC_EXCLUDE |
+                      PATHSPEC_ATTR);
 
        for (n = 0; n < pathspec->nr; n++) {
                size_t i = 0, len = 0, item_len;
@@ -179,17 +176,20 @@ char *common_prefix(const struct pathspec *pathspec)
 
 int fill_directory(struct dir_struct *dir, const struct pathspec *pathspec)
 {
-       size_t len;
+       const char *prefix;
+       size_t prefix_len;
 
        /*
         * Calculate common prefix for the pathspec, and
         * use that to optimize the directory walk
         */
-       len = common_prefix_len(pathspec);
+       prefix_len = common_prefix_len(pathspec);
+       prefix = prefix_len ? pathspec->items[0].match : "";
 
        /* Read the directory and prune it */
-       read_directory(dir, pathspec->nr ? pathspec->_raw[0] : "", len, pathspec);
-       return len;
+       read_directory(dir, prefix, prefix_len, pathspec);
+
+       return prefix_len;
 }
 
 int within_depth(const char *name, int namelen,
@@ -211,6 +211,36 @@ int within_depth(const char *name, int namelen,
 #define DO_MATCH_DIRECTORY (1<<1)
 #define DO_MATCH_SUBMODULE (1<<2)
 
+static int match_attrs(const char *name, int namelen,
+                      const struct pathspec_item *item)
+{
+       int i;
+
+       git_check_attr(name, item->attr_check);
+       for (i = 0; i < item->attr_match_nr; i++) {
+               const char *value;
+               int matched;
+               enum attr_match_mode match_mode;
+
+               value = item->attr_check->items[i].value;
+               match_mode = item->attr_match[i].match_mode;
+
+               if (ATTR_TRUE(value))
+                       matched = (match_mode == MATCH_SET);
+               else if (ATTR_FALSE(value))
+                       matched = (match_mode == MATCH_UNSET);
+               else if (ATTR_UNSET(value))
+                       matched = (match_mode == MATCH_UNSPECIFIED);
+               else
+                       matched = (match_mode == MATCH_VALUE &&
+                                  !strcmp(item->attr_match[i].value, value));
+               if (!matched)
+                       return 0;
+       }
+
+       return 1;
+}
+
 /*
  * Does 'match' match the given name?
  * A match is found if
@@ -263,6 +293,9 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix,
            strncmp(item->match, name - prefix, item->prefix))
                return 0;
 
+       if (item->attr_match_nr && !match_attrs(name, namelen, item))
+               return 0;
+
        /* If the match was just the prefix, we matched */
        if (!*match)
                return MATCHED_RECURSIVELY;
@@ -341,7 +374,8 @@ static int do_match_pathspec(const struct pathspec *ps,
                       PATHSPEC_LITERAL |
                       PATHSPEC_GLOB |
                       PATHSPEC_ICASE |
-                      PATHSPEC_EXCLUDE);
+                      PATHSPEC_EXCLUDE |
+                      PATHSPEC_ATTR);
 
        if (!ps->nr) {
                if (!ps->recursive ||
@@ -1312,7 +1346,7 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
 static enum path_treatment treat_directory(struct dir_struct *dir,
        struct untracked_cache_dir *untracked,
        const char *dirname, int len, int baselen, int exclude,
-       const struct path_simplify *simplify)
+       const struct pathspec *pathspec)
 {
        /* The "len-1" is to strip the final '/' */
        switch (directory_exists_in_index(dirname, len-1)) {
@@ -1341,7 +1375,7 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
        untracked = lookup_untracked(dir->untracked, untracked,
                                     dirname + baselen, len - baselen);
        return read_directory_recursive(dir, dirname, len,
-                                       untracked, 1, simplify);
+                                       untracked, 1, pathspec);
 }
 
 /*
@@ -1349,24 +1383,34 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
  * reading - if the path cannot possibly be in the pathspec,
  * return true, and we'll skip it early.
  */
-static int simplify_away(const char *path, int pathlen, const struct path_simplify *simplify)
+static int simplify_away(const char *path, int pathlen,
+                        const struct pathspec *pathspec)
 {
-       if (simplify) {
-               for (;;) {
-                       const char *match = simplify->path;
-                       int len = simplify->len;
+       int i;
 
-                       if (!match)
-                               break;
-                       if (len > pathlen)
-                               len = pathlen;
-                       if (!memcmp(path, match, len))
-                               return 0;
-                       simplify++;
-               }
-               return 1;
+       if (!pathspec || !pathspec->nr)
+               return 0;
+
+       GUARD_PATHSPEC(pathspec,
+                      PATHSPEC_FROMTOP |
+                      PATHSPEC_MAXDEPTH |
+                      PATHSPEC_LITERAL |
+                      PATHSPEC_GLOB |
+                      PATHSPEC_ICASE |
+                      PATHSPEC_EXCLUDE |
+                      PATHSPEC_ATTR);
+
+       for (i = 0; i < pathspec->nr; i++) {
+               const struct pathspec_item *item = &pathspec->items[i];
+               int len = item->nowildcard_len;
+
+               if (len > pathlen)
+                       len = pathlen;
+               if (!ps_strncmp(item, item->match, path, len))
+                       return 0;
        }
-       return 0;
+
+       return 1;
 }
 
 /*
@@ -1380,19 +1424,33 @@ static int simplify_away(const char *path, int pathlen, const struct path_simpli
  *   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;
-               }
+static int exclude_matches_pathspec(const char *path, int pathlen,
+                                   const struct pathspec *pathspec)
+{
+       int i;
+
+       if (!pathspec || !pathspec->nr)
+               return 0;
+
+       GUARD_PATHSPEC(pathspec,
+                      PATHSPEC_FROMTOP |
+                      PATHSPEC_MAXDEPTH |
+                      PATHSPEC_LITERAL |
+                      PATHSPEC_GLOB |
+                      PATHSPEC_ICASE |
+                      PATHSPEC_EXCLUDE);
+
+       for (i = 0; i < pathspec->nr; i++) {
+               const struct pathspec_item *item = &pathspec->items[i];
+               int len = item->nowildcard_len;
+
+               if (len == pathlen &&
+                   !ps_strncmp(item, item->match, path, pathlen))
+                       return 1;
+               if (len > pathlen &&
+                   item->match[pathlen] == '/' &&
+                   !ps_strncmp(item, item->match, path, pathlen))
+                       return 1;
        }
        return 0;
 }
@@ -1460,7 +1518,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
                                          struct untracked_cache_dir *untracked,
                                          struct strbuf *path,
                                          int baselen,
-                                         const struct path_simplify *simplify,
+                                         const struct pathspec *pathspec,
                                          int dtype, struct dirent *de)
 {
        int exclude;
@@ -1512,7 +1570,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
        case DT_DIR:
                strbuf_addch(path, '/');
                return treat_directory(dir, untracked, path->buf, path->len,
-                                      baselen, exclude, simplify);
+                                      baselen, exclude, pathspec);
        case DT_REG:
        case DT_LNK:
                return exclude ? path_excluded : path_untracked;
@@ -1524,7 +1582,7 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir,
                                           struct cached_dir *cdir,
                                           struct strbuf *path,
                                           int baselen,
-                                          const struct path_simplify *simplify)
+                                          const struct pathspec *pathspec)
 {
        strbuf_setlen(path, baselen);
        if (!cdir->ucd) {
@@ -1541,7 +1599,7 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir,
                 * with check_only set.
                 */
                return read_directory_recursive(dir, path->buf, path->len,
-                                               cdir->ucd, 1, simplify);
+                                               cdir->ucd, 1, pathspec);
        /*
         * We get path_recurse in the first run when
         * directory_exists_in_index() returns index_nonexistent. We
@@ -1556,23 +1614,23 @@ static enum path_treatment treat_path(struct dir_struct *dir,
                                      struct cached_dir *cdir,
                                      struct strbuf *path,
                                      int baselen,
-                                     const struct path_simplify *simplify)
+                                     const struct pathspec *pathspec)
 {
        int dtype;
        struct dirent *de = cdir->de;
 
        if (!de)
                return treat_path_fast(dir, untracked, cdir, path,
-                                      baselen, simplify);
+                                      baselen, pathspec);
        if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
                return path_none;
        strbuf_setlen(path, baselen);
        strbuf_addstr(path, de->d_name);
-       if (simplify_away(path->buf, path->len, simplify))
+       if (simplify_away(path->buf, path->len, pathspec))
                return path_none;
 
        dtype = DTYPE(de);
-       return treat_one_path(dir, untracked, path, baselen, simplify, dtype, de);
+       return treat_one_path(dir, untracked, path, baselen, pathspec, dtype, de);
 }
 
 static void add_untracked(struct untracked_cache_dir *dir, const char *name)
@@ -1703,7 +1761,7 @@ static void close_cached_dir(struct cached_dir *cdir)
 static enum path_treatment read_directory_recursive(struct dir_struct *dir,
                                    const char *base, int baselen,
                                    struct untracked_cache_dir *untracked, int check_only,
-                                   const struct path_simplify *simplify)
+                                   const struct pathspec *pathspec)
 {
        struct cached_dir cdir;
        enum path_treatment state, subdir_state, dir_state = path_none;
@@ -1719,20 +1777,25 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
 
        while (!read_cached_dir(&cdir)) {
                /* check how the file or directory should be treated */
-               state = treat_path(dir, untracked, &cdir, &path, baselen, simplify);
+               state = treat_path(dir, untracked, &cdir, &path,
+                                  baselen, pathspec);
 
                if (state > dir_state)
                        dir_state = state;
 
                /* recurse into subdir if instructed by treat_path */
-               if (state == path_recurse) {
+               if ((state == path_recurse) ||
+                       ((state == path_untracked) &&
+                        (dir->flags & DIR_SHOW_IGNORED_TOO) &&
+                        (get_dtype(cdir.de, path.buf, path.len) == DT_DIR))) {
                        struct untracked_cache_dir *ud;
                        ud = lookup_untracked(dir->untracked, untracked,
                                              path.buf + baselen,
                                              path.len - baselen);
                        subdir_state =
-                               read_directory_recursive(dir, path.buf, path.len,
-                                                        ud, check_only, simplify);
+                               read_directory_recursive(dir, path.buf,
+                                                        path.len, ud,
+                                                        check_only, pathspec);
                        if (subdir_state > dir_state)
                                dir_state = subdir_state;
                }
@@ -1756,7 +1819,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
                        else if ((dir->flags & DIR_SHOW_IGNORED_TOO) ||
                                ((dir->flags & DIR_COLLECT_IGNORED) &&
                                exclude_matches_pathspec(path.buf, path.len,
-                                       simplify)))
+                                                        pathspec)))
                                dir_add_ignored(dir, path.buf, path.len);
                        break;
 
@@ -1779,7 +1842,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
        return dir_state;
 }
 
-static int cmp_name(const void *p1, const void *p2)
+int cmp_dir_entry(const void *p1, const void *p2)
 {
        const struct dir_entry *e1 = *(const struct dir_entry **)p1;
        const struct dir_entry *e2 = *(const struct dir_entry **)p2;
@@ -1787,36 +1850,17 @@ static int cmp_name(const void *p1, const void *p2)
        return name_compare(e1->name, e1->len, e2->name, e2->len);
 }
 
-static struct path_simplify *create_simplify(const char **pathspec)
-{
-       int nr, alloc = 0;
-       struct path_simplify *simplify = NULL;
-
-       if (!pathspec)
-               return NULL;
-
-       for (nr = 0 ; ; nr++) {
-               const char *match;
-               ALLOC_GROW(simplify, nr + 1, alloc);
-               match = *pathspec++;
-               if (!match)
-                       break;
-               simplify[nr].path = match;
-               simplify[nr].len = simple_length(match);
-       }
-       simplify[nr].path = NULL;
-       simplify[nr].len = 0;
-       return simplify;
-}
-
-static void free_simplify(struct path_simplify *simplify)
+/* check if *out lexically strictly contains *in */
+int check_dir_entry_contains(const struct dir_entry *out, const struct dir_entry *in)
 {
-       free(simplify);
+       return (out->len < in->len) &&
+               (out->name[out->len - 1] == '/') &&
+               !memcmp(out->name, in->name, out->len);
 }
 
 static int treat_leading_path(struct dir_struct *dir,
                              const char *path, int len,
-                             const struct path_simplify *simplify)
+                             const struct pathspec *pathspec)
 {
        struct strbuf sb = STRBUF_INIT;
        int baselen, rc = 0;
@@ -1840,9 +1884,9 @@ static int treat_leading_path(struct dir_struct *dir,
                strbuf_add(&sb, path, baselen);
                if (!is_directory(sb.buf))
                        break;
-               if (simplify_away(sb.buf, sb.len, simplify))
+               if (simplify_away(sb.buf, sb.len, pathspec))
                        break;
-               if (treat_one_path(dir, NULL, &sb, baselen, simplify,
+               if (treat_one_path(dir, NULL, &sb, baselen, pathspec,
                                   DT_DIR, NULL) == path_none)
                        break; /* do not recurse into it */
                if (len <= baselen) {
@@ -2010,33 +2054,14 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d
        return root;
 }
 
-int read_directory(struct dir_struct *dir, const char *path, int len, const struct pathspec *pathspec)
+int read_directory(struct dir_struct *dir, const char *path,
+                  int len, const struct pathspec *pathspec)
 {
-       struct path_simplify *simplify;
        struct untracked_cache_dir *untracked;
 
-       /*
-        * Check out create_simplify()
-        */
-       if (pathspec)
-               GUARD_PATHSPEC(pathspec,
-                              PATHSPEC_FROMTOP |
-                              PATHSPEC_MAXDEPTH |
-                              PATHSPEC_LITERAL |
-                              PATHSPEC_GLOB |
-                              PATHSPEC_ICASE |
-                              PATHSPEC_EXCLUDE);
-
        if (has_symlink_leading_path(path, len))
                return dir->nr;
 
-       /*
-        * exclude patterns are treated like positive ones in
-        * create_simplify. Usually exclude patterns should be a
-        * subset of positive ones, which has no impacts on
-        * create_simplify().
-        */
-       simplify = create_simplify(pathspec ? pathspec->_raw : NULL);
        untracked = validate_untracked_cache(dir, len, pathspec);
        if (!untracked)
                /*
@@ -2044,11 +2069,34 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru
                 * e.g. prep_exclude()
                 */
                dir->untracked = NULL;
-       if (!len || treat_leading_path(dir, path, len, simplify))
-               read_directory_recursive(dir, path, len, untracked, 0, simplify);
-       free_simplify(simplify);
-       QSORT(dir->entries, dir->nr, cmp_name);
-       QSORT(dir->ignored, dir->ignored_nr, cmp_name);
+       if (!len || treat_leading_path(dir, path, len, pathspec))
+               read_directory_recursive(dir, path, len, untracked, 0, pathspec);
+       QSORT(dir->entries, dir->nr, cmp_dir_entry);
+       QSORT(dir->ignored, dir->ignored_nr, cmp_dir_entry);
+
+       /*
+        * If DIR_SHOW_IGNORED_TOO is set, read_directory_recursive() will
+        * also pick up untracked contents of untracked dirs; by default
+        * we discard these, but given DIR_KEEP_UNTRACKED_CONTENTS we do not.
+        */
+       if ((dir->flags & DIR_SHOW_IGNORED_TOO) &&
+                    !(dir->flags & DIR_KEEP_UNTRACKED_CONTENTS)) {
+               int i, j;
+
+               /* remove from dir->entries untracked contents of untracked dirs */
+               for (i = j = 0; j < dir->nr; j++) {
+                       if (i &&
+                           check_dir_entry_contains(dir->entries[i - 1], dir->entries[j])) {
+                               free(dir->entries[j]);
+                               dir->entries[j] = NULL;
+                       } else {
+                               dir->entries[i++] = dir->entries[j];
+                       }
+               }
+
+               dir->nr = i;
+       }
+
        if (dir->untracked) {
                static struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS);
                trace_printf_key(&trace_untracked_stats,
@@ -2748,3 +2796,50 @@ void untracked_cache_add_to_index(struct index_state *istate,
 {
        untracked_cache_invalidate_path(istate, path);
 }
+
+/* Update gitfile and core.worktree setting to connect work tree and git dir */
+void connect_work_tree_and_git_dir(const char *work_tree_, const char *git_dir_)
+{
+       struct strbuf gitfile_sb = STRBUF_INIT;
+       struct strbuf cfg_sb = STRBUF_INIT;
+       struct strbuf rel_path = STRBUF_INIT;
+       char *git_dir, *work_tree;
+
+       /* Prepare .git file */
+       strbuf_addf(&gitfile_sb, "%s/.git", work_tree_);
+       if (safe_create_leading_directories_const(gitfile_sb.buf))
+               die(_("could not create directories for %s"), gitfile_sb.buf);
+
+       /* Prepare config file */
+       strbuf_addf(&cfg_sb, "%s/config", git_dir_);
+       if (safe_create_leading_directories_const(cfg_sb.buf))
+               die(_("could not create directories for %s"), cfg_sb.buf);
+
+       git_dir = real_pathdup(git_dir_, 1);
+       work_tree = real_pathdup(work_tree_, 1);
+
+       /* Write .git file */
+       write_file(gitfile_sb.buf, "gitdir: %s",
+                  relative_path(git_dir, work_tree, &rel_path));
+       /* Update core.worktree setting */
+       git_config_set_in_file(cfg_sb.buf, "core.worktree",
+                              relative_path(work_tree, git_dir, &rel_path));
+
+       strbuf_release(&gitfile_sb);
+       strbuf_release(&cfg_sb);
+       strbuf_release(&rel_path);
+       free(work_tree);
+       free(git_dir);
+}
+
+/*
+ * Migrate the git directory of the given path from old_git_dir to new_git_dir.
+ */
+void relocate_gitdir(const char *path, const char *old_git_dir, const char *new_git_dir)
+{
+       if (rename(old_git_dir, new_git_dir) < 0)
+               die_errno(_("could not migrate git directory from '%s' to '%s'"),
+                       old_git_dir, new_git_dir);
+
+       connect_work_tree_and_git_dir(path, new_git_dir);
+}