diff options
Diffstat (limited to 'dir.c')
-rw-r--r-- | dir.c | 228 |
1 files changed, 208 insertions, 20 deletions
@@ -7,6 +7,16 @@ */ #include "cache.h" #include "dir.h" +#include "refs.h" + +struct path_simplify { + int len; + const char *path; +}; + +static int read_directory_recursive(struct dir_struct *dir, + const char *path, const char *base, int baselen, + int check_only, const struct path_simplify *simplify); int common_prefix(const char **pathspec) { @@ -282,15 +292,136 @@ struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int return ent; } -static int dir_exists(const char *dirname, int len) +enum exist_status { + index_nonexistent = 0, + index_directory, + index_gitdir, +}; + +/* + * The index sorts alphabetically by entry name, which + * means that a gitlink sorts as '\0' at the end, while + * a directory (which is defined not as an entry, but as + * the files it contains) will sort with the '/' at the + * end. + */ +static enum exist_status directory_exists_in_index(const char *dirname, int len) { int pos = cache_name_pos(dirname, len); - if (pos >= 0) + if (pos < 0) + pos = -pos-1; + while (pos < active_nr) { + struct cache_entry *ce = active_cache[pos++]; + unsigned char endchar; + + if (strncmp(ce->name, dirname, len)) + break; + endchar = ce->name[len]; + if (endchar > '/') + break; + if (endchar == '/') + return index_directory; + if (!endchar && S_ISDIRLNK(ntohl(ce->ce_mode))) + return index_gitdir; + } + return index_nonexistent; +} + +/* + * When we find a directory when traversing the filesystem, we + * have three distinct cases: + * + * - ignore it + * - see it as a directory + * - recurse into it + * + * and which one we choose depends on a combination of existing + * git index contents and the flags passed into the directory + * 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. + * + * Case 2: If we *already* have that directory name as a gitlink, + * we always continue to see it as a gitlink, regardless of whether + * there is an actual git directory there or not (it might not + * be checked out as a subproject!) + * + * Case 3: if we didn't have it in the index previously, we + * have a few sub-cases: + * + * (a) if "show_other_directories" is true, we show it as + * just a directory, unless "hide_empty_directories" is + * also true and the directory is empty, in which case + * we just ignore it entirely. + * (b) if it looks like a git directory, and we don't have + * 'no_dirlinks' set we treat it as a gitlink, and show it + * as a directory. + * (c) otherwise, we recurse into it. + */ +enum directory_treatment { + show_directory, + ignore_directory, + recurse_into_directory, +}; + +static enum directory_treatment treat_directory(struct dir_struct *dir, + const char *dirname, int len, + const struct path_simplify *simplify) +{ + /* The "len-1" is to strip the final '/' */ + switch (directory_exists_in_index(dirname, len-1)) { + case index_directory: + return recurse_into_directory; + + case index_gitdir: + if (dir->show_other_directories) + return ignore_directory; + return show_directory; + + case index_nonexistent: + if (dir->show_other_directories) + break; + if (!dir->no_dirlinks) { + unsigned char sha1[20]; + if (resolve_gitlink_ref(dirname, "HEAD", sha1) == 0) + return show_directory; + } + return recurse_into_directory; + } + + /* This is the "show_other_directories" case */ + if (!dir->hide_empty_directories) + return show_directory; + if (!read_directory_recursive(dir, dirname, dirname, len, 1, simplify)) + return ignore_directory; + return show_directory; +} + +/* + * 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. + */ +static int simplify_away(const char *path, int pathlen, const struct path_simplify *simplify) +{ + if (simplify) { + for (;;) { + const char *match = simplify->path; + int len = simplify->len; + + if (!match) + break; + if (len > pathlen) + len = pathlen; + if (!memcmp(path, match, len)) + return 0; + simplify++; + } return 1; - pos = -pos-1; - if (pos >= active_nr) /* can't */ - return 0; - return !strncmp(active_cache[pos]->name, dirname, len); + } + return 0; } /* @@ -302,7 +433,7 @@ static int dir_exists(const char *dirname, int len) * Also, we ignore the name ".git" (even if it is not a directory). * That likely will not change. */ -static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen, int check_only) +static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen, int check_only, const struct path_simplify *simplify) { DIR *fdir = opendir(path); int contents = 0; @@ -324,7 +455,12 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co !strcmp(de->d_name + 1, "git"))) continue; len = strlen(de->d_name); + /* Ignore overly long pathnames! */ + if (len + baselen + 8 > sizeof(fullname)) + continue; memcpy(fullname + baselen, de->d_name, len+1); + if (simplify_away(fullname, baselen + len, simplify)) + continue; if (excluded(dir, fullname) != dir->show_ignored) { if (!dir->show_ignored || DTYPE(de) != DT_DIR) { continue; @@ -346,19 +482,17 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co case DT_DIR: memcpy(fullname + baselen + len, "/", 2); len++; - if (dir->show_other_directories && - !dir_exists(fullname, baselen + len)) { - if (dir->hide_empty_directories && - !read_directory_recursive(dir, - fullname, fullname, - baselen + len, 1)) - continue; + switch (treat_directory(dir, fullname, baselen + len, simplify)) { + case show_directory: break; + case recurse_into_directory: + contents += read_directory_recursive(dir, + fullname, fullname, baselen + len, 0, simplify); + continue; + case ignore_directory: + continue; } - - contents += read_directory_recursive(dir, - fullname, fullname, baselen + len, 0); - continue; + break; case DT_REG: case DT_LNK: break; @@ -387,8 +521,61 @@ static int cmp_name(const void *p1, const void *p2) e2->name, e2->len); } -int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen) +/* + * Return the length of the "simple" part of a path match limiter. + */ +static int simple_length(const char *match) +{ + const char special[256] = { + [0] = 1, ['?'] = 1, + ['\\'] = 1, ['*'] = 1, + ['['] = 1 + }; + int len = -1; + + for (;;) { + unsigned char c = *match++; + len++; + if (special[c]) + return 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; + if (nr >= alloc) { + alloc = alloc_nr(alloc); + simplify = xrealloc(simplify, alloc * sizeof(*simplify)); + } + 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) { + if (simplify) + free(simplify); +} + +int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen, const char **pathspec) +{ + struct path_simplify *simplify = create_simplify(pathspec); + /* * Make sure to do the per-directory exclude for all the * directories leading up to our base. @@ -415,7 +602,8 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i } } - read_directory_recursive(dir, path, base, baselen, 0); + read_directory_recursive(dir, path, base, baselen, 0, simplify); + free_simplify(simplify); qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name); return dir->nr; } |