diff options
Diffstat (limited to 'dir.c')
-rw-r--r-- | dir.c | 665 |
1 files changed, 532 insertions, 133 deletions
@@ -5,19 +5,31 @@ * Copyright (C) Linus Torvalds, 2005-2006 * Junio Hamano, 2005-2006 */ -#include "cache.h" +#include "git-compat-util.h" +#include "abspath.h" #include "config.h" +#include "convert.h" #include "dir.h" -#include "object-store.h" -#include "attr.h" +#include "environment.h" +#include "gettext.h" +#include "name-hash.h" +#include "object-file.h" +#include "object-store-ll.h" +#include "path.h" #include "refs.h" #include "wildmatch.h" #include "pathspec.h" #include "utf8.h" #include "varint.h" #include "ewah/ewok.h" -#include "fsmonitor.h" +#include "fsmonitor-ll.h" +#include "read-cache-ll.h" +#include "setup.h" +#include "sparse-index.h" #include "submodule-config.h" +#include "symlinks.h" +#include "trace2.h" +#include "tree.h" /* * Tells read_directory_recursive how a file or directory should be treated. @@ -53,12 +65,6 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, int check_only, int stop_at_first_file, const struct pathspec *pathspec); static int resolve_dtype(int dtype, struct index_state *istate, const char *path, int len); - -void dir_init(struct dir_struct *dir) -{ - memset(dir, 0, sizeof(*dir)); -} - struct dirent *readdir_skip_dot_and_dotdot(DIR *dirp) { struct dirent *e; @@ -84,11 +90,21 @@ int fspathcmp(const char *a, const char *b) return ignore_case ? strcasecmp(a, b) : strcmp(a, b); } +int fspatheq(const char *a, const char *b) +{ + return !fspathcmp(a, b); +} + int fspathncmp(const char *a, const char *b, size_t count) { return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count); } +unsigned int fspathhash(const char *str) +{ + return ignore_case ? strihash(str) : strhash(str); +} + int git_fnmatch(const struct pathspec_item *item, const char *pattern, const char *string, int prefix) @@ -263,7 +279,7 @@ static int do_read_blob(const struct object_id *oid, struct oid_stat *oid_stat, *size_out = 0; *data_out = NULL; - data = read_object_file(oid, &type, &sz); + data = repo_read_object_file(the_repository, oid, &type, &sz); if (!data || type != OBJ_BLOB) { free(data); return -1; @@ -359,7 +375,7 @@ static int match_pathspec_item(struct index_state *istate, return 0; if (item->attr_match_nr && - !match_pathspec_attrs(istate, name, namelen, item)) + !match_pathspec_attrs(istate, name - prefix, namelen + prefix, item)) return 0; /* If the match was just the prefix, we matched */ @@ -651,10 +667,10 @@ void parse_path_pattern(const char **pattern, *patternlen = len; } -int pl_hashmap_cmp(const void *unused_cmp_data, +int pl_hashmap_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *a, const struct hashmap_entry *b, - const void *key) + const void *key UNUSED) { const struct pattern_entry *ee1 = container_of(a, struct pattern_entry, ent); @@ -665,9 +681,7 @@ int pl_hashmap_cmp(const void *unused_cmp_data, ? ee1->patternlen : ee2->patternlen; - if (ignore_case) - return strncasecmp(ee1->pattern, ee2->pattern, min_len); - return strncmp(ee1->pattern, ee2->pattern, min_len); + return fspathncmp(ee1->pattern, ee2->pattern, min_len); } static char *dup_and_filter_pattern(const char *pattern) @@ -723,13 +737,20 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern } if (given->patternlen < 2 || - *given->pattern == '*' || + *given->pattern != '/' || strstr(given->pattern, "**")) { /* Not a cone pattern. */ warning(_("unrecognized pattern: '%s'"), given->pattern); goto clear_hashmaps; } + if (!(given->flags & PATTERN_FLAG_MUSTBEDIR) && + strcmp(given->pattern, "/*")) { + /* Not a cone pattern. */ + warning(_("unrecognized pattern: '%s'"), given->pattern); + goto clear_hashmaps; + } + prev = given->pattern; cur = given->pattern + 1; next = given->pattern + 2; @@ -778,9 +799,7 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern translated->pattern = truncated; translated->patternlen = given->patternlen - 2; hashmap_entry_init(&translated->ent, - ignore_case ? - strihash(translated->pattern) : - strhash(translated->pattern)); + fspathhash(translated->pattern)); if (!hashmap_get_entry(&pl->recursive_hashmap, translated, ent, NULL)) { @@ -809,9 +828,7 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern translated->pattern = dup_and_filter_pattern(given->pattern); translated->patternlen = given->patternlen; hashmap_entry_init(&translated->ent, - ignore_case ? - strihash(translated->pattern) : - strhash(translated->pattern)); + fspathhash(translated->pattern)); hashmap_add(&pl->recursive_hashmap, &translated->ent); @@ -819,9 +836,7 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern /* 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); + goto clear_hashmaps; } return; @@ -841,10 +856,7 @@ static int hashmap_contains_path(struct hashmap *map, /* Check straight mapping */ p.pattern = pattern->buf; p.patternlen = pattern->len; - hashmap_entry_init(&p.ent, - ignore_case ? - strihash(p.pattern) : - strhash(p.pattern)); + hashmap_entry_init(&p.ent, fspathhash(p.pattern)); return !!hashmap_get_entry(map, &p, ent, NULL); } @@ -1118,7 +1130,7 @@ static int add_patterns(const char *fname, const char *base, int baselen, &istate->cache[pos]->oid); else hash_object_file(the_hash_algo, buf, size, - "blob", &oid_stat->oid); + OBJ_BLOB, &oid_stat->oid); fill_stat_data(&oid_stat->stat, &st); oid_stat->valid = 1; } @@ -1190,7 +1202,7 @@ struct pattern_list *add_pattern_list(struct dir_struct *dir, struct pattern_list *pl; struct exclude_list_group *group; - group = &dir->exclude_list_group[group_type]; + group = &dir->internal.exclude_list_group[group_type]; ALLOC_GROW(group->pl, group->nr + 1, group->alloc); pl = &group->pl[group->nr++]; memset(pl, 0, sizeof(*pl)); @@ -1211,7 +1223,7 @@ static void add_patterns_from_file_1(struct dir_struct *dir, const char *fname, * differently when dir->untracked is non-NULL. */ if (!dir->untracked) - dir->unmanaged_exclude_files++; + dir->internal.unmanaged_exclude_files++; pl = add_pattern_list(dir, EXC_FILE, fname); if (add_patterns(fname, "", 0, pl, NULL, 0, oid_stat) < 0) die(_("cannot use %s as an exclude file"), fname); @@ -1219,7 +1231,7 @@ static void add_patterns_from_file_1(struct dir_struct *dir, const char *fname, void add_patterns_from_file(struct dir_struct *dir, const char *fname) { - dir->unmanaged_exclude_files++; /* see validate_untracked_cache() */ + dir->internal.unmanaged_exclude_files++; /* see validate_untracked_cache() */ add_patterns_from_file_1(dir, fname, NULL); } @@ -1249,8 +1261,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, - unsigned flags) + const char *pattern, int prefix, int patternlen) { const char *name; int namelen; @@ -1352,8 +1363,7 @@ static struct path_pattern *last_matching_pattern_from_list(const char *pathname if (match_pathname(pathname, pathlen, pattern->base, pattern->baselen ? pattern->baselen - 1 : 0, - exclude, prefix, pattern->patternlen, - pattern->flags)) { + exclude, prefix, pattern->patternlen)) { res = pattern; break; } @@ -1376,7 +1386,7 @@ enum pattern_match_result path_matches_pattern_list( struct path_pattern *pattern; struct strbuf parent_pathname = STRBUF_INIT; int result = NOT_MATCHED; - const char *slash_pos; + size_t slash_pos; if (!pl->use_cone_patterns) { pattern = last_matching_pattern_from_list(pathname, pathlen, basename, @@ -1397,21 +1407,35 @@ enum pattern_match_result path_matches_pattern_list( strbuf_addch(&parent_pathname, '/'); strbuf_add(&parent_pathname, pathname, pathlen); + /* + * Directory entries are matched if and only if a file + * contained immediately within them is matched. For the + * case of a directory entry, modify the path to create + * a fake filename within this directory, allowing us to + * use the file-base matching logic in an equivalent way. + */ + if (parent_pathname.len > 0 && + parent_pathname.buf[parent_pathname.len - 1] == '/') { + slash_pos = parent_pathname.len - 1; + strbuf_add(&parent_pathname, "-", 1); + } else { + const char *slash_ptr = strrchr(parent_pathname.buf, '/'); + slash_pos = slash_ptr ? slash_ptr - parent_pathname.buf : 0; + } + 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) { + if (!slash_pos) { /* include every file in root */ result = MATCHED; goto done; } - strbuf_setlen(&parent_pathname, slash_pos - parent_pathname.buf); + strbuf_setlen(&parent_pathname, slash_pos); if (hashmap_contains_path(&pl->parent_hashmap, &parent_pathname)) { result = MATCHED; @@ -1428,6 +1452,76 @@ done: return result; } +int init_sparse_checkout_patterns(struct index_state *istate) +{ + if (!core_apply_sparse_checkout) + return 1; + if (istate->sparse_checkout_patterns) + return 0; + + CALLOC_ARRAY(istate->sparse_checkout_patterns, 1); + + if (get_sparse_checkout_patterns(istate->sparse_checkout_patterns) < 0) { + FREE_AND_NULL(istate->sparse_checkout_patterns); + return -1; + } + + return 0; +} + +static int path_in_sparse_checkout_1(const char *path, + struct index_state *istate, + int require_cone_mode) +{ + int dtype = DT_REG; + enum pattern_match_result match = UNDECIDED; + const char *end, *slash; + + /* + * We default to accepting a path if the path is empty, there are no + * patterns, or the patterns are of the wrong type. + */ + if (!*path || + init_sparse_checkout_patterns(istate) || + (require_cone_mode && + !istate->sparse_checkout_patterns->use_cone_patterns)) + return 1; + + /* + * If UNDECIDED, use the match from the parent dir (recursively), or + * fall back to NOT_MATCHED at the topmost level. Note that cone mode + * never returns UNDECIDED, so we will execute only one iteration in + * this case. + */ + for (end = path + strlen(path); + end > path && match == UNDECIDED; + end = slash) { + + for (slash = end - 1; slash > path && *slash != '/'; slash--) + ; /* do nothing */ + + match = path_matches_pattern_list(path, end - path, + slash > path ? slash + 1 : path, &dtype, + istate->sparse_checkout_patterns, istate); + + /* We are going to match the parent dir now */ + dtype = DT_DIR; + } + return match > 0; +} + +int path_in_sparse_checkout(const char *path, + struct index_state *istate) +{ + return path_in_sparse_checkout_1(path, istate, 0); +} + +int path_in_cone_mode_sparse_checkout(const char *path, + struct index_state *istate) +{ + return path_in_sparse_checkout_1(path, istate, 1); +} + static struct path_pattern *last_matching_pattern_from_lists( struct dir_struct *dir, struct index_state *istate, const char *pathname, int pathlen, @@ -1437,7 +1531,7 @@ static struct path_pattern *last_matching_pattern_from_lists( struct exclude_list_group *group; struct path_pattern *pattern; for (i = EXC_CMDL; i <= EXC_FILE; i++) { - group = &dir->exclude_list_group[i]; + group = &dir->internal.exclude_list_group[i]; for (j = group->nr - 1; j >= 0; j--) { pattern = last_matching_pattern_from_list( pathname, pathlen, basename, dtype_p, @@ -1463,20 +1557,20 @@ static void prep_exclude(struct dir_struct *dir, struct untracked_cache_dir *untracked; int current; - group = &dir->exclude_list_group[EXC_DIRS]; + group = &dir->internal.exclude_list_group[EXC_DIRS]; /* * Pop the exclude lists from the EXCL_DIRS exclude_list_group * which originate from directories not in the prefix of the * path being checked. */ - while ((stk = dir->exclude_stack) != NULL) { + while ((stk = dir->internal.exclude_stack) != NULL) { if (stk->baselen <= baselen && - !strncmp(dir->basebuf.buf, base, stk->baselen)) + !strncmp(dir->internal.basebuf.buf, base, stk->baselen)) break; - pl = &group->pl[dir->exclude_stack->exclude_ix]; - dir->exclude_stack = stk->prev; - dir->pattern = NULL; + pl = &group->pl[dir->internal.exclude_stack->exclude_ix]; + dir->internal.exclude_stack = stk->prev; + dir->internal.pattern = NULL; free((char *)pl->src); /* see strbuf_detach() below */ clear_pattern_list(pl); free(stk); @@ -1484,7 +1578,7 @@ static void prep_exclude(struct dir_struct *dir, } /* Skip traversing into sub directories if the parent is excluded */ - if (dir->pattern) + if (dir->internal.pattern) return; /* @@ -1492,12 +1586,12 @@ static void prep_exclude(struct dir_struct *dir, * memset(dir, 0, sizeof(*dir)) before use. Changing all of * them seems lots of work for little benefit. */ - if (!dir->basebuf.buf) - strbuf_init(&dir->basebuf, PATH_MAX); + if (!dir->internal.basebuf.buf) + strbuf_init(&dir->internal.basebuf, PATH_MAX); /* Read from the parent directories and push them down. */ current = stk ? stk->baselen : -1; - strbuf_setlen(&dir->basebuf, current < 0 ? 0 : current); + strbuf_setlen(&dir->internal.basebuf, current < 0 ? 0 : current); if (dir->untracked) untracked = stk ? stk->ucd : dir->untracked->root; else @@ -1517,32 +1611,33 @@ static void prep_exclude(struct dir_struct *dir, die("oops in prep_exclude"); cp++; untracked = - lookup_untracked(dir->untracked, untracked, + lookup_untracked(dir->untracked, + untracked, base + current, cp - base - current); } - stk->prev = dir->exclude_stack; + stk->prev = dir->internal.exclude_stack; stk->baselen = cp - base; stk->exclude_ix = group->nr; stk->ucd = untracked; pl = add_pattern_list(dir, EXC_DIRS, NULL); - strbuf_add(&dir->basebuf, base + current, stk->baselen - current); - assert(stk->baselen == dir->basebuf.len); + strbuf_add(&dir->internal.basebuf, base + current, stk->baselen - current); + assert(stk->baselen == dir->internal.basebuf.len); /* Abort if the directory is excluded */ if (stk->baselen) { int dt = DT_DIR; - dir->basebuf.buf[stk->baselen - 1] = 0; - dir->pattern = last_matching_pattern_from_lists(dir, + dir->internal.basebuf.buf[stk->baselen - 1] = 0; + dir->internal.pattern = last_matching_pattern_from_lists(dir, istate, - dir->basebuf.buf, stk->baselen - 1, - dir->basebuf.buf + current, &dt); - dir->basebuf.buf[stk->baselen - 1] = '/'; - if (dir->pattern && - dir->pattern->flags & PATTERN_FLAG_NEGATIVE) - dir->pattern = NULL; - if (dir->pattern) { - dir->exclude_stack = stk; + dir->internal.basebuf.buf, stk->baselen - 1, + dir->internal.basebuf.buf + current, &dt); + dir->internal.basebuf.buf[stk->baselen - 1] = '/'; + if (dir->internal.pattern && + dir->internal.pattern->flags & PATTERN_FLAG_NEGATIVE) + dir->internal.pattern = NULL; + if (dir->internal.pattern) { + dir->internal.exclude_stack = stk; return; } } @@ -1565,15 +1660,15 @@ static void prep_exclude(struct dir_struct *dir, */ !is_null_oid(&untracked->exclude_oid))) { /* - * dir->basebuf gets reused by the traversal, but we - * need fname to remain unchanged to ensure the src - * member of each struct path_pattern correctly + * dir->internal.basebuf gets reused by the traversal, + * but we need fname to remain unchanged to ensure the + * src member of each struct path_pattern correctly * back-references its source file. Other invocations * of add_pattern_list provide stable strings, so we * strbuf_detach() and free() here in the caller. */ struct strbuf sb = STRBUF_INIT; - strbuf_addbuf(&sb, &dir->basebuf); + strbuf_addbuf(&sb, &dir->internal.basebuf); strbuf_addstr(&sb, dir->exclude_per_dir); pl->src = strbuf_detach(&sb, NULL); add_patterns(pl->src, pl->src, stk->baselen, pl, istate, @@ -1599,10 +1694,10 @@ static void prep_exclude(struct dir_struct *dir, invalidate_gitignore(dir->untracked, untracked); oidcpy(&untracked->exclude_oid, &oid_stat.oid); } - dir->exclude_stack = stk; + dir->internal.exclude_stack = stk; current = stk->baselen; } - strbuf_setlen(&dir->basebuf, baselen); + strbuf_setlen(&dir->internal.basebuf, baselen); } /* @@ -1622,8 +1717,8 @@ struct path_pattern *last_matching_pattern(struct dir_struct *dir, prep_exclude(dir, istate, pathname, basename-pathname); - if (dir->pattern) - return dir->pattern; + if (dir->internal.pattern) + return dir->internal.pattern; return last_matching_pattern_from_lists(dir, istate, pathname, pathlen, basename, dtype_p); @@ -1660,7 +1755,7 @@ static struct dir_entry *dir_add_name(struct dir_struct *dir, if (index_file_exists(istate, pathname, len, ignore_case)) return NULL; - ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc); + ALLOC_GROW(dir->entries, dir->nr+1, dir->internal.alloc); return dir->entries[dir->nr++] = dir_entry_new(pathname, len); } @@ -1671,7 +1766,7 @@ struct dir_entry *dir_add_ignored(struct dir_struct *dir, if (!index_name_is_other(istate, pathname, len)) return NULL; - ALLOC_GROW(dir->ignored, dir->ignored_nr+1, dir->ignored_alloc); + ALLOC_GROW(dir->ignored, dir->ignored_nr+1, dir->internal.ignored_alloc); return dir->ignored[dir->ignored_nr++] = dir_entry_new(pathname, len); } @@ -1782,7 +1877,7 @@ static enum path_treatment treat_directory(struct dir_struct *dir, */ enum path_treatment state; int matches_how = 0; - int nested_repo = 0, check_only, stop_early; + int check_only, stop_early; int old_ignored_nr, old_untracked_nr; /* The "len-1" is to strip the final '/' */ enum exist_status status = directory_exists_in_index(istate, dirname, len-1); @@ -1814,16 +1909,37 @@ static enum path_treatment treat_directory(struct dir_struct *dir, if ((dir->flags & DIR_SKIP_NESTED_GIT) || !(dir->flags & DIR_NO_GITLINKS)) { + /* + * Determine if `dirname` is a nested repo by confirming that: + * 1) we are in a nonbare repository, and + * 2) `dirname` is not an immediate parent of `the_repository->gitdir`, + * which could occur if the git_dir or worktree location was + * manually configured by the user; see t2205 testcases 1-3 for + * examples where this matters + */ + int nested_repo; struct strbuf sb = STRBUF_INIT; strbuf_addstr(&sb, dirname); nested_repo = is_nonbare_repository_dir(&sb); + + if (nested_repo) { + char *real_dirname, *real_gitdir; + strbuf_addstr(&sb, ".git"); + real_dirname = real_pathdup(sb.buf, 1); + real_gitdir = real_pathdup(the_repository->gitdir, 1); + + nested_repo = !!strcmp(real_dirname, real_gitdir); + free(real_gitdir); + free(real_dirname); + } strbuf_release(&sb); - } - if (nested_repo) { - if ((dir->flags & DIR_SKIP_NESTED_GIT) || - (matches_how == MATCHED_RECURSIVELY_LEADING_PATHSPEC)) - return path_none; - return excluded ? path_excluded : path_untracked; + + if (nested_repo) { + if ((dir->flags & DIR_SKIP_NESTED_GIT) || + (matches_how == MATCHED_RECURSIVELY_LEADING_PATHSPEC)) + return path_none; + return excluded ? path_excluded : path_untracked; + } } if (!(dir->flags & DIR_SHOW_OTHER_DIRECTORIES)) { @@ -2062,7 +2178,8 @@ static int exclude_matches_pathspec(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]; @@ -2118,6 +2235,39 @@ static int get_index_dtype(struct index_state *istate, return DT_UNKNOWN; } +unsigned char get_dtype(struct dirent *e, struct strbuf *path, + int follow_symlink) +{ + struct stat st; + unsigned char dtype = DTYPE(e); + size_t base_path_len; + + if (dtype != DT_UNKNOWN && !(follow_symlink && dtype == DT_LNK)) + return dtype; + + /* + * d_type unknown or unfollowed symlink, try to fall back on [l]stat + * results. If [l]stat fails, explicitly set DT_UNKNOWN. + */ + base_path_len = path->len; + strbuf_addstr(path, e->d_name); + if ((follow_symlink && stat(path->buf, &st)) || + (!follow_symlink && lstat(path->buf, &st))) + goto cleanup; + + /* determine d_type from st_mode */ + if (S_ISREG(st.st_mode)) + dtype = DT_REG; + else if (S_ISDIR(st.st_mode)) + dtype = DT_DIR; + else if (S_ISLNK(st.st_mode)) + dtype = DT_LNK; + +cleanup: + strbuf_setlen(path, base_path_len); + return dtype; +} + static int resolve_dtype(int dtype, struct index_state *istate, const char *path, int len) { @@ -2466,7 +2616,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, if (open_cached_dir(&cdir, dir, untracked, istate, &path, check_only)) goto out; - dir->visited_directories++; + dir->internal.visited_directories++; if (untracked) untracked->check_only = !!check_only; @@ -2475,7 +2625,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, /* check how the file or directory should be treated */ state = treat_path(dir, untracked, &cdir, istate, &path, baselen, pathspec); - dir->visited_paths++; + dir->internal.visited_paths++; if (state > dir_state) dir_state = state; @@ -2483,7 +2633,8 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, /* recurse into subdir if instructed by treat_path */ if (state == path_recurse) { struct untracked_cache_dir *ud; - ud = lookup_untracked(dir->untracked, untracked, + ud = lookup_untracked(dir->untracked, + untracked, path.buf + baselen, path.len - baselen); subdir_state = @@ -2668,13 +2819,33 @@ static void set_untracked_ident(struct untracked_cache *uc) strbuf_addch(&uc->ident, 0); } -static void new_untracked_cache(struct index_state *istate) +static unsigned new_untracked_cache_flags(struct index_state *istate) +{ + struct repository *repo = istate->repo; + char *val; + + /* + * This logic is coordinated with the setting of these flags in + * wt-status.c#wt_status_collect_untracked(), and the evaluation + * of the config setting in commit.c#git_status_config() + */ + if (!repo_config_get_string(repo, "status.showuntrackedfiles", &val) && + !strcmp(val, "all")) + return 0; + + /* + * The default, if "all" is not set, is "normal" - leading us here. + * If the value is "none" then it really doesn't matter. + */ + return DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES; +} + +static void new_untracked_cache(struct index_state *istate, int flags) { struct untracked_cache *uc = xcalloc(1, sizeof(*uc)); strbuf_init(&uc->ident, 100); uc->exclude_per_dir = ".gitignore"; - /* should be the same flags used by git-status */ - uc->dir_flags = DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES; + uc->dir_flags = flags >= 0 ? flags : new_untracked_cache_flags(istate); set_untracked_ident(uc); istate->untracked = uc; istate->cache_changed |= UNTRACKED_CHANGED; @@ -2683,11 +2854,11 @@ static void new_untracked_cache(struct index_state *istate) void add_untracked_cache(struct index_state *istate) { if (!istate->untracked) { - new_untracked_cache(istate); + new_untracked_cache(istate, -1); } else { if (!ident_in_untracked(istate->untracked)) { free_untracked_cache(istate->untracked); - new_untracked_cache(istate); + new_untracked_cache(istate, -1); } } } @@ -2703,7 +2874,8 @@ void remove_untracked_cache(struct index_state *istate) static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *dir, int base_len, - const struct pathspec *pathspec) + const struct pathspec *pathspec, + struct index_state *istate) { struct untracked_cache_dir *root; static int untracked_cache_disabled = -1; @@ -2722,7 +2894,7 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d * condition also catches running setup_standard_excludes() * before setting dir->untracked! */ - if (dir->unmanaged_exclude_files) + if (dir->internal.unmanaged_exclude_files) return NULL; /* @@ -2734,17 +2906,9 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d if (base_len || (pathspec && pathspec->nr)) return NULL; - /* Different set of flags may produce different results */ - if (dir->flags != dir->untracked->dir_flags || - /* - * See treat_directory(), case index_nonexistent. Without - * this flag, we may need to also cache .git file content - * for the resolve_gitlink_ref() call, which we don't. - */ - !(dir->flags & DIR_SHOW_OTHER_DIRECTORIES) || - /* We don't support collecting ignore files */ - (dir->flags & (DIR_SHOW_IGNORED | DIR_SHOW_IGNORED_TOO | - DIR_COLLECT_IGNORED))) + /* We don't support collecting ignore files */ + if (dir->flags & (DIR_SHOW_IGNORED | DIR_SHOW_IGNORED_TOO | + DIR_COLLECT_IGNORED)) return NULL; /* @@ -2759,7 +2923,7 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d * EXC_CMDL is not considered in the cache. If people set it, * skip the cache. */ - if (dir->exclude_list_group[EXC_CMDL].nr) + if (dir->internal.exclude_list_group[EXC_CMDL].nr) return NULL; if (!ident_in_untracked(dir->untracked)) { @@ -2767,20 +2931,67 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d return NULL; } - if (!dir->untracked->root) + /* + * If the untracked structure we received does not have the same flags + * as requested in this run, we're going to need to either discard the + * existing structure (and potentially later recreate), or bypass the + * untracked cache mechanism for this run. + */ + if (dir->flags != dir->untracked->dir_flags) { + /* + * If the untracked structure we received does not have the same flags + * as configured, then we need to reset / create a new "untracked" + * structure to match the new config. + * + * Keeping the saved and used untracked cache consistent with the + * configuration provides an opportunity for frequent users of + * "git status -uall" to leverage the untracked cache by aligning their + * configuration - setting "status.showuntrackedfiles" to "all" or + * "normal" as appropriate. + * + * Previously using -uall (or setting "status.showuntrackedfiles" to + * "all") was incompatible with untracked cache and *consistently* + * caused surprisingly bad performance (with fscache and fsmonitor + * enabled) on Windows. + * + * IMPROVEMENT OPPORTUNITY: If we reworked the untracked cache storage + * to not be as bound up with the desired output in a given run, + * and instead iterated through and stored enough information to + * correctly serve both "modes", then users could get peak performance + * with or without '-uall' regardless of their + * "status.showuntrackedfiles" config. + */ + if (dir->untracked->dir_flags != new_untracked_cache_flags(istate)) { + free_untracked_cache(istate->untracked); + new_untracked_cache(istate, dir->flags); + dir->untracked = istate->untracked; + } + else { + /* + * Current untracked cache data is consistent with config, but not + * usable in this request/run; just bypass untracked cache. + */ + return NULL; + } + } + + if (!dir->untracked->root) { + /* Untracked cache existed but is not initialized; fix that */ FLEX_ALLOC_STR(dir->untracked->root, name, ""); + istate->cache_changed |= UNTRACKED_CHANGED; + } /* Validate $GIT_DIR/info/exclude and core.excludesfile */ root = dir->untracked->root; - if (!oideq(&dir->ss_info_exclude.oid, + if (!oideq(&dir->internal.ss_info_exclude.oid, &dir->untracked->ss_info_exclude.oid)) { invalidate_gitignore(dir->untracked, root); - dir->untracked->ss_info_exclude = dir->ss_info_exclude; + dir->untracked->ss_info_exclude = dir->internal.ss_info_exclude; } - if (!oideq(&dir->ss_excludes_file.oid, + if (!oideq(&dir->internal.ss_excludes_file.oid, &dir->untracked->ss_excludes_file.oid)) { invalidate_gitignore(dir->untracked, root); - dir->untracked->ss_excludes_file = dir->ss_excludes_file; + dir->untracked->ss_excludes_file = dir->internal.ss_excludes_file; } /* Make sure this directory is not dropped out at saving phase */ @@ -2806,9 +3017,9 @@ static void emit_traversal_statistics(struct dir_struct *dir, } trace2_data_intmax("read_directory", repo, - "directories-visited", dir->visited_directories); + "directories-visited", dir->internal.visited_directories); trace2_data_intmax("read_directory", repo, - "paths-visited", dir->visited_paths); + "paths-visited", dir->internal.visited_paths); if (!dir->untracked) return; @@ -2830,15 +3041,15 @@ int read_directory(struct dir_struct *dir, struct index_state *istate, struct untracked_cache_dir *untracked; trace2_region_enter("dir", "read_directory", istate->repo); - dir->visited_paths = 0; - dir->visited_directories = 0; + dir->internal.visited_paths = 0; + dir->internal.visited_directories = 0; if (has_symlink_leading_path(path, len)) { trace2_region_leave("dir", "read_directory", istate->repo); return dir->nr; } - untracked = validate_untracked_cache(dir, len, pathspec); + untracked = validate_untracked_cache(dir, len, pathspec, istate); if (!untracked) /* * make sure untracked cache code path is disabled, @@ -2858,7 +3069,9 @@ int read_directory(struct dir_struct *dir, struct index_state *istate, if (force_untracked_cache < 0) force_untracked_cache = - git_env_bool("GIT_FORCE_UNTRACKED_CACHE", 0); + git_env_bool("GIT_FORCE_UNTRACKED_CACHE", -1); + if (force_untracked_cache < 0) + force_untracked_cache = (istate->repo->settings.core_untracked_cache == UNTRACKED_CACHE_WRITE); if (force_untracked_cache && dir->untracked == istate->untracked && (dir->untracked->dir_opened || @@ -2959,6 +3172,129 @@ int is_empty_dir(const char *path) return ret; } +char *git_url_basename(const char *repo, int is_bundle, int is_bare) +{ + const char *end = repo + strlen(repo), *start, *ptr; + size_t len; + char *dir; + + /* + * Skip scheme. + */ + start = strstr(repo, "://"); + if (!start) + start = repo; + else + start += 3; + + /* + * Skip authentication data. The stripping does happen + * greedily, such that we strip up to the last '@' inside + * the host part. + */ + for (ptr = start; ptr < end && !is_dir_sep(*ptr); ptr++) { + if (*ptr == '@') + start = ptr + 1; + } + + /* + * Strip trailing spaces, slashes and /.git + */ + while (start < end && (is_dir_sep(end[-1]) || isspace(end[-1]))) + end--; + if (end - start > 5 && is_dir_sep(end[-5]) && + !strncmp(end - 4, ".git", 4)) { + end -= 5; + while (start < end && is_dir_sep(end[-1])) + end--; + } + + /* + * It should not be possible to overflow `ptrdiff_t` by passing in an + * insanely long URL, but GCC does not know that and will complain + * without this check. + */ + if (end - start < 0) + die(_("No directory name could be guessed.\n" + "Please specify a directory on the command line")); + + /* + * Strip trailing port number if we've got only a + * hostname (that is, there is no dir separator but a + * colon). This check is required such that we do not + * strip URI's like '/foo/bar:2222.git', which should + * result in a dir '2222' being guessed due to backwards + * compatibility. + */ + if (memchr(start, '/', end - start) == NULL + && memchr(start, ':', end - start) != NULL) { + ptr = end; + while (start < ptr && isdigit(ptr[-1]) && ptr[-1] != ':') + ptr--; + if (start < ptr && ptr[-1] == ':') + end = ptr - 1; + } + + /* + * Find last component. To remain backwards compatible we + * also regard colons as path separators, such that + * cloning a repository 'foo:bar.git' would result in a + * directory 'bar' being guessed. + */ + ptr = end; + while (start < ptr && !is_dir_sep(ptr[-1]) && ptr[-1] != ':') + ptr--; + start = ptr; + + /* + * Strip .{bundle,git}. + */ + len = end - start; + strip_suffix_mem(start, &len, is_bundle ? ".bundle" : ".git"); + + if (!len || (len == 1 && *start == '/')) + die(_("No directory name could be guessed.\n" + "Please specify a directory on the command line")); + + if (is_bare) + dir = xstrfmt("%.*s.git", (int)len, start); + else + dir = xstrndup(start, len); + /* + * Replace sequences of 'control' characters and whitespace + * with one ascii space, remove leading and trailing spaces. + */ + if (*dir) { + char *out = dir; + int prev_space = 1 /* strip leading whitespace */; + for (end = dir; *end; ++end) { + char ch = *end; + if ((unsigned char)ch < '\x20') + ch = '\x20'; + if (isspace(ch)) { + if (prev_space) + continue; + prev_space = 1; + } else + prev_space = 0; + *out++ = ch; + } + *out = '\0'; + if (out > dir && prev_space) + out[-1] = '\0'; + } + return dir; +} + +void strip_dir_trailing_slashes(char *dir) +{ + char *end = dir + strlen(dir); + + while (dir < end - 1 && is_dir_sep(end[-1])) + end--; + *end = '\0'; +} + static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up) { DIR *dir; @@ -2966,6 +3302,7 @@ 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); + int purge_original_cwd = (flag & REMOVE_DIR_PURGE_ORIGINAL_CWD); struct object_id submodule_head; if ((flag & REMOVE_DIR_KEEP_NESTED_GIT) && @@ -3021,9 +3358,14 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up) closedir(dir); strbuf_setlen(path, original_len); - if (!ret && !keep_toplevel && !kept_down) - ret = (!rmdir(path->buf) || errno == ENOENT) ? 0 : -1; - else if (kept_up) + if (!ret && !keep_toplevel && !kept_down) { + if (!purge_original_cwd && + startup_info->original_cwd && + !strcmp(startup_info->original_cwd, path->buf)) + ret = -1; /* Do not remove current working directory */ + else + ret = (!rmdir(path->buf) || errno == ENOENT) ? 0 : -1; + } else if (kept_up) /* * report the uplevel that it is not an error that we * did not rmdir() our directory. @@ -3048,14 +3390,14 @@ void setup_standard_excludes(struct dir_struct *dir) excludes_file = xdg_config_home("ignore"); if (excludes_file && !access_or_warn(excludes_file, R_OK, 0)) add_patterns_from_file_1(dir, excludes_file, - dir->untracked ? &dir->ss_excludes_file : NULL); + dir->untracked ? &dir->internal.ss_excludes_file : NULL); /* per repository user preference */ if (startup_info->have_repository) { const char *path = git_path_info_exclude(); if (!access_or_warn(path, R_OK, 0)) add_patterns_from_file_1(dir, path, - dir->untracked ? &dir->ss_info_exclude : NULL); + dir->untracked ? &dir->internal.ss_info_exclude : NULL); } } @@ -3089,6 +3431,9 @@ int remove_path(const char *name) slash = dirs + (slash - name); do { *slash = '\0'; + if (startup_info->original_cwd && + !strcmp(startup_info->original_cwd, dirs)) + break; } while (rmdir(dirs) == 0 && (slash = strrchr(dirs, '/'))); free(dirs); } @@ -3105,9 +3450,10 @@ void dir_clear(struct dir_struct *dir) struct exclude_list_group *group; struct pattern_list *pl; struct exclude_stack *stk; + struct dir_struct new = DIR_INIT; for (i = EXC_CMDL; i <= EXC_FILE; i++) { - group = &dir->exclude_list_group[i]; + group = &dir->internal.exclude_list_group[i]; for (j = 0; j < group->nr; j++) { pl = &group->pl[j]; if (i == EXC_DIRS) @@ -3124,15 +3470,15 @@ void dir_clear(struct dir_struct *dir) free(dir->ignored); free(dir->entries); - stk = dir->exclude_stack; + stk = dir->internal.exclude_stack; while (stk) { struct exclude_stack *prev = stk->prev; free(stk); stk = prev; } - strbuf_release(&dir->basebuf); + strbuf_release(&dir->internal.basebuf); - dir_init(dir); + memcpy(dir, &new, sizeof(*dir)); } struct ondisk_untracked_cache { @@ -3290,8 +3636,12 @@ static void free_untracked(struct untracked_cache_dir *ucd) void free_untracked_cache(struct untracked_cache *uc) { - if (uc) - free_untracked(uc->root); + if (!uc) + return; + + free(uc->exclude_per_dir_to_free); + strbuf_release(&uc->ident); + free_untracked(uc->root); free(uc); } @@ -3448,7 +3798,7 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long next + offset + hashsz); uc->dir_flags = get_be32(next + ouc_offset(dir_flags)); exclude_per_dir = (const char *)next + exclude_per_dir_offset; - uc->exclude_per_dir = xstrdup(exclude_per_dir); + uc->exclude_per_dir = uc->exclude_per_dir_to_free = xstrdup(exclude_per_dir); /* NUL after exclude_per_dir is covered by sizeof(*ouc) */ next += exclude_per_dir_offset + strlen(exclude_per_dir) + 1; if (next >= end) @@ -3568,6 +3918,26 @@ void untracked_cache_invalidate_path(struct index_state *istate, path, strlen(path)); } +void untracked_cache_invalidate_trimmed_path(struct index_state *istate, + const char *path, + int safe_path) +{ + size_t len = strlen(path); + + if (!len) + BUG("untracked_cache_invalidate_trimmed_path given zero length path"); + + if (path[len - 1] != '/') { + untracked_cache_invalidate_path(istate, path, safe_path); + } else { + struct strbuf tmp = STRBUF_INIT; + + strbuf_add(&tmp, path, len - 1); + untracked_cache_invalidate_path(istate, tmp.buf, safe_path); + strbuf_release(&tmp); + } +} + void untracked_cache_remove_from_index(struct index_state *istate, const char *path) { @@ -3621,7 +3991,7 @@ static void connect_wt_gitdir_in_nested(const char *sub_worktree, 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); + submodule_name_to_gitdir(&sub_gd, &subrepo, sub->name); connect_work_tree_and_git_dir(sub_wt.buf, sub_gd.buf, 1); } @@ -3681,3 +4051,32 @@ void relocate_gitdir(const char *path, const char *old_git_dir, const char *new_ connect_work_tree_and_git_dir(path, new_git_dir, 0); } + +int path_match_flags(const char *const str, const enum path_match_flags flags) +{ + const char *p = str; + + if (flags & PATH_MATCH_NATIVE && + flags & PATH_MATCH_XPLATFORM) + BUG("path_match_flags() must get one match kind, not multiple!"); + else if (!(flags & PATH_MATCH_KINDS_MASK)) + BUG("path_match_flags() must get at least one match kind!"); + + if (flags & PATH_MATCH_STARTS_WITH_DOT_SLASH && + flags & PATH_MATCH_STARTS_WITH_DOT_DOT_SLASH) + BUG("path_match_flags() must get one platform kind, not multiple!"); + else if (!(flags & PATH_MATCH_PLATFORM_MASK)) + BUG("path_match_flags() must get at least one platform kind!"); + + if (*p++ != '.') + return 0; + if (flags & PATH_MATCH_STARTS_WITH_DOT_DOT_SLASH && + *p++ != '.') + return 0; + + if (flags & PATH_MATCH_NATIVE) + return is_dir_sep(*p); + else if (flags & PATH_MATCH_XPLATFORM) + return is_xplatform_dir_sep(*p); + BUG("unreachable"); +} |