diff options
Diffstat (limited to 'dir.c')
-rw-r--r-- | dir.c | 252 |
1 files changed, 196 insertions, 56 deletions
@@ -194,12 +194,19 @@ static int match_one(const char *match, const char *name, int namelen) } /* - * Given a name and a list of pathspecs, see if the name matches - * any of the pathspecs. The caller is also interested in seeing - * all pathspec matches some names it calls this function with - * (otherwise the user could have mistyped the unmatched pathspec), - * and a mark is left in seen[] array for pathspec element that - * actually matched anything. + * Given a name and a list of pathspecs, returns the nature of the + * closest (i.e. most specific) match of the name to any of the + * pathspecs. + * + * The caller typically calls this multiple times with the same + * pathspec and seen[] array but with different name/namelen + * (e.g. entries from the index) and is interested in seeing if and + * how each pathspec matches all the names it calls this function + * with. A mark is left in the seen[] array for each pathspec element + * indicating the closest type of match that element achieved, so if + * seen[n] remains zero after multiple invocations, that means the nth + * pathspec did not match any names, which could indicate that the + * user mistyped the nth pathspec. */ int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen) @@ -269,12 +276,19 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix, } /* - * Given a name and a list of pathspecs, see if the name matches - * any of the pathspecs. The caller is also interested in seeing - * all pathspec matches some names it calls this function with - * (otherwise the user could have mistyped the unmatched pathspec), - * and a mark is left in seen[] array for pathspec element that - * actually matched anything. + * Given a name and a list of pathspecs, returns the nature of the + * closest (i.e. most specific) match of the name to any of the + * pathspecs. + * + * The caller typically calls this multiple times with the same + * pathspec and seen[] array but with different name/namelen + * (e.g. entries from the index) and is interested in seeing if and + * how each pathspec matches all the names it calls this function + * with. A mark is left in the seen[] array for each pathspec element + * indicating the closest type of match that element achieved, so if + * seen[n] remains zero after multiple invocations, that means the nth + * pathspec did not match any names, which could indicate that the + * user mistyped the nth pathspec. */ int match_pathspec_depth(const struct pathspec *ps, const char *name, int namelen, @@ -379,7 +393,7 @@ void parse_exclude_pattern(const char **pattern, } void add_exclude(const char *string, const char *base, - int baselen, struct exclude_list *el) + int baselen, struct exclude_list *el, int srcpos) { struct exclude *x; int patternlen; @@ -403,8 +417,10 @@ void add_exclude(const char *string, const char *base, x->base = base; x->baselen = baselen; x->flags = flags; + x->srcpos = srcpos; ALLOC_GROW(el->excludes, el->nr + 1, el->alloc); el->excludes[el->nr++] = x; + x->el = el; } static void *read_skip_worktree_file_from_index(const char *path, size_t *size) @@ -441,20 +457,21 @@ void clear_exclude_list(struct exclude_list *el) for (i = 0; i < el->nr; i++) free(el->excludes[i]); free(el->excludes); + free(el->filebuf); el->nr = 0; el->excludes = NULL; + el->filebuf = NULL; } int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen, - char **buf_p, struct exclude_list *el, int check_index) { struct stat st; - int fd, i; + int fd, i, lineno = 1; size_t size = 0; char *buf, *entry; @@ -492,25 +509,43 @@ int add_excludes_from_file_to_list(const char *fname, close(fd); } - if (buf_p) - *buf_p = buf; + el->filebuf = buf; entry = buf; for (i = 0; i < size; i++) { if (buf[i] == '\n') { if (entry != buf + i && entry[0] != '#') { buf[i - (i && buf[i-1] == '\r')] = 0; - add_exclude(entry, base, baselen, el); + add_exclude(entry, base, baselen, el, lineno); } + lineno++; entry = buf + i + 1; } } return 0; } +struct exclude_list *add_exclude_list(struct dir_struct *dir, + int group_type, const char *src) +{ + struct exclude_list *el; + struct exclude_list_group *group; + + group = &dir->exclude_list_group[group_type]; + ALLOC_GROW(group->el, group->nr + 1, group->alloc); + el = &group->el[group->nr++]; + memset(el, 0, sizeof(*el)); + el->src = src; + return el; +} + +/* + * Used to set up core.excludesfile and .git/info/exclude lists. + */ void add_excludes_from_file(struct dir_struct *dir, const char *fname) { - if (add_excludes_from_file_to_list(fname, "", 0, NULL, - &dir->exclude_list[EXC_FILE], 0) < 0) + struct exclude_list *el; + el = add_exclude_list(dir, EXC_FILE, fname); + if (add_excludes_from_file_to_list(fname, "", 0, el, 0) < 0) die("cannot use %s as an exclude file", fname); } @@ -520,6 +555,7 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname) */ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) { + struct exclude_list_group *group; struct exclude_list *el; struct exclude_stack *stk = NULL; int current; @@ -528,17 +564,21 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX)) return; /* too long a path -- ignore */ - /* Pop the directories that are not the prefix of the path being checked. */ - el = &dir->exclude_list[EXC_DIRS]; + group = &dir->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) { if (stk->baselen <= baselen && !strncmp(dir->basebuf, base, stk->baselen)) break; + el = &group->el[dir->exclude_stack->exclude_ix]; dir->exclude_stack = stk->prev; - while (stk->exclude_ix < el->nr) - free(el->excludes[--el->nr]); - free(stk->filebuf); + free((char *)el->src); /* see strdup() below */ + clear_exclude_list(el); free(stk); + group->nr--; } /* Read from the parent directories and push them down. */ @@ -559,13 +599,22 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) } stk->prev = dir->exclude_stack; stk->baselen = cp - base; - stk->exclude_ix = el->nr; memcpy(dir->basebuf + current, base + current, stk->baselen - current); strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir); + /* + * dir->basebuf gets reused by the traversal, but we + * need fname to remain unchanged to ensure the src + * member of each struct exclude correctly + * back-references its source file. Other invocations + * of add_exclude_list provide stable strings, so we + * strdup() and free() here in the caller. + */ + el = add_exclude_list(dir, EXC_DIRS, strdup(dir->basebuf)); + stk->exclude_ix = group->nr - 1; add_excludes_from_file_to_list(dir->basebuf, dir->basebuf, stk->baselen, - &stk->filebuf, el, 1); + el, 1); dir->exclude_stack = stk; current = stk->baselen; } @@ -636,7 +685,8 @@ int match_pathname(const char *pathname, int pathlen, } return wildmatch(pattern, name, - ignore_case ? FNM_CASEFOLD : 0) == 0; + WM_PATHNAME | (ignore_case ? WM_CASEFOLD : 0), + NULL) == 0; } /* @@ -712,18 +762,23 @@ static struct exclude *last_exclude_matching(struct dir_struct *dir, int *dtype_p) { int pathlen = strlen(pathname); - int st; + int i, j; + struct exclude_list_group *group; struct exclude *exclude; const char *basename = strrchr(pathname, '/'); basename = (basename) ? basename+1 : pathname; prep_exclude(dir, pathname, basename-pathname); - for (st = EXC_CMDL; st <= EXC_FILE; st++) { - exclude = last_exclude_matching_from_list( - pathname, pathlen, basename, dtype_p, - &dir->exclude_list[st]); - if (exclude) - return exclude; + + for (i = EXC_CMDL; i <= EXC_FILE; i++) { + group = &dir->exclude_list_group[i]; + for (j = group->nr - 1; j >= 0; j--) { + exclude = last_exclude_matching_from_list( + pathname, pathlen, basename, dtype_p, + &group->el[j]); + if (exclude) + return exclude; + } } return NULL; } @@ -842,7 +897,8 @@ static struct dir_entry *dir_entry_new(const char *pathname, int len) static struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len) { - if (cache_name_exists(pathname, len, ignore_case)) + if (!(dir->flags & DIR_SHOW_IGNORED) && + cache_name_exists(pathname, len, ignore_case)) return NULL; ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc); @@ -944,8 +1000,9 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len) * traversal routine. * * Case 1: If we *already* have entries in the index under that - * directory name, we always recurse into the directory to see - * all the files. + * directory name, we recurse into the directory to see all the files, + * unless the directory is excluded and we want to show ignored + * directories * * Case 2: If we *already* have that directory name as a gitlink, * we always continue to see it as a gitlink, regardless of whether @@ -959,6 +1016,9 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len) * just a directory, unless "hide_empty_directories" is * also true and the directory is empty, in which case * we just ignore it entirely. + * if we are looking for ignored directories, look if it + * contains only ignored files to decide if it must be shown as + * ignored or not. * (b) if it looks like a git directory, and we don't have * 'no_gitlinks' set we treat it as a gitlink, and show it * as a directory. @@ -971,12 +1031,15 @@ enum directory_treatment { }; static enum directory_treatment treat_directory(struct dir_struct *dir, - const char *dirname, int len, + const char *dirname, int len, int exclude, const struct path_simplify *simplify) { /* The "len-1" is to strip the final '/' */ switch (directory_exists_in_index(dirname, len-1)) { case index_directory: + if ((dir->flags & DIR_SHOW_OTHER_DIRECTORIES) && exclude) + break; + return recurse_into_directory; case index_gitdir: @@ -996,7 +1059,23 @@ static enum directory_treatment treat_directory(struct dir_struct *dir, } /* This is the "show_other_directories" case */ - if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES)) + + /* + * We are looking for ignored files and our directory is not ignored, + * check if it contains only ignored files + */ + if ((dir->flags & DIR_SHOW_IGNORED) && !exclude) { + int ignored; + dir->flags &= ~DIR_SHOW_IGNORED; + dir->flags |= DIR_HIDE_EMPTY_DIRECTORIES; + ignored = read_directory_recursive(dir, dirname, len, 1, simplify); + dir->flags &= ~DIR_HIDE_EMPTY_DIRECTORIES; + dir->flags |= DIR_SHOW_IGNORED; + + return ignored ? ignore_directory : show_directory; + } + if (!(dir->flags & DIR_SHOW_IGNORED) && + !(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES)) return show_directory; if (!read_directory_recursive(dir, dirname, len, 1, simplify)) return ignore_directory; @@ -1004,6 +1083,45 @@ static enum directory_treatment treat_directory(struct dir_struct *dir, } /* + * Decide what to do when we find a file while traversing the + * filesystem. Mostly two cases: + * + * 1. We are looking for ignored files + * (a) File is ignored, include it + * (b) File is in ignored path, include it + * (c) File is not ignored, exclude it + * + * 2. Other scenarios, include the file if not excluded + * + * Return 1 for exclude, 0 for include. + */ +static int treat_file(struct dir_struct *dir, struct strbuf *path, int exclude, int *dtype) +{ + struct path_exclude_check check; + int exclude_file = 0; + + if (exclude) + exclude_file = !(dir->flags & DIR_SHOW_IGNORED); + else if (dir->flags & DIR_SHOW_IGNORED) { + /* Always exclude indexed files */ + struct cache_entry *ce = index_name_exists(&the_index, + path->buf, path->len, ignore_case); + + if (ce) + return 1; + + path_exclude_check_init(&check, dir); + + if (!is_path_excluded(&check, path->buf, path->len, dtype)) + exclude_file = 1; + + path_exclude_check_clear(&check); + } + + return exclude_file; +} + +/* * This is an inexact early pruning of any recursive directory * reading - if the path cannot possibly be in the pathspec, * return true, and we'll skip it early. @@ -1141,27 +1259,14 @@ static enum path_treatment treat_one_path(struct dir_struct *dir, if (dtype == DT_UNKNOWN) dtype = get_dtype(de, path->buf, path->len); - /* - * Do we want to see just the ignored files? - * We still need to recurse into directories, - * even if we don't ignore them, since the - * directory may contain files that we do.. - */ - if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) { - if (dtype != DT_DIR) - return path_ignored; - } - switch (dtype) { default: return path_ignored; case DT_DIR: strbuf_addch(path, '/'); - switch (treat_directory(dir, path->buf, path->len, simplify)) { + + switch (treat_directory(dir, path->buf, path->len, exclude, simplify)) { case show_directory: - if (exclude != !!(dir->flags - & DIR_SHOW_IGNORED)) - return path_ignored; break; case recurse_into_directory: return path_recurse; @@ -1171,7 +1276,12 @@ static enum path_treatment treat_one_path(struct dir_struct *dir, break; case DT_REG: case DT_LNK: - break; + switch (treat_file(dir, path, exclude, &dtype)) { + case 1: + return path_ignored; + default: + break; + } } return path_handled; } @@ -1572,3 +1682,33 @@ int limit_pathspec_to_literal(void) flag = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0); return flag; } + +/* + * Frees memory within dir which was allocated for exclude lists and + * the exclude_stack. Does not free dir itself. + */ +void clear_directory(struct dir_struct *dir) +{ + int i, j; + struct exclude_list_group *group; + struct exclude_list *el; + struct exclude_stack *stk; + + for (i = EXC_CMDL; i <= EXC_FILE; i++) { + group = &dir->exclude_list_group[i]; + for (j = 0; j < group->nr; j++) { + el = &group->el[j]; + if (i == EXC_DIRS) + free((char *)el->src); + clear_exclude_list(el); + } + free(group->el); + } + + stk = dir->exclude_stack; + while (stk) { + struct exclude_stack *prev = stk->prev; + free(stk); + stk = prev; + } +} |