diff options
Diffstat (limited to 'dir.c')
-rw-r--r-- | dir.c | 1101 |
1 files changed, 753 insertions, 348 deletions
@@ -7,19 +7,20 @@ * Copyright (C) Linus Torvalds, 2005-2006 * Junio Hamano, 2005-2006 */ +#define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" +#include "config.h" #include "dir.h" +#include "object-store.h" +#include "attr.h" #include "refs.h" #include "wildmatch.h" #include "pathspec.h" #include "utf8.h" #include "varint.h" #include "ewah/ewok.h" - -struct path_simplify { - int len; - const char *path; -}; +#include "fsmonitor.h" +#include "submodule-config.h" /* * Tells read_directory_recursive how a file or directory should be treated. @@ -49,26 +50,29 @@ 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); -static int get_dtype(struct dirent *de, const char *path, int len); + struct index_state *istate, const char *path, int len, + struct untracked_cache_dir *untracked, + int check_only, int stop_at_first_file, const struct pathspec *pathspec); +static int get_dtype(struct dirent *de, struct index_state *istate, + const char *path, int len); -/* helper string functions with support for the ignore_case flag */ -int strcmp_icase(const char *a, const char *b) +int count_slashes(const char *s) { - return ignore_case ? strcasecmp(a, b) : strcmp(a, b); + int cnt = 0; + while (*s) + if (*s++ == '/') + cnt++; + return cnt; } -int strncmp_icase(const char *a, const char *b, size_t count) +int fspathcmp(const char *a, const char *b) { - return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count); + return ignore_case ? strcasecmp(a, b) : strcmp(a, b); } -int fnmatch_icase(const char *pattern, const char *string, int flags) +int fspathncmp(const char *a, const char *b, size_t count) { - return wildmatch(pattern, string, - flags | (ignore_case ? WM_CASEFOLD : 0), - NULL); + return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count); } int git_fnmatch(const struct pathspec_item *item, @@ -91,13 +95,11 @@ int git_fnmatch(const struct pathspec_item *item, if (item->magic & PATHSPEC_GLOB) return wildmatch(pattern, string, WM_PATHNAME | - (item->magic & PATHSPEC_ICASE ? WM_CASEFOLD : 0), - NULL); + (item->magic & PATHSPEC_ICASE ? WM_CASEFOLD : 0)); else /* wildmatch has not learned no FNM_PATHNAME mode yet */ return wildmatch(pattern, string, - item->magic & PATHSPEC_ICASE ? WM_CASEFOLD : 0, - NULL); + item->magic & PATHSPEC_ICASE ? WM_CASEFOLD : 0); } static int fnmatch_icase_mem(const char *pattern, int patternlen, @@ -121,7 +123,7 @@ static int fnmatch_icase_mem(const char *pattern, int patternlen, if (ignore_case) flags |= WM_CASEFOLD; - match_status = wildmatch(use_pat, use_str, flags, NULL); + match_status = wildmatch(use_pat, use_str, flags); strbuf_release(&pat_buf); strbuf_release(&str_buf); @@ -147,7 +149,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; @@ -185,19 +188,24 @@ char *common_prefix(const struct pathspec *pathspec) return len ? xmemdupz(pathspec->items[0].match, len) : NULL; } -int fill_directory(struct dir_struct *dir, const struct pathspec *pathspec) +int fill_directory(struct dir_struct *dir, + struct index_state *istate, + 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, istate, prefix, prefix_len, pathspec); + + return prefix_len; } int within_depth(const char *name, int namelen, @@ -215,8 +223,89 @@ int within_depth(const char *name, int namelen, return 1; } -#define DO_MATCH_EXCLUDE 1 -#define DO_MATCH_DIRECTORY 2 +/* + * Read the contents of the blob with the given OID into a buffer. + * Append a trailing LF to the end if the last line doesn't have one. + * + * Returns: + * -1 when the OID is invalid or unknown or does not refer to a blob. + * 0 when the blob is empty. + * 1 along with { data, size } of the (possibly augmented) buffer + * when successful. + * + * Optionally updates the given oid_stat with the given OID (when valid). + */ +static int do_read_blob(const struct object_id *oid, struct oid_stat *oid_stat, + size_t *size_out, char **data_out) +{ + enum object_type type; + unsigned long sz; + char *data; + + *size_out = 0; + *data_out = NULL; + + data = read_object_file(oid, &type, &sz); + if (!data || type != OBJ_BLOB) { + free(data); + return -1; + } + + if (oid_stat) { + memset(&oid_stat->stat, 0, sizeof(oid_stat->stat)); + oidcpy(&oid_stat->oid, oid); + } + + if (sz == 0) { + free(data); + return 0; + } + + if (data[sz - 1] != '\n') { + data = xrealloc(data, st_add(sz, 1)); + data[sz++] = '\n'; + } + + *size_out = xsize_t(sz); + *data_out = data; + + return 1; +} + +#define DO_MATCH_EXCLUDE (1<<0) +#define DO_MATCH_DIRECTORY (1<<1) +#define DO_MATCH_SUBMODULE (1<<2) + +static int match_attrs(const struct index_state *istate, + const char *name, int namelen, + const struct pathspec_item *item) +{ + int i; + + git_check_attr(istate, 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? @@ -230,7 +319,8 @@ int within_depth(const char *name, int namelen, * * It returns 0 when there is no match. */ -static int match_pathspec_item(const struct pathspec_item *item, int prefix, +static int match_pathspec_item(const struct index_state *istate, + const struct pathspec_item *item, int prefix, const char *name, int namelen, unsigned flags) { /* name/namelen has prefix cut off by caller */ @@ -270,6 +360,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(istate, name, namelen, item)) + return 0; + /* If the match was just the prefix, we matched */ if (!*match) return MATCHED_RECURSIVELY; @@ -291,6 +384,32 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix, 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) { + /* name is a literal prefix of the pathspec */ + if ((namelen < matchlen) && + (match[namelen] == '/') && + !ps_strncmp(item, match, name, namelen)) + return MATCHED_RECURSIVELY; + + /* name" doesn't match up to the first wild character */ + if (item->nowildcard_len < item->len && + ps_strncmp(item, match, name, + item->nowildcard_len - prefix)) + return 0; + + /* + * Here is where we would perform a wildmatch to check if + * "name" can be matched as a directory (or a prefix) against + * the pathspec. Since wildmatch doesn't have this capability + * at the present we have to punt and say that it is a match, + * potentially returning a false positive + * The submodules themselves will be able to perform more + * accurate matching to determine if the pathspec matches. + */ + return MATCHED_RECURSIVELY; + } + return 0; } @@ -309,7 +428,8 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix, * pathspec did not match any names, which could indicate that the * user mistyped the nth pathspec. */ -static int do_match_pathspec(const struct pathspec *ps, +static int do_match_pathspec(const struct index_state *istate, + const struct pathspec *ps, const char *name, int namelen, int prefix, char *seen, unsigned flags) @@ -322,7 +442,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 || @@ -354,7 +475,7 @@ static int do_match_pathspec(const struct pathspec *ps, */ if (seen && ps->items[i].magic & PATHSPEC_EXCLUDE) seen[i] = MATCHED_FNMATCH; - how = match_pathspec_item(ps->items+i, prefix, name, + how = match_pathspec_item(istate, ps->items+i, prefix, name, namelen, flags); if (ps->recursive && (ps->magic & PATHSPEC_MAXDEPTH) && @@ -378,22 +499,39 @@ static int do_match_pathspec(const struct pathspec *ps, return retval; } -int match_pathspec(const struct pathspec *ps, +int match_pathspec(const struct index_state *istate, + const struct pathspec *ps, const char *name, int namelen, int prefix, char *seen, int is_dir) { int positive, negative; unsigned flags = is_dir ? DO_MATCH_DIRECTORY : 0; - positive = do_match_pathspec(ps, name, namelen, + positive = do_match_pathspec(istate, ps, name, namelen, prefix, seen, flags); if (!(ps->magic & PATHSPEC_EXCLUDE) || !positive) return positive; - negative = do_match_pathspec(ps, name, namelen, + negative = do_match_pathspec(istate, ps, name, namelen, prefix, seen, flags | DO_MATCH_EXCLUDE); return negative ? 0 : positive; } +/** + * Check if a submodule is a superset of the pathspec + */ +int submodule_path_match(const struct index_state *istate, + const struct pathspec *ps, + const char *submodule_name, + char *seen) +{ + int matched = do_match_pathspec(istate, ps, submodule_name, + strlen(submodule_name), + 0, seen, + DO_MATCH_DIRECTORY | + DO_MATCH_SUBMODULE); + return matched; +} + int report_path_error(const char *ps_matched, const struct pathspec *pathspec, const char *prefix) @@ -428,7 +566,7 @@ int report_path_error(const char *ps_matched, if (found_dup) continue; - error("pathspec '%s' did not match any file(s) known to git.", + error(_("pathspec '%s' did not match any file(s) known to git"), pathspec->items[num].original); errors++; } @@ -457,7 +595,7 @@ int no_wildcard(const char *string) void parse_exclude_pattern(const char **pattern, int *patternlen, - int *flags, + unsigned *flags, int *nowildcardlen) { const char *p = *pattern; @@ -498,7 +636,7 @@ void add_exclude(const char *string, const char *base, { struct exclude *x; int patternlen; - int flags; + unsigned flags; int nowildcardlen; parse_exclude_pattern(&string, &patternlen, &flags, &nowildcardlen); @@ -519,31 +657,21 @@ void add_exclude(const char *string, const char *base, x->el = el; } -static void *read_skip_worktree_file_from_index(const char *path, size_t *size, - struct sha1_stat *sha1_stat) +static int read_skip_worktree_file_from_index(const struct index_state *istate, + const char *path, + size_t *size_out, char **data_out, + struct oid_stat *oid_stat) { int pos, len; - unsigned long sz; - enum object_type type; - void *data; len = strlen(path); - pos = cache_name_pos(path, len); + pos = index_name_pos(istate, path, len); if (pos < 0) - return NULL; - if (!ce_skip_worktree(active_cache[pos])) - return NULL; - data = read_sha1_file(active_cache[pos]->sha1, &type, &sz); - if (!data || type != OBJ_BLOB) { - free(data); - return NULL; - } - *size = xsize_t(sz); - if (sha1_stat) { - memset(&sha1_stat->stat, 0, sizeof(sha1_stat->stat)); - hashcpy(sha1_stat->sha1, active_cache[pos]->sha1); - } - return data; + return -1; + if (!ce_skip_worktree(istate->cache[pos])) + return -1; + + return do_read_blob(&istate->cache[pos]->oid, oid_stat, size_out, data_out); } /* @@ -623,8 +751,8 @@ static struct untracked_cache_dir *lookup_untracked(struct untracked_cache *uc, FLEX_ALLOC_MEM(d, name, name, len); ALLOC_GROW(dir->dirs, dir->dirs_nr + 1, dir->dirs_alloc); - memmove(dir->dirs + first + 1, dir->dirs + first, - (dir->dirs_nr - first) * sizeof(*dir->dirs)); + MOVE_ARRAY(dir->dirs + first + 1, dir->dirs + first, + dir->dirs_nr - first); dir->dirs_nr++; dir->dirs[first] = d; return d; @@ -650,16 +778,29 @@ static void invalidate_directory(struct untracked_cache *uc, struct untracked_cache_dir *dir) { int i; - uc->dir_invalidated++; + + /* + * Invalidation increment here is just roughly correct. If + * untracked_nr or any of dirs[].recurse is non-zero, we + * should increment dir_invalidated too. But that's more + * expensive to do. + */ + if (dir->valid) + uc->dir_invalidated++; + dir->valid = 0; dir->untracked_nr = 0; for (i = 0; i < dir->dirs_nr; i++) dir->dirs[i]->recurse = 0; } +static int add_excludes_from_buffer(char *buf, size_t size, + const char *base, int baselen, + struct exclude_list *el); + /* * Given a file with name "fname", read it (either from disk, or from - * the index if "check_index" is non-zero), parse it and store the + * an index if 'istate' is non-null), parse it and store the * exclude rules in "el". * * If "ss" is not NULL, compute SHA-1 of the exclude file and fill @@ -667,38 +808,35 @@ static void invalidate_directory(struct untracked_cache *uc, * ss_valid is non-zero, "ss" must contain good value as input. */ static int add_excludes(const char *fname, const char *base, int baselen, - struct exclude_list *el, int check_index, - struct sha1_stat *sha1_stat) + struct exclude_list *el, struct index_state *istate, + struct oid_stat *oid_stat) { struct stat st; - int fd, i, lineno = 1; + int r; + int fd; size_t size = 0; - char *buf, *entry; + char *buf; fd = open(fname, O_RDONLY); if (fd < 0 || fstat(fd, &st) < 0) { - if (errno != ENOENT) - warn_on_inaccessible(fname); - if (0 <= fd) + if (fd < 0) + warn_on_fopen_errors(fname); + else close(fd); - if (!check_index || - (buf = read_skip_worktree_file_from_index(fname, &size, sha1_stat)) == NULL) + if (!istate) return -1; - if (size == 0) { - free(buf); - return 0; - } - if (buf[size-1] != '\n') { - buf = xrealloc(buf, st_add(size, 1)); - buf[size++] = '\n'; - } + r = read_skip_worktree_file_from_index(istate, fname, + &size, &buf, + oid_stat); + if (r != 1) + return r; } else { size = xsize_t(st.st_size); if (size == 0) { - if (sha1_stat) { - fill_stat_data(&sha1_stat->stat, &st); - hashcpy(sha1_stat->sha1, EMPTY_BLOB_SHA1_BIN); - sha1_stat->valid = 1; + if (oid_stat) { + fill_stat_data(&oid_stat->stat, &st); + oidcpy(&oid_stat->oid, the_hash_algo->empty_blob); + oid_stat->valid = 1; } close(fd); return 0; @@ -711,24 +849,37 @@ static int add_excludes(const char *fname, const char *base, int baselen, } buf[size++] = '\n'; close(fd); - if (sha1_stat) { + if (oid_stat) { int pos; - if (sha1_stat->valid && - !match_stat_data_racy(&the_index, &sha1_stat->stat, &st)) + if (oid_stat->valid && + !match_stat_data_racy(istate, &oid_stat->stat, &st)) ; /* no content change, ss->sha1 still good */ - else if (check_index && - (pos = cache_name_pos(fname, strlen(fname))) >= 0 && - !ce_stage(active_cache[pos]) && - ce_uptodate(active_cache[pos]) && - !would_convert_to_git(fname)) - hashcpy(sha1_stat->sha1, active_cache[pos]->sha1); + else if (istate && + (pos = index_name_pos(istate, fname, strlen(fname))) >= 0 && + !ce_stage(istate->cache[pos]) && + ce_uptodate(istate->cache[pos]) && + !would_convert_to_git(istate, fname)) + oidcpy(&oid_stat->oid, + &istate->cache[pos]->oid); else - hash_sha1_file(buf, size, "blob", sha1_stat->sha1); - fill_stat_data(&sha1_stat->stat, &st); - sha1_stat->valid = 1; + hash_object_file(buf, size, "blob", + &oid_stat->oid); + fill_stat_data(&oid_stat->stat, &st); + oid_stat->valid = 1; } } + add_excludes_from_buffer(buf, size, base, baselen, el); + return 0; +} + +static int add_excludes_from_buffer(char *buf, size_t size, + const char *base, int baselen, + struct exclude_list *el) +{ + int i, lineno = 1; + char *entry; + el->filebuf = buf; if (skip_utf8_bom(&buf, size)) @@ -752,9 +903,26 @@ static int add_excludes(const char *fname, const char *base, int baselen, int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen, struct exclude_list *el, - int check_index) + struct index_state *istate) { - return add_excludes(fname, base, baselen, el, check_index, NULL); + return add_excludes(fname, base, baselen, el, istate, NULL); +} + +int add_excludes_from_blob_to_list( + struct object_id *oid, + const char *base, int baselen, + struct exclude_list *el) +{ + char *buf; + size_t size; + int r; + + r = do_read_blob(oid, NULL, &size, &buf); + if (r != 1) + return r; + + add_excludes_from_buffer(buf, size, base, baselen, el); + return 0; } struct exclude_list *add_exclude_list(struct dir_struct *dir, @@ -775,7 +943,7 @@ struct exclude_list *add_exclude_list(struct dir_struct *dir, * Used to set up core.excludesfile and .git/info/exclude lists. */ static void add_excludes_from_file_1(struct dir_struct *dir, const char *fname, - struct sha1_stat *sha1_stat) + struct oid_stat *oid_stat) { struct exclude_list *el; /* @@ -786,8 +954,8 @@ static void add_excludes_from_file_1(struct dir_struct *dir, const char *fname, if (!dir->untracked) dir->unmanaged_exclude_files++; el = add_exclude_list(dir, EXC_FILE, fname); - if (add_excludes(fname, "", 0, el, 0, sha1_stat) < 0) - die("cannot use %s as an exclude file", fname); + if (add_excludes(fname, "", 0, el, NULL, oid_stat) < 0) + die(_("cannot use %s as an exclude file"), fname); } void add_excludes_from_file(struct dir_struct *dir, const char *fname) @@ -798,16 +966,16 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname) int match_basename(const char *basename, int basenamelen, const char *pattern, int prefix, int patternlen, - int flags) + unsigned flags) { if (prefix == patternlen) { if (patternlen == basenamelen && - !strncmp_icase(pattern, basename, basenamelen)) + !fspathncmp(pattern, basename, basenamelen)) return 1; } else if (flags & EXC_FLAG_ENDSWITH) { /* "*literal" matching against "fooliteral" */ if (patternlen - 1 <= basenamelen && - !strncmp_icase(pattern + 1, + !fspathncmp(pattern + 1, basename + basenamelen - (patternlen - 1), patternlen - 1)) return 1; @@ -823,7 +991,7 @@ int match_basename(const char *basename, int basenamelen, int match_pathname(const char *pathname, int pathlen, const char *base, int baselen, const char *pattern, int prefix, int patternlen, - int flags) + unsigned flags) { const char *name; int namelen; @@ -844,7 +1012,7 @@ int match_pathname(const char *pathname, int pathlen, */ if (pathlen < baselen + 1 || (baselen && pathname[baselen] != '/') || - strncmp_icase(pathname, base, baselen)) + fspathncmp(pathname, base, baselen)) return 0; namelen = baselen ? pathlen - baselen - 1 : pathlen; @@ -858,7 +1026,7 @@ int match_pathname(const char *pathname, int pathlen, if (prefix > namelen) return 0; - if (strncmp_icase(pattern, name, prefix)) + if (fspathncmp(pattern, name, prefix)) return 0; pattern += prefix; patternlen -= prefix; @@ -889,7 +1057,8 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname, int pathlen, const char *basename, int *dtype, - struct exclude_list *el) + struct exclude_list *el, + struct index_state *istate) { struct exclude *exc = NULL; /* undecided */ int i; @@ -904,7 +1073,7 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname, if (x->flags & EXC_FLAG_MUSTBEDIR) { if (*dtype == DT_UNKNOWN) - *dtype = get_dtype(NULL, pathname, pathlen); + *dtype = get_dtype(NULL, istate, pathname, pathlen); if (*dtype != DT_DIR) continue; } @@ -937,16 +1106,18 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname, */ int is_excluded_from_list(const char *pathname, int pathlen, const char *basename, int *dtype, - struct exclude_list *el) + struct exclude_list *el, struct index_state *istate) { struct exclude *exclude; - exclude = last_exclude_matching_from_list(pathname, pathlen, basename, dtype, el); + exclude = last_exclude_matching_from_list(pathname, pathlen, basename, + dtype, el, istate); if (exclude) return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1; return -1; /* undecided */ } static struct exclude *last_exclude_matching_from_lists(struct dir_struct *dir, + struct index_state *istate, const char *pathname, int pathlen, const char *basename, int *dtype_p) { @@ -958,7 +1129,7 @@ static struct exclude *last_exclude_matching_from_lists(struct dir_struct *dir, for (j = group->nr - 1; j >= 0; j--) { exclude = last_exclude_matching_from_list( pathname, pathlen, basename, dtype_p, - &group->el[j]); + &group->el[j], istate); if (exclude) return exclude; } @@ -970,7 +1141,9 @@ static struct exclude *last_exclude_matching_from_lists(struct dir_struct *dir, * Loads the per-directory exclude list for the substring of base * which has a char length of baselen. */ -static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) +static void prep_exclude(struct dir_struct *dir, + struct index_state *istate, + const char *base, int baselen) { struct exclude_list_group *group; struct exclude_list *el; @@ -1020,7 +1193,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) while (current < baselen) { const char *cp; - struct sha1_stat sha1_stat; + struct oid_stat oid_stat; stk = xcalloc(1, sizeof(*stk)); if (current < 0) { @@ -1049,6 +1222,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) int dt = DT_DIR; dir->basebuf.buf[stk->baselen - 1] = 0; dir->exclude = last_exclude_matching_from_lists(dir, + istate, dir->basebuf.buf, stk->baselen - 1, dir->basebuf.buf + current, &dt); dir->basebuf.buf[stk->baselen - 1] = '/'; @@ -1062,8 +1236,8 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) } /* Try to read per-directory file */ - hashclr(sha1_stat.sha1); - sha1_stat.valid = 0; + oidclr(&oid_stat.oid); + oid_stat.valid = 0; if (dir->exclude_per_dir && /* * If we know that no files have been added in @@ -1073,11 +1247,11 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) (!untracked || !untracked->valid || /* * .. and .gitignore does not exist before - * (i.e. null exclude_sha1). Then we can skip + * (i.e. null exclude_oid). Then we can skip * loading .gitignore, which would result in * ENOENT anyway. */ - !is_null_sha1(untracked->exclude_sha1))) { + !is_null_oid(&untracked->exclude_oid))) { /* * dir->basebuf gets reused by the traversal, but we * need fname to remain unchanged to ensure the src @@ -1090,8 +1264,8 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) strbuf_addbuf(&sb, &dir->basebuf); strbuf_addstr(&sb, dir->exclude_per_dir); el->src = strbuf_detach(&sb, NULL); - add_excludes(el->src, el->src, stk->baselen, el, 1, - untracked ? &sha1_stat : NULL); + add_excludes(el->src, el->src, stk->baselen, el, istate, + untracked ? &oid_stat : NULL); } /* * NEEDSWORK: when untracked cache is enabled, prep_exclude() @@ -1108,9 +1282,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) * order, though, if you do that. */ if (untracked && - hashcmp(sha1_stat.sha1, untracked->exclude_sha1)) { + oidcmp(&oid_stat.oid, &untracked->exclude_oid)) { invalidate_gitignore(dir->untracked, untracked); - hashcpy(untracked->exclude_sha1, sha1_stat.sha1); + oidcpy(&untracked->exclude_oid, &oid_stat.oid); } dir->exclude_stack = stk; current = stk->baselen; @@ -1125,19 +1299,20 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) * undecided. */ struct exclude *last_exclude_matching(struct dir_struct *dir, - const char *pathname, - int *dtype_p) + struct index_state *istate, + const char *pathname, + int *dtype_p) { int pathlen = strlen(pathname); const char *basename = strrchr(pathname, '/'); basename = (basename) ? basename+1 : pathname; - prep_exclude(dir, pathname, basename-pathname); + prep_exclude(dir, istate, pathname, basename-pathname); if (dir->exclude) return dir->exclude; - return last_exclude_matching_from_lists(dir, pathname, pathlen, + return last_exclude_matching_from_lists(dir, istate, pathname, pathlen, basename, dtype_p); } @@ -1146,10 +1321,11 @@ struct exclude *last_exclude_matching(struct dir_struct *dir, * scans all exclude lists to determine whether pathname is excluded. * Returns 1 if true, otherwise 0. */ -int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p) +int is_excluded(struct dir_struct *dir, struct index_state *istate, + const char *pathname, int *dtype_p) { struct exclude *exclude = - last_exclude_matching(dir, pathname, dtype_p); + last_exclude_matching(dir, istate, pathname, dtype_p); if (exclude) return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1; return 0; @@ -1164,18 +1340,22 @@ static struct dir_entry *dir_entry_new(const char *pathname, int len) return ent; } -static struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len) +static struct dir_entry *dir_add_name(struct dir_struct *dir, + struct index_state *istate, + const char *pathname, int len) { - if (cache_file_exists(pathname, len, ignore_case)) + if (index_file_exists(istate, pathname, len, ignore_case)) return NULL; ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc); return dir->entries[dir->nr++] = dir_entry_new(pathname, len); } -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, + struct index_state *istate, + const char *pathname, int len) { - if (!cache_name_is_other(pathname, len)) + if (!index_name_is_other(istate, pathname, len)) return NULL; ALLOC_GROW(dir->ignored, dir->ignored_nr+1, dir->ignored_alloc); @@ -1193,14 +1373,15 @@ enum exist_status { * the directory name; instead, use the case insensitive * directory hash. */ -static enum exist_status directory_exists_in_index_icase(const char *dirname, int len) +static enum exist_status directory_exists_in_index_icase(struct index_state *istate, + const char *dirname, int len) { struct cache_entry *ce; - if (cache_dir_exists(dirname, len)) + if (index_dir_exists(istate, dirname, len)) return index_directory; - ce = cache_file_exists(dirname, len, ignore_case); + ce = index_file_exists(istate, dirname, len, ignore_case); if (ce && S_ISGITLINK(ce->ce_mode)) return index_gitdir; @@ -1214,18 +1395,19 @@ static enum exist_status directory_exists_in_index_icase(const char *dirname, in * the files it contains) will sort with the '/' at the * end. */ -static enum exist_status directory_exists_in_index(const char *dirname, int len) +static enum exist_status directory_exists_in_index(struct index_state *istate, + const char *dirname, int len) { int pos; if (ignore_case) - return directory_exists_in_index_icase(dirname, len); + return directory_exists_in_index_icase(istate, dirname, len); - pos = cache_name_pos(dirname, len); + pos = index_name_pos(istate, dirname, len); if (pos < 0) pos = -pos-1; - while (pos < active_nr) { - const struct cache_entry *ce = active_cache[pos++]; + while (pos < istate->cache_nr) { + const struct cache_entry *ce = istate->cache[pos++]; unsigned char endchar; if (strncmp(ce->name, dirname, len)) @@ -1275,12 +1457,13 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len) * (c) otherwise, we recurse into it. */ static enum path_treatment treat_directory(struct dir_struct *dir, + struct index_state *istate, 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)) { + switch (directory_exists_in_index(istate, dirname, len-1)) { case index_directory: return path_recurse; @@ -1290,10 +1473,34 @@ static enum path_treatment treat_directory(struct dir_struct *dir, case index_nonexistent: if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES) break; + if (exclude && + (dir->flags & DIR_SHOW_IGNORED_TOO) && + (dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING)) { + + /* + * This is an excluded directory and we are + * showing ignored paths that match an exclude + * pattern. (e.g. show directory as ignored + * only if it matches an exclude pattern). + * This path will either be 'path_excluded` + * (if we are showing empty directories or if + * the directory is not empty), or will be + * 'path_none' (empty directory, and we are + * not showing empty directories). + */ + if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES)) + return path_excluded; + + if (read_directory_recursive(dir, istate, dirname, len, + untracked, 1, 1, pathspec) == path_excluded) + return path_excluded; + + return path_none; + } if (!(dir->flags & DIR_NO_GITLINKS)) { - unsigned char sha1[20]; - if (resolve_gitlink_ref(dirname, "HEAD", sha1) == 0) - return path_untracked; + struct object_id oid; + if (resolve_gitlink_ref(dirname, "HEAD", &oid) == 0) + return exclude ? path_excluded : path_untracked; } return path_recurse; } @@ -1305,8 +1512,13 @@ 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); + + /* + * If this is an excluded directory, then we only need to check if + * the directory contains any files. + */ + return read_directory_recursive(dir, istate, dirname, len, + untracked, 1, exclude, pathspec); } /* @@ -1314,24 +1526,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; } /* @@ -1345,29 +1567,44 @@ 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; } -static int get_index_dtype(const char *path, int len) +static int get_index_dtype(struct index_state *istate, + const char *path, int len) { int pos; const struct cache_entry *ce; - ce = cache_file_exists(path, len, 0); + ce = index_file_exists(istate, path, len, 0); if (ce) { if (!ce_uptodate(ce)) return DT_UNKNOWN; @@ -1381,12 +1618,12 @@ static int get_index_dtype(const char *path, int len) } /* Try to look it up as a directory */ - pos = cache_name_pos(path, len); + pos = index_name_pos(istate, path, len); if (pos >= 0) return DT_UNKNOWN; pos = -pos-1; - while (pos < active_nr) { - ce = active_cache[pos++]; + while (pos < istate->cache_nr) { + ce = istate->cache[pos++]; if (strncmp(ce->name, path, len)) break; if (ce->name[len] > '/') @@ -1400,14 +1637,15 @@ static int get_index_dtype(const char *path, int len) return DT_UNKNOWN; } -static int get_dtype(struct dirent *de, const char *path, int len) +static int get_dtype(struct dirent *de, struct index_state *istate, + const char *path, int len) { int dtype = de ? DTYPE(de) : DT_UNKNOWN; struct stat st; if (dtype != DT_UNKNOWN) return dtype; - dtype = get_index_dtype(path, len); + dtype = get_index_dtype(istate, path, len); if (dtype != DT_UNKNOWN) return dtype; if (lstat(path, &st)) @@ -1423,16 +1661,18 @@ static int get_dtype(struct dirent *de, const char *path, int len) static enum path_treatment treat_one_path(struct dir_struct *dir, struct untracked_cache_dir *untracked, + struct index_state *istate, struct strbuf *path, int baselen, - const struct path_simplify *simplify, + const struct pathspec *pathspec, int dtype, struct dirent *de) { int exclude; - int has_path_in_index = !!cache_file_exists(path->buf, path->len, ignore_case); + int has_path_in_index = !!index_file_exists(istate, path->buf, path->len, ignore_case); + enum path_treatment path_treatment; if (dtype == DT_UNKNOWN) - dtype = get_dtype(de, path->buf, path->len); + dtype = get_dtype(de, istate, path->buf, path->len); /* Always exclude indexed files */ if (dtype != DT_DIR && has_path_in_index) @@ -1459,10 +1699,10 @@ static enum path_treatment treat_one_path(struct dir_struct *dir, if ((dir->flags & DIR_COLLECT_KILLED_ONLY) && (dtype == DT_DIR) && !has_path_in_index && - (directory_exists_in_index(path->buf, path->len) == index_nonexistent)) + (directory_exists_in_index(istate, path->buf, path->len) == index_nonexistent)) return path_none; - exclude = is_excluded(dir, path->buf, &dtype); + exclude = is_excluded(dir, istate, path->buf, &dtype); /* * Excluded? If we don't explicitly want to show @@ -1476,8 +1716,23 @@ static enum path_treatment treat_one_path(struct dir_struct *dir, return path_none; case DT_DIR: strbuf_addch(path, '/'); - return treat_directory(dir, untracked, path->buf, path->len, - baselen, exclude, simplify); + path_treatment = treat_directory(dir, istate, untracked, + path->buf, path->len, + baselen, exclude, pathspec); + /* + * If 1) we only want to return directories that + * match an exclude pattern and 2) this directory does + * not match an exclude pattern but all of its + * contents are excluded, then indicate that we should + * recurse into this directory (instead of marking the + * directory itself as an ignored path). + */ + if (!exclude && + path_treatment == path_excluded && + (dir->flags & DIR_SHOW_IGNORED_TOO) && + (dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING)) + return path_recurse; + return path_treatment; case DT_REG: case DT_LNK: return exclude ? path_excluded : path_untracked; @@ -1487,9 +1742,10 @@ static enum path_treatment treat_one_path(struct dir_struct *dir, static enum path_treatment treat_path_fast(struct dir_struct *dir, struct untracked_cache_dir *untracked, struct cached_dir *cdir, + struct index_state *istate, struct strbuf *path, int baselen, - const struct path_simplify *simplify) + const struct pathspec *pathspec) { strbuf_setlen(path, baselen); if (!cdir->ucd) { @@ -1505,8 +1761,8 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir, * to its bottom. Verify again the same set of directories * with check_only set. */ - return read_directory_recursive(dir, path->buf, path->len, - cdir->ucd, 1, simplify); + return read_directory_recursive(dir, istate, path->buf, path->len, + cdir->ucd, 1, 0, pathspec); /* * We get path_recurse in the first run when * directory_exists_in_index() returns index_nonexistent. We @@ -1519,25 +1775,26 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir, static enum path_treatment treat_path(struct dir_struct *dir, struct untracked_cache_dir *untracked, struct cached_dir *cdir, + struct index_state *istate, 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); - if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git")) + return treat_path_fast(dir, untracked, cdir, istate, path, + baselen, pathspec); + if (is_dot_or_dotdot(de->d_name) || !fspathcmp(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, istate, path, baselen, pathspec, dtype, de); } static void add_untracked(struct untracked_cache_dir *dir, const char *name) @@ -1551,6 +1808,7 @@ static void add_untracked(struct untracked_cache_dir *dir, const char *name) static int valid_cached_dir(struct dir_struct *dir, struct untracked_cache_dir *untracked, + struct index_state *istate, struct strbuf *path, int check_only) { @@ -1559,23 +1817,24 @@ static int valid_cached_dir(struct dir_struct *dir, if (!untracked) return 0; - if (stat(path->len ? path->buf : ".", &st)) { - invalidate_directory(dir->untracked, untracked); - memset(&untracked->stat_data, 0, sizeof(untracked->stat_data)); - return 0; - } - if (!untracked->valid || - match_stat_data_racy(&the_index, &untracked->stat_data, &st)) { - if (untracked->valid) - invalidate_directory(dir->untracked, untracked); - fill_stat_data(&untracked->stat_data, &st); - return 0; + /* + * With fsmonitor, we can trust the untracked cache's valid field. + */ + refresh_fsmonitor(istate); + if (!(dir->untracked->use_fsmonitor && untracked->valid)) { + if (lstat(path->len ? path->buf : ".", &st)) { + memset(&untracked->stat_data, 0, sizeof(untracked->stat_data)); + return 0; + } + if (!untracked->valid || + match_stat_data_racy(istate, &untracked->stat_data, &st)) { + fill_stat_data(&untracked->stat_data, &st); + return 0; + } } - if (untracked->check_only != !!check_only) { - invalidate_directory(dir->untracked, untracked); + if (untracked->check_only != !!check_only) return 0; - } /* * prep_exclude will be called eventually on this directory, @@ -1586,10 +1845,10 @@ static int valid_cached_dir(struct dir_struct *dir, */ if (path->len && path->buf[path->len - 1] != '/') { strbuf_addch(path, '/'); - prep_exclude(dir, path->buf, path->len); + prep_exclude(dir, istate, path->buf, path->len); strbuf_setlen(path, path->len - 1); } else - prep_exclude(dir, path->buf, path->len); + prep_exclude(dir, istate, path->buf, path->len); /* hopefully prep_exclude() haven't invalidated this entry... */ return untracked->valid; @@ -1598,16 +1857,24 @@ static int valid_cached_dir(struct dir_struct *dir, static int open_cached_dir(struct cached_dir *cdir, struct dir_struct *dir, struct untracked_cache_dir *untracked, + struct index_state *istate, struct strbuf *path, int check_only) { + const char *c_path; + memset(cdir, 0, sizeof(*cdir)); cdir->untracked = untracked; - if (valid_cached_dir(dir, untracked, path, check_only)) + if (valid_cached_dir(dir, untracked, istate, path, check_only)) return 0; - cdir->fdir = opendir(path->len ? path->buf : "."); - if (dir->untracked) + c_path = path->len ? path->buf : "."; + cdir->fdir = opendir(c_path); + if (!cdir->fdir) + warning_errno(_("could not open directory '%s'"), c_path); + if (dir->untracked) { + invalidate_directory(dir->untracked, untracked); dir->untracked->dir_opened++; + } if (!cdir->fdir) return -1; return 0; @@ -1663,12 +1930,20 @@ static void close_cached_dir(struct cached_dir *cdir) * Also, we ignore the name ".git" (even if it is not a directory). * That likely will not change. * + * If 'stop_at_first_file' is specified, 'path_excluded' is returned + * to signal that a file was found. This is the least significant value that + * indicates that a file was encountered that does not depend on the order of + * whether an untracked or exluded path was encountered first. + * * Returns the most significant path_treatment value encountered in the scan. + * If 'stop_at_first_file' is specified, `path_excluded` is the most + * significant path_treatment value that will be returned. */ + 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) + struct index_state *istate, const char *base, int baselen, + struct untracked_cache_dir *untracked, int check_only, + int stop_at_first_file, const struct pathspec *pathspec) { struct cached_dir cdir; enum path_treatment state, subdir_state, dir_state = path_none; @@ -1676,7 +1951,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, strbuf_add(&path, base, baselen); - if (open_cached_dir(&cdir, dir, untracked, &path, check_only)) + if (open_cached_dir(&cdir, dir, untracked, istate, &path, check_only)) goto out; if (untracked) @@ -1684,25 +1959,52 @@ 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, istate, &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, istate, 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, istate, path.buf, + path.len, ud, + check_only, stop_at_first_file, pathspec); if (subdir_state > dir_state) dir_state = subdir_state; } if (check_only) { + if (stop_at_first_file) { + /* + * If stopping at first file, then + * signal that a file was found by + * returning `path_excluded`. This is + * to return a consistent value + * regardless of whether an ignored or + * excluded file happened to be + * encountered 1st. + * + * In current usage, the + * `stop_at_first_file` is passed when + * an ancestor directory has matched + * an exclude pattern, so any found + * files will be excluded. + */ + if (dir_state >= path_excluded) { + dir_state = path_excluded; + break; + } + } + /* abort early if maximum state has been reached */ if (dir_state == path_untracked) { if (cdir.fdir) @@ -1717,18 +2019,18 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, switch (state) { case path_excluded: if (dir->flags & DIR_SHOW_IGNORED) - dir_add_name(dir, path.buf, path.len); + dir_add_name(dir, istate, path.buf, path.len); else if ((dir->flags & DIR_SHOW_IGNORED_TOO) || ((dir->flags & DIR_COLLECT_IGNORED) && exclude_matches_pathspec(path.buf, path.len, - simplify))) - dir_add_ignored(dir, path.buf, path.len); + pathspec))) + dir_add_ignored(dir, istate, path.buf, path.len); break; case path_untracked: if (dir->flags & DIR_SHOW_IGNORED) break; - dir_add_name(dir, path.buf, path.len); + dir_add_name(dir, istate, path.buf, path.len); if (cdir.fdir) add_untracked(untracked, path.buf + baselen); break; @@ -1744,7 +2046,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; @@ -1752,36 +2054,18 @@ 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, + struct index_state *istate, const char *path, int len, - const struct path_simplify *simplify) + const struct pathspec *pathspec) { struct strbuf sb = STRBUF_INIT; int baselen, rc = 0; @@ -1805,9 +2089,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, istate, &sb, baselen, pathspec, DT_DIR, NULL) == path_none) break; /* do not recurse into it */ if (len <= baselen) { @@ -1895,8 +2179,13 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d const struct pathspec *pathspec) { struct untracked_cache_dir *root; + static int untracked_cache_disabled = -1; - if (!dir->untracked || getenv("GIT_DISABLE_UNTRACKED_CACHE")) + if (!dir->untracked) + return NULL; + if (untracked_cache_disabled < 0) + untracked_cache_disabled = git_env_bool("GIT_DISABLE_UNTRACKED_CACHE", 0); + if (untracked_cache_disabled) return NULL; /* @@ -1947,7 +2236,7 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d return NULL; if (!ident_in_untracked(dir->untracked)) { - warning(_("Untracked cache is disabled on this system or location.")); + warning(_("untracked cache is disabled on this system or location")); return NULL; } @@ -1959,13 +2248,13 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d /* Validate $GIT_DIR/info/exclude and core.excludesfile */ root = dir->untracked->root; - if (hashcmp(dir->ss_info_exclude.sha1, - dir->untracked->ss_info_exclude.sha1)) { + if (oidcmp(&dir->ss_info_exclude.oid, + &dir->untracked->ss_info_exclude.oid)) { invalidate_gitignore(dir->untracked, root); dir->untracked->ss_info_exclude = dir->ss_info_exclude; } - if (hashcmp(dir->ss_excludes_file.sha1, - dir->untracked->ss_excludes_file.sha1)) { + if (oidcmp(&dir->ss_excludes_file.oid, + &dir->untracked->ss_excludes_file.oid)) { invalidate_gitignore(dir->untracked, root); dir->untracked->ss_excludes_file = dir->ss_excludes_file; } @@ -1975,33 +2264,15 @@ 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, struct index_state *istate, + 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); + uint64_t start = getnanotime(); 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) /* @@ -2009,13 +2280,41 @@ 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, sizeof(struct dir_entry *), cmp_name); - qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name); + if (!len || treat_leading_path(dir, istate, path, len, pathspec)) + read_directory_recursive(dir, istate, path, len, untracked, 0, 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_AND_NULL(dir->entries[j]); + } else { + dir->entries[i++] = dir->entries[j]; + } + } + + dir->nr = i; + } + + trace_performance_since(start, "read directory %.*s", len, path); if (dir->untracked) { + static int force_untracked_cache = -1; static struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS); + + if (force_untracked_cache < 0) + force_untracked_cache = + git_env_bool("GIT_FORCE_UNTRACKED_CACHE", 0); trace_printf_key(&trace_untracked_stats, "node creation: %u\n" "gitignore invalidation: %u\n" @@ -2025,14 +2324,14 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru dir->untracked->gitignore_invalidated, dir->untracked->dir_invalidated, dir->untracked->dir_opened); - if (dir->untracked == the_index.untracked && + if (force_untracked_cache && + dir->untracked == istate->untracked && (dir->untracked->dir_opened || dir->untracked->gitignore_invalidated || dir->untracked->dir_invalidated)) - the_index.cache_changed |= UNTRACKED_CHANGED; - if (dir->untracked != the_index.untracked) { - free(dir->untracked); - dir->untracked = NULL; + istate->cache_changed |= UNTRACKED_CHANGED; + if (dir->untracked != istate->untracked) { + FREE_AND_NULL(dir->untracked); } } return dir->nr; @@ -2125,10 +2424,10 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up) 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]; + struct object_id submodule_head; 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; @@ -2202,11 +2501,9 @@ static GIT_PATH_FUNC(git_path_info_exclude, "info/exclude") void setup_standard_excludes(struct dir_struct *dir) { - const char *path; - dir->exclude_per_dir = ".gitignore"; - /* core.excludefile defaulting to $XDG_HOME/git/ignore */ + /* core.excludesfile defaulting to $XDG_CONFIG_HOME/git/ignore */ if (!excludes_file) excludes_file = xdg_config_home("ignore"); if (excludes_file && !access_or_warn(excludes_file, R_OK, 0)) @@ -2214,17 +2511,19 @@ void setup_standard_excludes(struct dir_struct *dir) dir->untracked ? &dir->ss_excludes_file : NULL); /* per repository user preference */ - path = git_path_info_exclude(); - if (!access_or_warn(path, R_OK, 0)) - add_excludes_from_file_1(dir, path, - dir->untracked ? &dir->ss_info_exclude : NULL); + if (startup_info->have_repository) { + const char *path = git_path_info_exclude(); + if (!access_or_warn(path, R_OK, 0)) + add_excludes_from_file_1(dir, path, + dir->untracked ? &dir->ss_info_exclude : NULL); + } } int remove_path(const char *name) { char *slash; - if (unlink(name) && errno != ENOENT && errno != ENOTDIR) + if (unlink(name) && !is_missing_file_error(errno)) return -1; slash = strrchr(name, '/'); @@ -2279,7 +2578,8 @@ struct ondisk_untracked_cache { char exclude_per_dir[FLEX_ARRAY]; }; -#define ouc_size(len) (offsetof(struct ondisk_untracked_cache, exclude_per_dir) + len + 1) +#define ouc_offset(x) offsetof(struct ondisk_untracked_cache, x) +#define ouc_size(len) (ouc_offset(exclude_per_dir) + len + 1) struct write_data { int index; /* number of written untracked_cache_dir */ @@ -2329,9 +2629,10 @@ static void write_one_dir(struct untracked_cache_dir *untracked, stat_data_to_disk(&stat_data, &untracked->stat_data); strbuf_add(&wd->sb_stat, &stat_data, sizeof(stat_data)); } - if (!is_null_sha1(untracked->exclude_sha1)) { + if (!is_null_oid(&untracked->exclude_oid)) { ewah_set(wd->sha1_valid, i); - strbuf_add(&wd->sb_sha1, untracked->exclude_sha1, 20); + strbuf_add(&wd->sb_sha1, untracked->exclude_oid.hash, + the_hash_algo->rawsz); } intlen = encode_varint(untracked->untracked_nr, intbuf); @@ -2366,17 +2667,16 @@ void write_untracked_extension(struct strbuf *out, struct untracked_cache *untra FLEX_ALLOC_MEM(ouc, exclude_per_dir, untracked->exclude_per_dir, len); stat_data_to_disk(&ouc->info_exclude_stat, &untracked->ss_info_exclude.stat); stat_data_to_disk(&ouc->excludes_file_stat, &untracked->ss_excludes_file.stat); - hashcpy(ouc->info_exclude_sha1, untracked->ss_info_exclude.sha1); - hashcpy(ouc->excludes_file_sha1, untracked->ss_excludes_file.sha1); + hashcpy(ouc->info_exclude_sha1, untracked->ss_info_exclude.oid.hash); + hashcpy(ouc->excludes_file_sha1, untracked->ss_excludes_file.oid.hash); ouc->dir_flags = htonl(untracked->dir_flags); varint_len = encode_varint(untracked->ident.len, varbuf); strbuf_add(out, varbuf, varint_len); - strbuf_add(out, untracked->ident.buf, untracked->ident.len); + strbuf_addbuf(out, &untracked->ident); strbuf_add(out, ouc, ouc_size(len)); - free(ouc); - ouc = NULL; + FREE_AND_NULL(ouc); if (!untracked->root) { varint_len = encode_varint(0, varbuf); @@ -2442,17 +2742,18 @@ struct read_data { const unsigned char *end; }; -static void stat_data_from_disk(struct stat_data *to, const struct stat_data *from) +static void stat_data_from_disk(struct stat_data *to, const unsigned char *data) { - to->sd_ctime.sec = get_be32(&from->sd_ctime.sec); - to->sd_ctime.nsec = get_be32(&from->sd_ctime.nsec); - to->sd_mtime.sec = get_be32(&from->sd_mtime.sec); - to->sd_mtime.nsec = get_be32(&from->sd_mtime.nsec); - to->sd_dev = get_be32(&from->sd_dev); - to->sd_ino = get_be32(&from->sd_ino); - to->sd_uid = get_be32(&from->sd_uid); - to->sd_gid = get_be32(&from->sd_gid); - to->sd_size = get_be32(&from->sd_size); + memcpy(to, data, sizeof(*to)); + to->sd_ctime.sec = ntohl(to->sd_ctime.sec); + to->sd_ctime.nsec = ntohl(to->sd_ctime.nsec); + to->sd_mtime.sec = ntohl(to->sd_mtime.sec); + to->sd_mtime.nsec = ntohl(to->sd_mtime.nsec); + to->sd_dev = ntohl(to->sd_dev); + to->sd_ino = ntohl(to->sd_ino); + to->sd_uid = ntohl(to->sd_uid); + to->sd_gid = ntohl(to->sd_gid); + to->sd_size = ntohl(to->sd_size); } static int read_one_dir(struct untracked_cache_dir **untracked_, @@ -2527,40 +2828,40 @@ static void read_stat(size_t pos, void *cb) rd->data = rd->end + 1; return; } - stat_data_from_disk(&ud->stat_data, (struct stat_data *)rd->data); + stat_data_from_disk(&ud->stat_data, rd->data); rd->data += sizeof(struct stat_data); ud->valid = 1; } -static void read_sha1(size_t pos, void *cb) +static void read_oid(size_t pos, void *cb) { struct read_data *rd = cb; struct untracked_cache_dir *ud = rd->ucd[pos]; - if (rd->data + 20 > rd->end) { + if (rd->data + the_hash_algo->rawsz > rd->end) { rd->data = rd->end + 1; return; } - hashcpy(ud->exclude_sha1, rd->data); - rd->data += 20; + hashcpy(ud->exclude_oid.hash, rd->data); + rd->data += the_hash_algo->rawsz; } -static void load_sha1_stat(struct sha1_stat *sha1_stat, - const struct stat_data *stat, - const unsigned char *sha1) +static void load_oid_stat(struct oid_stat *oid_stat, const unsigned char *data, + const unsigned char *sha1) { - stat_data_from_disk(&sha1_stat->stat, stat); - hashcpy(sha1_stat->sha1, sha1); - sha1_stat->valid = 1; + stat_data_from_disk(&oid_stat->stat, data); + hashcpy(oid_stat->oid.hash, sha1); + oid_stat->valid = 1; } struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz) { - const struct ondisk_untracked_cache *ouc; struct untracked_cache *uc; struct read_data rd; const unsigned char *next = data, *end = (const unsigned char *)data + sz; const char *ident; - int ident_len, len; + int ident_len; + ssize_t len; + const char *exclude_per_dir; if (sz <= 1 || end[-1] != '\0') return NULL; @@ -2572,21 +2873,23 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long ident = (const char *)next; next += ident_len; - ouc = (const struct ondisk_untracked_cache *)next; if (next + ouc_size(0) > end) return NULL; uc = xcalloc(1, sizeof(*uc)); strbuf_init(&uc->ident, ident_len); strbuf_add(&uc->ident, ident, ident_len); - load_sha1_stat(&uc->ss_info_exclude, &ouc->info_exclude_stat, - ouc->info_exclude_sha1); - load_sha1_stat(&uc->ss_excludes_file, &ouc->excludes_file_stat, - ouc->excludes_file_sha1); - uc->dir_flags = get_be32(&ouc->dir_flags); - uc->exclude_per_dir = xstrdup(ouc->exclude_per_dir); + load_oid_stat(&uc->ss_info_exclude, + next + ouc_offset(info_exclude_stat), + next + ouc_offset(info_exclude_sha1)); + load_oid_stat(&uc->ss_excludes_file, + next + ouc_offset(excludes_file_stat), + next + ouc_offset(excludes_file_sha1)); + uc->dir_flags = get_be32(next + ouc_offset(dir_flags)); + exclude_per_dir = (const char *)next + ouc_offset(exclude_per_dir); + uc->exclude_per_dir = xstrdup(exclude_per_dir); /* NUL after exclude_per_dir is covered by sizeof(*ouc) */ - next += ouc_size(strlen(ouc->exclude_per_dir)); + next += ouc_size(strlen(exclude_per_dir)); if (next >= end) goto done2; @@ -2623,7 +2926,7 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long ewah_each_bit(rd.check_only, set_check_only, &rd); rd.data = next + len; ewah_each_bit(rd.valid, read_stat, &rd); - ewah_each_bit(rd.sha1_valid, read_sha1, &rd); + ewah_each_bit(rd.sha1_valid, read_oid, &rd); next = rd.data; done: @@ -2694,10 +2997,12 @@ static int invalidate_one_component(struct untracked_cache *uc, } void untracked_cache_invalidate_path(struct index_state *istate, - const char *path) + const char *path, int safe_path) { if (!istate->untracked || !istate->untracked->root) return; + if (!safe_path && !verify_path(path, 0)) + return; invalidate_one_component(istate->untracked, istate->untracked->root, path, strlen(path)); } @@ -2705,11 +3010,111 @@ void untracked_cache_invalidate_path(struct index_state *istate, void untracked_cache_remove_from_index(struct index_state *istate, const char *path) { - untracked_cache_invalidate_path(istate, path); + untracked_cache_invalidate_path(istate, path, 1); } void untracked_cache_add_to_index(struct index_state *istate, const char *path) { - untracked_cache_invalidate_path(istate, path); + untracked_cache_invalidate_path(istate, path, 1); +} + +static void connect_wt_gitdir_in_nested(const char *sub_worktree, + const char *sub_gitdir) +{ + int i; + struct repository subrepo; + struct strbuf sub_wt = STRBUF_INIT; + struct strbuf sub_gd = STRBUF_INIT; + + const struct submodule *sub; + + /* If the submodule has no working tree, we can ignore it. */ + if (repo_init(&subrepo, sub_gitdir, sub_worktree)) + return; + + if (repo_read_index(&subrepo) < 0) + die(_("index file corrupt in repo %s"), subrepo.gitdir); + + for (i = 0; i < subrepo.index->cache_nr; i++) { + const struct cache_entry *ce = subrepo.index->cache[i]; + + if (!S_ISGITLINK(ce->ce_mode)) + continue; + + while (i + 1 < subrepo.index->cache_nr && + !strcmp(ce->name, subrepo.index->cache[i + 1]->name)) + /* + * Skip entries with the same name in different stages + * to make sure an entry is returned only once. + */ + i++; + + sub = submodule_from_path(&subrepo, &null_oid, ce->name); + if (!sub || !is_submodule_active(&subrepo, ce->name)) + /* .gitmodules broken or inactive sub */ + continue; + + strbuf_reset(&sub_wt); + strbuf_reset(&sub_gd); + strbuf_addf(&sub_wt, "%s/%s", sub_worktree, sub->path); + strbuf_addf(&sub_gd, "%s/modules/%s", sub_gitdir, sub->name); + + connect_work_tree_and_git_dir(sub_wt.buf, sub_gd.buf, 1); + } + strbuf_release(&sub_wt); + strbuf_release(&sub_gd); + repo_clear(&subrepo); +} + +void connect_work_tree_and_git_dir(const char *work_tree_, + const char *git_dir_, + int recurse_into_nested) +{ + 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); + + if (recurse_into_nested) + connect_wt_gitdir_in_nested(work_tree, git_dir); + + 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, 0); } |