summaryrefslogtreecommitdiff
path: root/dir.c
diff options
context:
space:
mode:
Diffstat (limited to 'dir.c')
-rw-r--r--dir.c764
1 files changed, 567 insertions, 197 deletions
diff --git a/dir.c b/dir.c
index b3329f41b2..133c333df6 100644
--- a/dir.c
+++ b/dir.c
@@ -14,11 +14,11 @@ struct path_simplify {
const char *path;
};
-static int read_directory_recursive(struct dir_struct *dir,
- const char *path, const char *base, int baselen,
+static int read_directory_recursive(struct dir_struct *dir, const char *path, int len,
int check_only, const struct path_simplify *simplify);
+static int get_dtype(struct dirent *de, const char *path, int len);
-int common_prefix(const char **pathspec)
+static int common_prefix(const char **pathspec)
{
const char *path, *slash, *next;
int prefix;
@@ -51,8 +51,28 @@ int common_prefix(const char **pathspec)
return prefix;
}
+int fill_directory(struct dir_struct *dir, const char **pathspec)
+{
+ const char *path;
+ int len;
+
+ /*
+ * Calculate common prefix for the pathspec, and
+ * use that to optimize the directory walk
+ */
+ len = common_prefix(pathspec);
+ path = "";
+
+ if (len)
+ path = xmemdupz(*pathspec, len);
+
+ /* Read the directory and prune it */
+ read_directory(dir, path, len, pathspec);
+ return len;
+}
+
/*
- * Does 'match' matches the given name?
+ * Does 'match' match the given name?
* A match is found if
*
* (1) the 'match' string is leading directory of 'name', or
@@ -68,18 +88,31 @@ static int match_one(const char *match, const char *name, int namelen)
int matchlen;
/* If the match was just the prefix, we matched */
- matchlen = strlen(match);
- if (!matchlen)
+ if (!*match)
return MATCHED_RECURSIVELY;
+ for (;;) {
+ unsigned char c1 = *match;
+ unsigned char c2 = *name;
+ if (c1 == '\0' || is_glob_special(c1))
+ break;
+ if (c1 != c2)
+ return 0;
+ match++;
+ name++;
+ namelen--;
+ }
+
+
/*
* If we don't match the matchstring exactly,
* we need to match by fnmatch
*/
+ matchlen = strlen(match);
if (strncmp(match, name, matchlen))
return !fnmatch(match, name, 0) ? MATCHED_FNMATCH : 0;
- if (!name[matchlen])
+ if (namelen == matchlen)
return MATCHED_EXACTLY;
if (match[matchlen-1] == '/' || name[matchlen] == '/')
return MATCHED_RECURSIVELY;
@@ -94,50 +127,108 @@ static int match_one(const char *match, const char *name, int namelen)
* and a mark is left in seen[] array for pathspec element that
* actually matched anything.
*/
-int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen)
+int match_pathspec(const char **pathspec, const char *name, int namelen,
+ int prefix, char *seen)
{
- int retval;
- const char *match;
+ int i, retval = 0;
+
+ if (!pathspec)
+ return 1;
name += prefix;
namelen -= prefix;
- for (retval = 0; (match = *pathspec++) != NULL; seen++) {
+ for (i = 0; pathspec[i] != NULL; i++) {
int how;
- if (retval && *seen == MATCHED_EXACTLY)
+ const char *match = pathspec[i] + prefix;
+ if (seen && seen[i] == MATCHED_EXACTLY)
continue;
- match += prefix;
how = match_one(match, name, namelen);
if (how) {
if (retval < how)
retval = how;
- if (*seen < how)
- *seen = how;
+ if (seen && seen[i] < how)
+ seen[i] = how;
}
}
return retval;
}
+static int no_wildcard(const char *string)
+{
+ return string[strcspn(string, "*?[{\\")] == '\0';
+}
+
void add_exclude(const char *string, const char *base,
int baselen, struct exclude_list *which)
{
- struct exclude *x = xmalloc(sizeof (*x));
-
- x->pattern = string;
+ struct exclude *x;
+ size_t len;
+ int to_exclude = 1;
+ int flags = 0;
+
+ if (*string == '!') {
+ to_exclude = 0;
+ string++;
+ }
+ len = strlen(string);
+ if (len && string[len - 1] == '/') {
+ char *s;
+ x = xmalloc(sizeof(*x) + len);
+ s = (char *)(x+1);
+ memcpy(s, string, len - 1);
+ s[len - 1] = '\0';
+ string = s;
+ x->pattern = s;
+ flags = EXC_FLAG_MUSTBEDIR;
+ } else {
+ x = xmalloc(sizeof(*x));
+ x->pattern = string;
+ }
+ x->to_exclude = to_exclude;
+ x->patternlen = strlen(string);
x->base = base;
x->baselen = baselen;
- if (which->nr == which->alloc) {
- which->alloc = alloc_nr(which->alloc);
- which->excludes = xrealloc(which->excludes,
- which->alloc * sizeof(x));
- }
+ x->flags = flags;
+ if (!strchr(string, '/'))
+ x->flags |= EXC_FLAG_NODIR;
+ if (no_wildcard(string))
+ x->flags |= EXC_FLAG_NOWILDCARD;
+ if (*string == '*' && no_wildcard(string+1))
+ x->flags |= EXC_FLAG_ENDSWITH;
+ ALLOC_GROW(which->excludes, which->nr + 1, which->alloc);
which->excludes[which->nr++] = x;
}
-static int add_excludes_from_file_1(const char *fname,
- const char *base,
- int baselen,
- struct exclude_list *which)
+static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
+{
+ int pos, len;
+ unsigned long sz;
+ enum object_type type;
+ void *data;
+ struct index_state *istate = &the_index;
+
+ len = strlen(path);
+ pos = index_name_pos(istate, path, len);
+ if (pos < 0)
+ return NULL;
+ if (!ce_skip_worktree(istate->cache[pos]))
+ return NULL;
+ data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz);
+ if (!data || type != OBJ_BLOB) {
+ free(data);
+ return NULL;
+ }
+ *size = xsize_t(sz);
+ return data;
+}
+
+int add_excludes_from_file_to_list(const char *fname,
+ const char *base,
+ int baselen,
+ char **buf_p,
+ struct exclude_list *which,
+ int check_index)
{
struct stat st;
int fd, i;
@@ -145,19 +236,39 @@ static int add_excludes_from_file_1(const char *fname,
char *buf, *entry;
fd = open(fname, O_RDONLY);
- if (fd < 0 || fstat(fd, &st) < 0)
- goto err;
- size = xsize_t(st.st_size);
- if (size == 0) {
+ if (fd < 0 || fstat(fd, &st) < 0) {
+ if (0 <= fd)
+ close(fd);
+ if (!check_index ||
+ (buf = read_skip_worktree_file_from_index(fname, &size)) == NULL)
+ return -1;
+ if (size == 0) {
+ free(buf);
+ return 0;
+ }
+ if (buf[size-1] != '\n') {
+ buf = xrealloc(buf, size+1);
+ buf[size++] = '\n';
+ }
+ }
+ else {
+ size = xsize_t(st.st_size);
+ if (size == 0) {
+ close(fd);
+ return 0;
+ }
+ buf = xmalloc(size+1);
+ if (read_in_full(fd, buf, size) != size) {
+ free(buf);
+ close(fd);
+ return -1;
+ }
+ buf[size++] = '\n';
close(fd);
- return 0;
}
- buf = xmalloc(size+1);
- if (read_in_full(fd, buf, size) != size)
- goto err;
- close(fd);
- buf[size++] = '\n';
+ if (buf_p)
+ *buf_p = buf;
entry = buf;
for (i = 0; i < size; i++) {
if (buf[i] == '\n') {
@@ -169,48 +280,75 @@ static int add_excludes_from_file_1(const char *fname,
}
}
return 0;
-
- err:
- if (0 <= fd)
- close(fd);
- return -1;
}
void add_excludes_from_file(struct dir_struct *dir, const char *fname)
{
- if (add_excludes_from_file_1(fname, "", 0,
- &dir->exclude_list[EXC_FILE]) < 0)
+ if (add_excludes_from_file_to_list(fname, "", 0, NULL,
+ &dir->exclude_list[EXC_FILE], 0) < 0)
die("cannot use %s as an exclude file", fname);
}
-int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen)
+static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
{
- char exclude_file[PATH_MAX];
- struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
- int current_nr = el->nr;
-
- if (dir->exclude_per_dir) {
- memcpy(exclude_file, base, baselen);
- strcpy(exclude_file + baselen, dir->exclude_per_dir);
- add_excludes_from_file_1(exclude_file, base, baselen, el);
+ struct exclude_list *el;
+ struct exclude_stack *stk = NULL;
+ int current;
+
+ if ((!dir->exclude_per_dir) ||
+ (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX))
+ return; /* too long a path -- ignore */
+
+ /* Pop the ones that are not the prefix of the path being checked. */
+ el = &dir->exclude_list[EXC_DIRS];
+ while ((stk = dir->exclude_stack) != NULL) {
+ if (stk->baselen <= baselen &&
+ !strncmp(dir->basebuf, base, stk->baselen))
+ break;
+ dir->exclude_stack = stk->prev;
+ while (stk->exclude_ix < el->nr)
+ free(el->excludes[--el->nr]);
+ free(stk->filebuf);
+ free(stk);
}
- return current_nr;
-}
-void pop_exclude_per_directory(struct dir_struct *dir, int stk)
-{
- struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
+ /* Read from the parent directories and push them down. */
+ current = stk ? stk->baselen : -1;
+ while (current < baselen) {
+ struct exclude_stack *stk = xcalloc(1, sizeof(*stk));
+ const char *cp;
- while (stk < el->nr)
- free(el->excludes[--el->nr]);
+ if (current < 0) {
+ cp = base;
+ current = 0;
+ }
+ else {
+ cp = strchr(base + current + 1, '/');
+ if (!cp)
+ die("oops in prep_exclude");
+ cp++;
+ }
+ 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);
+ add_excludes_from_file_to_list(dir->basebuf,
+ dir->basebuf, stk->baselen,
+ &stk->filebuf, el, 1);
+ dir->exclude_stack = stk;
+ current = stk->baselen;
+ }
+ dir->basebuf[baselen] = '\0';
}
-/* Scan the list and let the last match determines the fate.
+/* Scan the list and let the last match determine the fate.
* Return 1 for exclude, 0 for include and -1 for undecided.
*/
-static int excluded_1(const char *pathname,
- int pathlen,
- struct exclude_list *el)
+int excluded_from_list(const char *pathname,
+ int pathlen, const char *basename, int *dtype,
+ struct exclude_list *el)
{
int i;
@@ -218,19 +356,34 @@ static int excluded_1(const char *pathname,
for (i = el->nr - 1; 0 <= i; i--) {
struct exclude *x = el->excludes[i];
const char *exclude = x->pattern;
- int to_exclude = 1;
+ int to_exclude = x->to_exclude;
- if (*exclude == '!') {
- to_exclude = 0;
- exclude++;
+ if (x->flags & EXC_FLAG_MUSTBEDIR) {
+ if (!dtype) {
+ if (!prefixcmp(pathname, exclude))
+ return to_exclude;
+ else
+ continue;
+ }
+ if (*dtype == DT_UNKNOWN)
+ *dtype = get_dtype(NULL, pathname, pathlen);
+ if (*dtype != DT_DIR)
+ continue;
}
- if (!strchr(exclude, '/')) {
+ if (x->flags & EXC_FLAG_NODIR) {
/* match basename */
- const char *basename = strrchr(pathname, '/');
- basename = (basename) ? basename+1 : pathname;
- if (fnmatch(exclude, basename, 0) == 0)
- return to_exclude;
+ if (x->flags & EXC_FLAG_NOWILDCARD) {
+ if (!strcmp(exclude, basename))
+ return to_exclude;
+ } else if (x->flags & EXC_FLAG_ENDSWITH) {
+ if (x->patternlen - 1 <= pathlen &&
+ !strcmp(exclude + 1, pathname + pathlen - x->patternlen + 1))
+ return to_exclude;
+ } else {
+ if (fnmatch(exclude, basename, 0) == 0)
+ return to_exclude;
+ }
}
else {
/* match with FNM_PATHNAME:
@@ -246,22 +399,31 @@ static int excluded_1(const char *pathname,
strncmp(pathname, x->base, baselen))
continue;
- if (fnmatch(exclude, pathname+baselen,
- FNM_PATHNAME) == 0)
- return to_exclude;
+ if (x->flags & EXC_FLAG_NOWILDCARD) {
+ if (!strcmp(exclude, pathname + baselen))
+ return to_exclude;
+ } else {
+ if (fnmatch(exclude, pathname+baselen,
+ FNM_PATHNAME) == 0)
+ return to_exclude;
+ }
}
}
}
return -1; /* undecided */
}
-int excluded(struct dir_struct *dir, const char *pathname)
+int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
{
int pathlen = strlen(pathname);
int st;
+ const char *basename = strrchr(pathname, '/');
+ basename = (basename) ? basename+1 : pathname;
+ prep_exclude(dir, pathname, basename-pathname);
for (st = EXC_CMDL; st <= EXC_FILE; st++) {
- switch (excluded_1(pathname, pathlen, &dir->exclude_list[st])) {
+ switch (excluded_from_list(pathname, pathlen, basename,
+ dtype_p, &dir->exclude_list[st])) {
case 0:
return 0;
case 1:
@@ -271,7 +433,8 @@ int excluded(struct dir_struct *dir, const char *pathname)
return 0;
}
-static struct dir_entry *dir_entry_new(const char *pathname, int len) {
+static struct dir_entry *dir_entry_new(const char *pathname, int len)
+{
struct dir_entry *ent;
ent = xmalloc(sizeof(*ent) + len + 1);
@@ -281,18 +444,18 @@ static struct dir_entry *dir_entry_new(const char *pathname, int len) {
return ent;
}
-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, const char *pathname, int len)
{
- if (cache_name_pos(pathname, len) >= 0)
+ if (cache_name_exists(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)
+static struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len)
{
- if (cache_name_pos(pathname, len) >= 0)
+ if (!cache_name_is_other(pathname, len))
return NULL;
ALLOC_GROW(dir->ignored, dir->ignored_nr+1, dir->ignored_alloc);
@@ -328,7 +491,7 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
break;
if (endchar == '/')
return index_directory;
- if (!endchar && S_ISGITLINK(ntohl(ce->ce_mode)))
+ if (!endchar && S_ISGITLINK(ce->ce_mode))
return index_gitdir;
}
return index_nonexistent;
@@ -383,14 +546,14 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,
return recurse_into_directory;
case index_gitdir:
- if (dir->show_other_directories)
+ if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
return ignore_directory;
return show_directory;
case index_nonexistent:
- if (dir->show_other_directories)
+ if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
break;
- if (!dir->no_gitlinks) {
+ if (!(dir->flags & DIR_NO_GITLINKS)) {
unsigned char sha1[20];
if (resolve_gitlink_ref(dirname, "HEAD", sha1) == 0)
return show_directory;
@@ -399,9 +562,9 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,
}
/* This is the "show_other_directories" case */
- if (!dir->hide_empty_directories)
+ if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
return show_directory;
- if (!read_directory_recursive(dir, dirname, dirname, len, 1, simplify))
+ if (!read_directory_recursive(dir, dirname, len, 1, simplify))
return ignore_directory;
return show_directory;
}
@@ -443,6 +606,151 @@ static int in_pathspec(const char *path, int len, const struct path_simplify *si
return 0;
}
+static int get_index_dtype(const char *path, int len)
+{
+ int pos;
+ struct cache_entry *ce;
+
+ ce = cache_name_exists(path, len, 0);
+ if (ce) {
+ if (!ce_uptodate(ce))
+ return DT_UNKNOWN;
+ if (S_ISGITLINK(ce->ce_mode))
+ return DT_DIR;
+ /*
+ * Nobody actually cares about the
+ * difference between DT_LNK and DT_REG
+ */
+ return DT_REG;
+ }
+
+ /* Try to look it up as a directory */
+ pos = cache_name_pos(path, len);
+ if (pos >= 0)
+ return DT_UNKNOWN;
+ pos = -pos-1;
+ while (pos < active_nr) {
+ ce = active_cache[pos++];
+ if (strncmp(ce->name, path, len))
+ break;
+ if (ce->name[len] > '/')
+ break;
+ if (ce->name[len] < '/')
+ continue;
+ if (!ce_uptodate(ce))
+ break; /* continue? */
+ return DT_DIR;
+ }
+ return DT_UNKNOWN;
+}
+
+static int get_dtype(struct dirent *de, 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);
+ if (dtype != DT_UNKNOWN)
+ return dtype;
+ if (lstat(path, &st))
+ return dtype;
+ if (S_ISREG(st.st_mode))
+ return DT_REG;
+ if (S_ISDIR(st.st_mode))
+ return DT_DIR;
+ if (S_ISLNK(st.st_mode))
+ return DT_LNK;
+ return dtype;
+}
+
+enum path_treatment {
+ path_ignored,
+ path_handled,
+ path_recurse,
+};
+
+static enum path_treatment treat_one_path(struct dir_struct *dir,
+ char *path, int *len,
+ const struct path_simplify *simplify,
+ int dtype, struct dirent *de)
+{
+ int exclude = excluded(dir, path, &dtype);
+ if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
+ && in_pathspec(path, *len, simplify))
+ dir_add_ignored(dir, path, *len);
+
+ /*
+ * Excluded? If we don't explicitly want to show
+ * ignored files, ignore it
+ */
+ if (exclude && !(dir->flags & DIR_SHOW_IGNORED))
+ return path_ignored;
+
+ if (dtype == DT_UNKNOWN)
+ dtype = get_dtype(de, 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:
+ memcpy(path + *len, "/", 2);
+ (*len)++;
+ switch (treat_directory(dir, path, *len, simplify)) {
+ case show_directory:
+ if (exclude != !!(dir->flags
+ & DIR_SHOW_IGNORED))
+ return path_ignored;
+ break;
+ case recurse_into_directory:
+ return path_recurse;
+ case ignore_directory:
+ return path_ignored;
+ }
+ break;
+ case DT_REG:
+ case DT_LNK:
+ break;
+ }
+ return path_handled;
+}
+
+static enum path_treatment treat_path(struct dir_struct *dir,
+ struct dirent *de,
+ char *path, int path_max,
+ int baselen,
+ const struct path_simplify *simplify,
+ int *len)
+{
+ int dtype;
+
+ if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
+ return path_ignored;
+ *len = strlen(de->d_name);
+ /* Ignore overly long pathnames! */
+ if (*len + baselen + 8 > path_max)
+ return path_ignored;
+ memcpy(path + baselen, de->d_name, *len + 1);
+ *len += baselen;
+ if (simplify_away(path, *len, simplify))
+ return path_ignored;
+
+ dtype = DTYPE(de);
+ return treat_one_path(dir, path, len, simplify, dtype, de);
+}
+
/*
* Read a directory tree. We currently ignore anything but
* directories, regular files and symlinks. That's because git
@@ -452,88 +760,40 @@ static int in_pathspec(const char *path, int len, const struct path_simplify *si
* 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, const struct path_simplify *simplify)
+static int read_directory_recursive(struct dir_struct *dir,
+ const char *base, int baselen,
+ int check_only,
+ const struct path_simplify *simplify)
{
- DIR *fdir = opendir(path);
+ DIR *fdir = opendir(*base ? base : ".");
int contents = 0;
if (fdir) {
- int exclude_stk;
struct dirent *de;
- char fullname[PATH_MAX + 1];
- memcpy(fullname, base, baselen);
-
- exclude_stk = push_exclude_per_directory(dir, base, baselen);
+ char path[PATH_MAX + 1];
+ memcpy(path, base, baselen);
while ((de = readdir(fdir)) != NULL) {
int len;
- int exclude;
-
- if ((de->d_name[0] == '.') &&
- (de->d_name[1] == 0 ||
- !strcmp(de->d_name + 1, ".") ||
- !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))
+ switch (treat_path(dir, de, path, sizeof(path),
+ baselen, simplify, &len)) {
+ case path_recurse:
+ contents += read_directory_recursive
+ (dir, path, len, 0, simplify);
continue;
-
- exclude = excluded(dir, fullname);
- if (exclude && dir->collect_ignored
- && in_pathspec(fullname, baselen + len, simplify))
- dir_add_ignored(dir, fullname, baselen + len);
- if (exclude != dir->show_ignored) {
- if (!dir->show_ignored || DTYPE(de) != DT_DIR) {
- continue;
- }
- }
-
- switch (DTYPE(de)) {
- struct stat st;
- default:
+ case path_ignored:
continue;
- case DT_UNKNOWN:
- if (lstat(fullname, &st))
- continue;
- if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
- break;
- if (!S_ISDIR(st.st_mode))
- continue;
- /* fallthrough */
- case DT_DIR:
- memcpy(fullname + baselen + len, "/", 2);
- len++;
- switch (treat_directory(dir, fullname, baselen + len, simplify)) {
- case show_directory:
- if (exclude != dir->show_ignored)
- continue;
- break;
- case recurse_into_directory:
- contents += read_directory_recursive(dir,
- fullname, fullname, baselen + len, 0, simplify);
- continue;
- case ignore_directory:
- continue;
- }
- break;
- case DT_REG:
- case DT_LNK:
+ case path_handled:
break;
}
contents++;
if (check_only)
goto exit_early;
else
- dir_add_name(dir, fullname, baselen + len);
+ dir_add_name(dir, path, len);
}
exit_early:
closedir(fdir);
-
- pop_exclude_per_directory(dir, exclude_stk);
}
return contents;
@@ -553,17 +813,12 @@ static int cmp_name(const void *p1, const void *p2)
*/
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])
+ if (c == '\0' || is_glob_special(c))
return len;
}
}
@@ -595,71 +850,88 @@ static struct path_simplify *create_simplify(const char **pathspec)
static void free_simplify(struct path_simplify *simplify)
{
- if (simplify)
- free(simplify);
+ free(simplify);
}
-int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen, const char **pathspec)
+static int treat_leading_path(struct dir_struct *dir,
+ const char *path, int len,
+ const struct path_simplify *simplify)
{
- struct path_simplify *simplify = create_simplify(pathspec);
+ char pathbuf[PATH_MAX];
+ int baselen, blen;
+ const char *cp;
- /*
- * Make sure to do the per-directory exclude for all the
- * directories leading up to our base.
- */
- if (baselen) {
- if (dir->exclude_per_dir) {
- char *p, *pp = xmalloc(baselen+1);
- memcpy(pp, base, baselen+1);
- p = pp;
- while (1) {
- char save = *p;
- *p = 0;
- push_exclude_per_directory(dir, pp, p-pp);
- *p++ = save;
- if (!save)
- break;
- p = strchr(p, '/');
- if (p)
- p++;
- else
- p = pp + baselen;
- }
- free(pp);
- }
+ while (len && path[len - 1] == '/')
+ len--;
+ if (!len)
+ return 1;
+ baselen = 0;
+ while (1) {
+ cp = path + baselen + !!baselen;
+ cp = memchr(cp, '/', path + len - cp);
+ if (!cp)
+ baselen = len;
+ else
+ baselen = cp - path;
+ memcpy(pathbuf, path, baselen);
+ pathbuf[baselen] = '\0';
+ if (!is_directory(pathbuf))
+ return 0;
+ if (simplify_away(pathbuf, baselen, simplify))
+ return 0;
+ blen = baselen;
+ if (treat_one_path(dir, pathbuf, &blen, simplify,
+ DT_DIR, NULL) == path_ignored)
+ return 0; /* do not recurse into it */
+ if (len <= baselen)
+ return 1; /* finished checking */
}
+}
+
+int read_directory(struct dir_struct *dir, const char *path, int len, const char **pathspec)
+{
+ struct path_simplify *simplify;
- read_directory_recursive(dir, path, base, baselen, 0, simplify);
+ if (has_symlink_leading_path(path, len))
+ return dir->nr;
+
+ simplify = create_simplify(pathspec);
+ if (!len || treat_leading_path(dir, path, len, simplify))
+ read_directory_recursive(dir, path, len, 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);
return dir->nr;
}
-int
-file_exists(const char *f)
+int file_exists(const char *f)
{
- struct stat sb;
- return stat(f, &sb) == 0;
+ struct stat sb;
+ return lstat(f, &sb) == 0;
}
/*
* get_relative_cwd() gets the prefix of the current working directory
* relative to 'dir'. If we are not inside 'dir', it returns NULL.
- * As a convenience, it also returns NULL if 'dir' is already NULL.
+ *
+ * As a convenience, it also returns NULL if 'dir' is already NULL. The
+ * reason for this behaviour is that it is natural for functions returning
+ * directory names to return NULL to say "this directory does not exist"
+ * or "this directory is invalid". These cases are usually handled the
+ * same as if the cwd is not inside 'dir' at all, so get_relative_cwd()
+ * returns NULL for both of them.
+ *
+ * Most notably, get_relative_cwd(buffer, size, get_git_work_tree())
+ * unifies the handling of "outside work tree" with "no work tree at all".
*/
char *get_relative_cwd(char *buffer, int size, const char *dir)
{
char *cwd = buffer;
- /*
- * a lazy caller can pass a NULL returned from get_git_work_tree()
- * and rely on this function to return NULL.
- */
if (!dir)
return NULL;
if (!getcwd(buffer, size))
- die("can't find the current directory: %s", strerror(errno));
+ die_errno("can't find the current directory");
if (!is_absolute_path(dir))
dir = make_absolute_path(dir);
@@ -680,3 +952,101 @@ int is_inside_dir(const char *dir)
char buffer[PATH_MAX];
return get_relative_cwd(buffer, sizeof(buffer), dir) != NULL;
}
+
+int is_empty_dir(const char *path)
+{
+ DIR *dir = opendir(path);
+ struct dirent *e;
+ int ret = 1;
+
+ if (!dir)
+ return 0;
+
+ while ((e = readdir(dir)) != NULL)
+ if (!is_dot_or_dotdot(e->d_name)) {
+ ret = 0;
+ break;
+ }
+
+ closedir(dir);
+ return ret;
+}
+
+int remove_dir_recursively(struct strbuf *path, int flag)
+{
+ DIR *dir;
+ struct dirent *e;
+ int ret = 0, original_len = path->len, len;
+ int only_empty = (flag & REMOVE_DIR_EMPTY_ONLY);
+ unsigned char submodule_head[20];
+
+ if ((flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
+ !resolve_gitlink_ref(path->buf, "HEAD", submodule_head))
+ /* Do not descend and nuke a nested git work tree. */
+ return 0;
+
+ dir = opendir(path->buf);
+ if (!dir)
+ return -1;
+ if (path->buf[original_len - 1] != '/')
+ strbuf_addch(path, '/');
+
+ len = path->len;
+ while ((e = readdir(dir)) != NULL) {
+ struct stat st;
+ if (is_dot_or_dotdot(e->d_name))
+ continue;
+
+ strbuf_setlen(path, len);
+ strbuf_addstr(path, e->d_name);
+ if (lstat(path->buf, &st))
+ ; /* fall thru */
+ else if (S_ISDIR(st.st_mode)) {
+ if (!remove_dir_recursively(path, only_empty))
+ continue; /* happy */
+ } else if (!only_empty && !unlink(path->buf))
+ continue; /* happy, too */
+
+ /* path too long, stat fails, or non-directory still exists */
+ ret = -1;
+ break;
+ }
+ closedir(dir);
+
+ strbuf_setlen(path, original_len);
+ if (!ret)
+ ret = rmdir(path->buf);
+ return ret;
+}
+
+void setup_standard_excludes(struct dir_struct *dir)
+{
+ const char *path;
+
+ dir->exclude_per_dir = ".gitignore";
+ path = git_path("info/exclude");
+ if (!access(path, R_OK))
+ add_excludes_from_file(dir, path);
+ if (excludes_file && !access(excludes_file, R_OK))
+ add_excludes_from_file(dir, excludes_file);
+}
+
+int remove_path(const char *name)
+{
+ char *slash;
+
+ if (unlink(name) && errno != ENOENT)
+ return -1;
+
+ slash = strrchr(name, '/');
+ if (slash) {
+ char *dirs = xstrdup(name);
+ slash = dirs + (slash - name);
+ do {
+ *slash = '\0';
+ } while (rmdir(dirs) == 0 && (slash = strrchr(dirs, '/')));
+ free(dirs);
+ }
+ return 0;
+}
+