}
/*
- * 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)
}
/*
- * 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,
}
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;
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)
return data;
}
-void free_excludes(struct exclude_list *el)
+/*
+ * Frees memory within el which was allocated for exclude patterns and
+ * the file buffer. Does not free el itself.
+ */
+void clear_exclude_list(struct exclude_list *el)
{
int i;
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;
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);
}
*/
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;
(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. */
}
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;
}
return fnmatch_icase(pattern, name, FNM_PATHNAME) == 0;
}
-/* Scan the list and let the last match determine the fate.
- * Return 1 for exclude, 0 for include and -1 for undecided.
+/*
+ * Scan the given exclude list in reverse to see whether pathname
+ * should be ignored. The first match (i.e. the last on the list), if
+ * any, determines the fate. Returns the exclude_list element which
+ * matched, or NULL for undecided.
*/
-int is_excluded_from_list(const char *pathname,
- int pathlen, const char *basename, int *dtype,
- struct exclude_list *el)
+static struct exclude *last_exclude_matching_from_list(const char *pathname,
+ int pathlen,
+ const char *basename,
+ int *dtype,
+ struct exclude_list *el)
{
int i;
if (!el->nr)
- return -1; /* undefined */
+ return NULL; /* undefined */
for (i = el->nr - 1; 0 <= i; i--) {
struct exclude *x = el->excludes[i];
const char *exclude = x->pattern;
- int to_exclude = x->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
int prefix = x->nowildcardlen;
if (x->flags & EXC_FLAG_MUSTBEDIR) {
pathlen - (basename - pathname),
exclude, prefix, x->patternlen,
x->flags))
- return to_exclude;
+ return x;
continue;
}
if (match_pathname(pathname, pathlen,
x->base, x->baselen ? x->baselen - 1 : 0,
exclude, prefix, x->patternlen, x->flags))
- return to_exclude;
+ return x;
}
+ return NULL; /* undecided */
+}
+
+/*
+ * Scan the list and let the last match determine the fate.
+ * Return 1 for exclude, 0 for include and -1 for undecided.
+ */
+int is_excluded_from_list(const char *pathname,
+ int pathlen, const char *basename, int *dtype,
+ struct exclude_list *el)
+{
+ struct exclude *exclude;
+ exclude = last_exclude_matching_from_list(pathname, pathlen, basename, dtype, el);
+ if (exclude)
+ return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
return -1; /* undecided */
}
-static int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
+/*
+ * Loads the exclude lists for the directory containing pathname, then
+ * scans all exclude lists to determine whether pathname is excluded.
+ * Returns the exclude_list element which matched, or NULL for
+ * undecided.
+ */
+static struct exclude *last_exclude_matching(struct dir_struct *dir,
+ const char *pathname,
+ 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++) {
- switch (is_excluded_from_list(pathname, pathlen,
- basename, dtype_p,
- &dir->exclude_list[st])) {
- case 0:
- return 0;
- case 1:
- return 1;
+
+ 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;
+}
+
+/*
+ * Loads the exclude lists for the directory containing pathname, then
+ * scans all exclude lists to determine whether pathname is excluded.
+ * Returns 1 if true, otherwise 0.
+ */
+static int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
+{
+ struct exclude *exclude =
+ last_exclude_matching(dir, pathname, dtype_p);
+ if (exclude)
+ return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
return 0;
}
struct dir_struct *dir)
{
check->dir = dir;
+ check->exclude = NULL;
strbuf_init(&check->path, 256);
}
}
/*
- * Is this name excluded? This is for a caller like show_files() that
- * do not honor directory hierarchy and iterate through paths that are
- * possibly in an ignored directory.
+ * For each subdirectory in name, starting with the top-most, checks
+ * to see if that subdirectory is excluded, and if so, returns the
+ * corresponding exclude structure. Otherwise, checks whether name
+ * itself (which is presumably a file) is excluded.
*
* A path to a directory known to be excluded is left in check->path to
* optimize for repeated checks for files in the same excluded directory.
*/
-int is_path_excluded(struct path_exclude_check *check,
- const char *name, int namelen, int *dtype)
+struct exclude *last_exclude_matching_path(struct path_exclude_check *check,
+ const char *name, int namelen,
+ int *dtype)
{
int i;
struct strbuf *path = &check->path;
+ struct exclude *exclude;
/*
* we allow the caller to pass namelen as an optimization; it
* must match the length of the name, as we eventually call
- * excluded() on the whole name string.
+ * is_excluded() on the whole name string.
*/
if (namelen < 0)
namelen = strlen(name);
+ /*
+ * If path is non-empty, and name is equal to path or a
+ * subdirectory of path, name should be excluded, because
+ * it's inside a directory which is already known to be
+ * excluded and was previously left in check->path.
+ */
if (path->len &&
path->len <= namelen &&
!memcmp(name, path->buf, path->len) &&
(!name[path->len] || name[path->len] == '/'))
- return 1;
+ return check->exclude;
strbuf_setlen(path, 0);
for (i = 0; name[i]; i++) {
if (ch == '/') {
int dt = DT_DIR;
- if (excluded(check->dir, path->buf, &dt))
- return 1;
+ exclude = last_exclude_matching(check->dir,
+ path->buf, &dt);
+ if (exclude) {
+ check->exclude = exclude;
+ return exclude;
+ }
}
strbuf_addch(path, ch);
}
/* An entry in the index; cannot be a directory with subentries */
strbuf_setlen(path, 0);
- return excluded(check->dir, name, dtype);
+ return last_exclude_matching(check->dir, name, dtype);
+}
+
+/*
+ * Is this name excluded? This is for a caller like show_files() that
+ * do not honor directory hierarchy and iterate through paths that are
+ * possibly in an ignored directory.
+ */
+int is_path_excluded(struct path_exclude_check *check,
+ const char *name, int namelen, int *dtype)
+{
+ struct exclude *exclude =
+ last_exclude_matching_path(check, name, namelen, dtype);
+ if (exclude)
+ return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
+ return 0;
}
static struct dir_entry *dir_entry_new(const char *pathname, int len)
const struct path_simplify *simplify,
int dtype, struct dirent *de)
{
- int exclude = excluded(dir, path->buf, &dtype);
+ int exclude = is_excluded(dir, path->buf, &dtype);
if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
&& exclude_matches_pathspec(path->buf, path->len, simplify))
dir_add_ignored(dir, path->buf, path->len);
free(pathspec->items);
pathspec->items = NULL;
}
+
+/*
+ * 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;
+ }
+}