]> git.ipfire.org Git - thirdparty/git.git/blobdiff - dir.c
t1091: use check_files to reduce boilerplate
[thirdparty/git.git] / dir.c
diff --git a/dir.c b/dir.c
index cab9c2a4588669f3aef3637ada377712998fa01a..22d08e61c296a0ca2676c705422b9b50c292650c 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -139,7 +139,7 @@ static size_t common_prefix_len(const struct pathspec *pathspec)
         * ":(icase)path" is treated as a pathspec full of
         * wildcard. In other words, only prefix is considered common
         * prefix. If the pathspec is abc/foo abc/bar, running in
-        * subdir xyz, the common prefix is still xyz, not xuz/abc as
+        * subdir xyz, the common prefix is still xyz, not xyz/abc as
         * in non-:(icase).
         */
        GUARD_PATHSPEC(pathspec,
@@ -273,19 +273,30 @@ static int do_read_blob(const struct object_id *oid, struct oid_stat *oid_stat,
 
 #define DO_MATCH_EXCLUDE   (1<<0)
 #define DO_MATCH_DIRECTORY (1<<1)
-#define DO_MATCH_SUBMODULE (1<<2)
+#define DO_MATCH_LEADING_PATHSPEC (1<<2)
 
 /*
- * Does 'match' match the given name?
- * A match is found if
+ * Does the given pathspec 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'.
+ * (1) the pathspec string is leading directory of 'name' ("RECURSIVELY"), or
+ * (2) the pathspec string has a leading part matching 'name' ("LEADING"), or
+ * (3) the pathspec string is a wildcard and matches 'name' ("WILDCARD"), or
+ * (4) the pathspec string is exactly the same as 'name' ("EXACT").
  *
- * and the return value tells which case it was.
+ * Return value tells which case it was (1-4), or 0 when there is no match.
  *
- * It returns 0 when there is no match.
+ * It may be instructive to look at a small table of concrete examples
+ * to understand the differences between 1, 2, and 4:
+ *
+ *                              Pathspecs
+ *                |    a/b    |   a/b/    |   a/b/c
+ *          ------+-----------+-----------+------------
+ *          a/b   |  EXACT    |  EXACT[1] | LEADING[2]
+ *  Names   a/b/  | RECURSIVE |   EXACT   | LEADING[2]
+ *          a/b/c | RECURSIVE | RECURSIVE |   EXACT
+ *
+ * [1] Only if DO_MATCH_DIRECTORY is passed; otherwise, this is NOT a match.
+ * [2] Only if DO_MATCH_LEADING_PATHSPEC is passed; otherwise, not a match.
  */
 static int match_pathspec_item(const struct index_state *istate,
                               const struct pathspec_item *item, int prefix,
@@ -353,13 +364,14 @@ static int match_pathspec_item(const struct index_state *istate,
                         item->nowildcard_len - prefix))
                return MATCHED_FNMATCH;
 
-       /* Perform checks to see if "name" is a super set of the pathspec */
-       if (flags & DO_MATCH_SUBMODULE) {
+       /* Perform checks to see if "name" is a leading string of the pathspec */
+       if (flags & DO_MATCH_LEADING_PATHSPEC) {
                /* name is a literal prefix of the pathspec */
+               int offset = name[namelen-1] == '/' ? 1 : 0;
                if ((namelen < matchlen) &&
-                   (match[namelen] == '/') &&
+                   (match[namelen-offset] == '/') &&
                    !ps_strncmp(item, match, name, namelen))
-                       return MATCHED_RECURSIVELY;
+                       return MATCHED_RECURSIVELY_LEADING_PATHSPEC;
 
                /* name" doesn't match up to the first wild character */
                if (item->nowildcard_len < item->len &&
@@ -376,7 +388,7 @@ static int match_pathspec_item(const struct index_state *istate,
                 * The submodules themselves will be able to perform more
                 * accurate matching to determine if the pathspec matches.
                 */
-               return MATCHED_RECURSIVELY;
+               return MATCHED_RECURSIVELY_LEADING_PATHSPEC;
        }
 
        return 0;
@@ -497,7 +509,7 @@ int submodule_path_match(const struct index_state *istate,
                                        strlen(submodule_name),
                                        0, seen,
                                        DO_MATCH_DIRECTORY |
-                                       DO_MATCH_SUBMODULE);
+                                       DO_MATCH_LEADING_PATHSPEC);
        return matched;
 }
 
@@ -599,6 +611,159 @@ void parse_path_pattern(const char **pattern,
        *patternlen = len;
 }
 
+int pl_hashmap_cmp(const void *unused_cmp_data,
+                  const struct hashmap_entry *a,
+                  const struct hashmap_entry *b,
+                  const void *key)
+{
+       const struct pattern_entry *ee1 =
+                       container_of(a, struct pattern_entry, ent);
+       const struct pattern_entry *ee2 =
+                       container_of(b, struct pattern_entry, ent);
+
+       size_t min_len = ee1->patternlen <= ee2->patternlen
+                        ? ee1->patternlen
+                        : ee2->patternlen;
+
+       if (ignore_case)
+               return strncasecmp(ee1->pattern, ee2->pattern, min_len);
+       return strncmp(ee1->pattern, ee2->pattern, min_len);
+}
+
+static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern *given)
+{
+       struct pattern_entry *translated;
+       char *truncated;
+       char *data = NULL;
+
+       if (!pl->use_cone_patterns)
+               return;
+
+       if (given->flags & PATTERN_FLAG_NEGATIVE &&
+           given->flags & PATTERN_FLAG_MUSTBEDIR &&
+           !strcmp(given->pattern, "/*")) {
+               pl->full_cone = 0;
+               return;
+       }
+
+       if (!given->flags && !strcmp(given->pattern, "/*")) {
+               pl->full_cone = 1;
+               return;
+       }
+
+       if (given->patternlen > 2 &&
+           !strcmp(given->pattern + given->patternlen - 2, "/*")) {
+               if (!(given->flags & PATTERN_FLAG_NEGATIVE)) {
+                       /* Not a cone pattern. */
+                       pl->use_cone_patterns = 0;
+                       warning(_("unrecognized pattern: '%s'"), given->pattern);
+                       goto clear_hashmaps;
+               }
+
+               truncated = xstrdup(given->pattern);
+               truncated[given->patternlen - 2] = 0;
+
+               translated = xmalloc(sizeof(struct pattern_entry));
+               translated->pattern = truncated;
+               translated->patternlen = given->patternlen - 2;
+               hashmap_entry_init(&translated->ent,
+                                  ignore_case ?
+                                  strihash(translated->pattern) :
+                                  strhash(translated->pattern));
+
+               if (!hashmap_get_entry(&pl->recursive_hashmap,
+                                      translated, ent, NULL)) {
+                       /* We did not see the "parent" included */
+                       warning(_("unrecognized negative pattern: '%s'"),
+                               given->pattern);
+                       free(truncated);
+                       free(translated);
+                       goto clear_hashmaps;
+               }
+
+               hashmap_add(&pl->parent_hashmap, &translated->ent);
+               hashmap_remove(&pl->recursive_hashmap, &translated->ent, &data);
+               free(data);
+               return;
+       }
+
+       if (given->flags & PATTERN_FLAG_NEGATIVE) {
+               warning(_("unrecognized negative pattern: '%s'"),
+                       given->pattern);
+               goto clear_hashmaps;
+       }
+
+       translated = xmalloc(sizeof(struct pattern_entry));
+
+       translated->pattern = xstrdup(given->pattern);
+       translated->patternlen = given->patternlen;
+       hashmap_entry_init(&translated->ent,
+                          ignore_case ?
+                          strihash(translated->pattern) :
+                          strhash(translated->pattern));
+
+       hashmap_add(&pl->recursive_hashmap, &translated->ent);
+
+       if (hashmap_get_entry(&pl->parent_hashmap, translated, ent, NULL)) {
+               /* we already included this at the parent level */
+               warning(_("your sparse-checkout file may have issues: pattern '%s' is repeated"),
+                       given->pattern);
+               hashmap_remove(&pl->parent_hashmap, &translated->ent, &data);
+               free(data);
+               free(translated);
+       }
+
+       return;
+
+clear_hashmaps:
+       warning(_("disabling cone pattern matching"));
+       hashmap_free_entries(&pl->parent_hashmap, struct pattern_entry, ent);
+       hashmap_free_entries(&pl->recursive_hashmap, struct pattern_entry, ent);
+       pl->use_cone_patterns = 0;
+}
+
+static int hashmap_contains_path(struct hashmap *map,
+                                struct strbuf *pattern)
+{
+       struct pattern_entry p;
+
+       /* Check straight mapping */
+       p.pattern = pattern->buf;
+       p.patternlen = pattern->len;
+       hashmap_entry_init(&p.ent,
+                          ignore_case ?
+                          strihash(p.pattern) :
+                          strhash(p.pattern));
+       return !!hashmap_get_entry(map, &p, ent, NULL);
+}
+
+int hashmap_contains_parent(struct hashmap *map,
+                           const char *path,
+                           struct strbuf *buffer)
+{
+       char *slash_pos;
+
+       strbuf_setlen(buffer, 0);
+
+       if (path[0] != '/')
+               strbuf_addch(buffer, '/');
+
+       strbuf_addstr(buffer, path);
+
+       slash_pos = strrchr(buffer->buf, '/');
+
+       while (slash_pos > buffer->buf) {
+               strbuf_setlen(buffer, slash_pos - buffer->buf);
+
+               if (hashmap_contains_path(map, buffer))
+                       return 1;
+
+               slash_pos = strrchr(buffer->buf, '/');
+       }
+
+       return 0;
+}
+
 void add_pattern(const char *string, const char *base,
                 int baselen, struct pattern_list *pl, int srcpos)
 {
@@ -623,6 +788,8 @@ void add_pattern(const char *string, const char *base,
        ALLOC_GROW(pl->patterns, pl->nr + 1, pl->alloc);
        pl->patterns[pl->nr++] = pattern;
        pattern->pl = pl;
+
+       add_pattern_to_hashsets(pl, pattern);
 }
 
 static int read_skip_worktree_file_from_index(const struct index_state *istate,
@@ -848,6 +1015,9 @@ static int add_patterns_from_buffer(char *buf, size_t size,
        int i, lineno = 1;
        char *entry;
 
+       hashmap_init(&pl->recursive_hashmap, pl_hashmap_cmp, NULL, 0);
+       hashmap_init(&pl->parent_hashmap, pl_hashmap_cmp, NULL, 0);
+
        pl->filebuf = buf;
 
        if (skip_utf8_bom(&buf, size))
@@ -1084,16 +1254,58 @@ enum pattern_match_result path_matches_pattern_list(
                                struct index_state *istate)
 {
        struct path_pattern *pattern;
-       pattern = last_matching_pattern_from_list(pathname, pathlen, basename,
-                                                 dtype, pl, istate);
-       if (pattern) {
-               if (pattern->flags & PATTERN_FLAG_NEGATIVE)
-                       return NOT_MATCHED;
-               else
-                       return MATCHED;
+       struct strbuf parent_pathname = STRBUF_INIT;
+       int result = NOT_MATCHED;
+       const char *slash_pos;
+
+       if (!pl->use_cone_patterns) {
+               pattern = last_matching_pattern_from_list(pathname, pathlen, basename,
+                                                       dtype, pl, istate);
+               if (pattern) {
+                       if (pattern->flags & PATTERN_FLAG_NEGATIVE)
+                               return NOT_MATCHED;
+                       else
+                               return MATCHED;
+               }
+
+               return UNDECIDED;
        }
 
-       return UNDECIDED;
+       if (pl->full_cone)
+               return MATCHED;
+
+       strbuf_addch(&parent_pathname, '/');
+       strbuf_add(&parent_pathname, pathname, pathlen);
+
+       if (hashmap_contains_path(&pl->recursive_hashmap,
+                                 &parent_pathname)) {
+               result = MATCHED_RECURSIVE;
+               goto done;
+       }
+
+       slash_pos = strrchr(parent_pathname.buf, '/');
+
+       if (slash_pos == parent_pathname.buf) {
+               /* include every file in root */
+               result = MATCHED;
+               goto done;
+       }
+
+       strbuf_setlen(&parent_pathname, slash_pos - parent_pathname.buf);
+
+       if (hashmap_contains_path(&pl->parent_hashmap, &parent_pathname)) {
+               result = MATCHED;
+               goto done;
+       }
+
+       if (hashmap_contains_parent(&pl->recursive_hashmap,
+                                   pathname,
+                                   &parent_pathname))
+               result = MATCHED_RECURSIVE;
+
+done:
+       strbuf_release(&parent_pathname);
+       return result;
 }
 
 static struct path_pattern *last_matching_pattern_from_lists(
@@ -1451,6 +1663,16 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
                return path_none;
 
        case index_nonexistent:
+               if (dir->flags & DIR_SKIP_NESTED_GIT) {
+                       int nested_repo;
+                       struct strbuf sb = STRBUF_INIT;
+                       strbuf_addstr(&sb, dirname);
+                       nested_repo = is_nonbare_repository_dir(&sb);
+                       strbuf_release(&sb);
+                       if (nested_repo)
+                               return path_none;
+               }
+
                if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
                        break;
                if (exclude &&
@@ -1950,8 +2172,11 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
                /* recurse into subdir if instructed by treat_path */
                if ((state == path_recurse) ||
                        ((state == path_untracked) &&
-                        (dir->flags & DIR_SHOW_IGNORED_TOO) &&
-                        (get_dtype(cdir.de, istate, path.buf, path.len) == DT_DIR))) {
+                        (get_dtype(cdir.de, istate, path.buf, path.len) == DT_DIR) &&
+                        ((dir->flags & DIR_SHOW_IGNORED_TOO) ||
+                         (pathspec &&
+                          do_match_pathspec(istate, pathspec, path.buf, path.len,
+                                            baselen, NULL, DO_MATCH_LEADING_PATHSPEC) == MATCHED_RECURSIVELY_LEADING_PATHSPEC)))) {
                        struct untracked_cache_dir *ud;
                        ud = lookup_untracked(dir->untracked, untracked,
                                              path.buf + baselen,
@@ -1962,6 +2187,12 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
                                                         check_only, stop_at_first_file, pathspec);
                        if (subdir_state > dir_state)
                                dir_state = subdir_state;
+
+                       if (pathspec &&
+                           !match_pathspec(istate, pathspec, path.buf, path.len,
+                                           0 /* prefix */, NULL,
+                                           0 /* do NOT special case dirs */))
+                               state = path_none;
                }
 
                if (check_only) {