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 6b4eeef584309fb31fdf2fac78e220f990344b9a..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"
@@ -134,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;
@@ -209,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
@@ -261,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;
@@ -339,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 ||
@@ -1361,7 +1397,8 @@ static int simplify_away(const char *path, int pathlen,
                       PATHSPEC_LITERAL |
                       PATHSPEC_GLOB |
                       PATHSPEC_ICASE |
-                      PATHSPEC_EXCLUDE);
+                      PATHSPEC_EXCLUDE |
+                      PATHSPEC_ATTR);
 
        for (i = 0; i < pathspec->nr; i++) {
                const struct pathspec_item *item = &pathspec->items[i];
@@ -1805,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;
@@ -1813,6 +1850,14 @@ static int cmp_name(const void *p1, const void *p2)
        return name_compare(e1->name, e1->len, e2->name, e2->len);
 }
 
+/* check if *out lexically strictly contains *in */
+int check_dir_entry_contains(const struct dir_entry *out, const struct dir_entry *in)
+{
+       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 pathspec *pathspec)
@@ -2026,8 +2071,32 @@ int read_directory(struct dir_struct *dir, const char *path,
                dir->untracked = NULL;
        if (!len || treat_leading_path(dir, path, len, pathspec))
                read_directory_recursive(dir, path, len, untracked, 0, pathspec);
-       QSORT(dir->entries, dir->nr, cmp_name);
-       QSORT(dir->ignored, dir->ignored_nr, cmp_name);
+       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,
@@ -2731,23 +2800,33 @@ void untracked_cache_add_to_index(struct index_state *istate,
 /* 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 file_name = STRBUF_INIT;
+       struct strbuf gitfile_sb = STRBUF_INIT;
+       struct strbuf cfg_sb = STRBUF_INIT;
        struct strbuf rel_path = STRBUF_INIT;
-       char *git_dir = real_pathdup(git_dir_, 1);
-       char *work_tree = real_pathdup(work_tree_, 1);
+       char *git_dir, *work_tree;
 
-       /* Update gitfile */
-       strbuf_addf(&file_name, "%s/.git", work_tree);
-       write_file(file_name.buf, "gitdir: %s",
-                  relative_path(git_dir, work_tree, &rel_path));
+       /* 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 */
-       strbuf_reset(&file_name);
-       strbuf_addf(&file_name, "%s/config", git_dir);
-       git_config_set_in_file(file_name.buf, "core.worktree",
+       git_config_set_in_file(cfg_sb.buf, "core.worktree",
                               relative_path(work_tree, git_dir, &rel_path));
 
-       strbuf_release(&file_name);
+       strbuf_release(&gitfile_sb);
+       strbuf_release(&cfg_sb);
        strbuf_release(&rel_path);
        free(work_tree);
        free(git_dir);