int check_only, const struct path_simplify *simplify);
static int get_dtype(struct dirent *de, const char *path, int len);
+static struct trace_key trace_exclude = TRACE_KEY_INIT(EXCLUDE);
+
/* helper string functions with support for the ignore_case flag */
int strcmp_icase(const char *a, const char *b)
{
parse_exclude_pattern(&string, &patternlen, &flags, &nowildcardlen);
if (flags & EXC_FLAG_MUSTBEDIR) {
- char *s;
- x = xmalloc(sizeof(*x) + patternlen + 1);
- s = (char *)(x+1);
- memcpy(s, string, patternlen);
- s[patternlen] = '\0';
- x->pattern = s;
+ FLEXPTR_ALLOC_MEM(x, pattern, string, patternlen);
} else {
x = xmalloc(sizeof(*x));
x->pattern = string;
x->baselen = baselen;
x->flags = flags;
x->srcpos = srcpos;
+ string_list_init(&x->sticky_paths, 1);
ALLOC_GROW(el->excludes, el->nr + 1, el->alloc);
el->excludes[el->nr++] = x;
x->el = el;
{
int i;
- for (i = 0; i < el->nr; i++)
+ for (i = 0; i < el->nr; i++) {
+ string_list_clear(&el->excludes[i]->sticky_paths, 0);
free(el->excludes[i]);
+ }
free(el->excludes);
free(el->filebuf);
}
uc->dir_created++;
- d = xmalloc(sizeof(*d) + len + 1);
- memset(d, 0, sizeof(*d));
- memcpy(d->name, name, len);
- d->name[len] = '\0';
+ FLEX_ALLOC_MEM(d, name, name, len);
ALLOC_GROW(dir->dirs, dir->dirs_nr + 1, dir->dirs_alloc);
memmove(dir->dirs + first + 1, dir->dirs + first,
return 0;
}
if (buf[size-1] != '\n') {
- buf = xrealloc(buf, size+1);
+ buf = xrealloc(buf, st_add(size, 1));
buf[size++] = '\n';
}
} else {
close(fd);
return 0;
}
- buf = xmalloc(size+1);
+ buf = xmallocz(size);
if (read_in_full(fd, buf, size) != size) {
free(buf);
close(fd);
* then our prefix match is all we need; we
* do not need to call fnmatch at all.
*/
- if (!patternlen && !namelen)
+ if (!patternlen && (!namelen || *name == '/'))
return 1;
}
WM_PATHNAME) == 0;
}
+static void add_sticky(struct exclude *exc, const char *pathname, int pathlen)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int i;
+
+ for (i = exc->sticky_paths.nr - 1; i >= 0; i--) {
+ const char *sticky = exc->sticky_paths.items[i].string;
+ int len = strlen(sticky);
+
+ if (pathlen < len && sticky[pathlen] == '/' &&
+ !strncmp(pathname, sticky, pathlen))
+ return;
+ }
+
+ strbuf_add(&sb, pathname, pathlen);
+ string_list_append_nodup(&exc->sticky_paths, strbuf_detach(&sb, NULL));
+}
+
+static int match_sticky(struct exclude *exc, const char *pathname, int pathlen, int dtype)
+{
+ int i;
+
+ for (i = exc->sticky_paths.nr - 1; i >= 0; i--) {
+ const char *sticky = exc->sticky_paths.items[i].string;
+ int len = strlen(sticky);
+
+ if (pathlen == len && dtype == DT_DIR &&
+ !strncmp(pathname, sticky, len))
+ return 1;
+
+ if (pathlen > len && pathname[len] == '/' &&
+ !strncmp(pathname, sticky, len))
+ return 1;
+ }
+
+ return 0;
+}
+
+static inline int different_decisions(const struct exclude *a,
+ const struct exclude *b)
+{
+ return (a->flags & EXC_FLAG_NEGATIVE) != (b->flags & EXC_FLAG_NEGATIVE);
+}
+
+/*
+ * Return non-zero if pathname is a directory and an ancestor of the
+ * literal path in a pattern.
+ */
+static int match_directory_part(const char *pathname, int pathlen,
+ int *dtype, struct exclude *x)
+{
+ const char *base = x->base;
+ int baselen = x->baselen ? x->baselen - 1 : 0;
+ const char *pattern = x->pattern;
+ int prefix = x->nowildcardlen;
+ int patternlen = x->patternlen;
+
+ if (*dtype == DT_UNKNOWN)
+ *dtype = get_dtype(NULL, pathname, pathlen);
+ if (*dtype != DT_DIR)
+ return 0;
+
+ if (*pattern == '/') {
+ pattern++;
+ patternlen--;
+ prefix--;
+ }
+
+ if (baselen) {
+ if (((pathlen < baselen && base[pathlen] == '/') ||
+ pathlen == baselen) &&
+ !strncmp_icase(pathname, base, pathlen))
+ return 1;
+ pathname += baselen + 1;
+ pathlen -= baselen + 1;
+ }
+
+
+ if (prefix &&
+ (((pathlen < prefix && pattern[pathlen] == '/') ||
+ pathlen == prefix) &&
+ !strncmp_icase(pathname, pattern, pathlen)))
+ return 1;
+
+ return 0;
+}
+
+static struct exclude *should_descend(const char *pathname, int pathlen,
+ int *dtype, struct exclude_list *el,
+ struct exclude *exc)
+{
+ int i;
+
+ for (i = el->nr - 1; 0 <= i; i--) {
+ struct exclude *x = el->excludes[i];
+
+ if (x == exc)
+ break;
+
+ if (!(x->flags & EXC_FLAG_NODIR) &&
+ different_decisions(x, exc) &&
+ match_directory_part(pathname, pathlen, dtype, x))
+ return x;
+ }
+ return NULL;
+}
+
/*
* 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
struct exclude_list *el)
{
struct exclude *exc = NULL; /* undecided */
- int i;
+ int i, maybe_descend = 0;
if (!el->nr)
return NULL; /* undefined */
+ trace_printf_key(&trace_exclude, "exclude: from %s\n", el->src);
+
for (i = el->nr - 1; 0 <= i; i--) {
struct exclude *x = el->excludes[i];
const char *exclude = x->pattern;
int prefix = x->nowildcardlen;
+ if (!maybe_descend && i < el->nr - 1 &&
+ different_decisions(x, el->excludes[i+1]))
+ maybe_descend = 1;
+
+ if (x->sticky_paths.nr) {
+ if (*dtype == DT_UNKNOWN)
+ *dtype = get_dtype(NULL, pathname, pathlen);
+ if (match_sticky(x, pathname, pathlen, *dtype)) {
+ exc = x;
+ break;
+ }
+ continue;
+ }
+
if (x->flags & EXC_FLAG_MUSTBEDIR) {
if (*dtype == DT_UNKNOWN)
*dtype = get_dtype(NULL, pathname, pathlen);
break;
}
}
+
+ if (!exc) {
+ trace_printf_key(&trace_exclude, "exclude: %.*s => n/a\n",
+ pathlen, pathname);
+ return NULL;
+ }
+
+ /*
+ * We have found a matching pattern "exc" that may exclude whole
+ * directory. We also found that there may be a pattern that matches
+ * something inside the directory and reincludes stuff.
+ *
+ * Go through the patterns again, find that pattern and double check.
+ * If it's true, return "undecided" and keep descending in. "exc" is
+ * marked sticky so that it continues to match inside the directory.
+ */
+ if (!(exc->flags & EXC_FLAG_NEGATIVE) && maybe_descend) {
+ struct exclude *x;
+
+ if (*dtype == DT_UNKNOWN)
+ *dtype = get_dtype(NULL, pathname, pathlen);
+
+ if (*dtype == DT_DIR &&
+ (x = should_descend(pathname, pathlen, dtype, el, exc))) {
+ add_sticky(exc, pathname, pathlen);
+ trace_printf_key(&trace_exclude,
+ "exclude: %.*s vs %s at line %d => %s,"
+ " forced open by %s at line %d => n/a\n",
+ pathlen, pathname, exc->pattern, exc->srcpos,
+ exc->flags & EXC_FLAG_NEGATIVE ? "no" : "yes",
+ x->pattern, x->srcpos);
+ return NULL;
+ }
+ }
+
+ trace_printf_key(&trace_exclude, "exclude: %.*s vs %s at line %d => %s%s\n",
+ pathlen, pathname, exc->pattern, exc->srcpos,
+ exc->flags & EXC_FLAG_NEGATIVE ? "no" : "yes",
+ exc->sticky_paths.nr ? " (stuck)" : "");
return exc;
}
{
struct dir_entry *ent;
- ent = xmalloc(sizeof(*ent) + len + 1);
+ FLEX_ALLOC_MEM(ent, name, pathname, len);
ent->len = len;
- memcpy(ent->name, pathname, len);
- ent->name[len] = 0;
return ent;
}
struct cached_dir cdir;
enum path_treatment state, subdir_state, dir_state = path_none;
struct strbuf path = STRBUF_INIT;
+ static int level = 0;
strbuf_add(&path, base, baselen);
+ trace_printf_key(&trace_exclude, "exclude: [%d] enter '%.*s'\n",
+ level++, baselen, base);
+
if (open_cached_dir(&cdir, dir, untracked, &path, check_only))
goto out;
}
close_cached_dir(&cdir);
out:
+ trace_printf_key(&trace_exclude, "exclude: [%d] leave '%.*s'\n",
+ --level, baselen, base);
strbuf_release(&path);
return dir_state;
return root;
}
+static void clear_sticky(struct dir_struct *dir)
+{
+ struct exclude_list_group *g;
+ struct exclude_list *el;
+ struct exclude *x;
+ int i, j, k;
+
+ for (i = EXC_CMDL; i <= EXC_FILE; i++) {
+ g = &dir->exclude_list_group[i];
+ for (j = g->nr - 1; j >= 0; j--) {
+ el = &g->el[j];
+ for (k = el->nr - 1; 0 <= k; k--) {
+ x = el->excludes[k];
+ string_list_clear(&x->sticky_paths, 0);
+ }
+ }
+ }
+}
+
int read_directory(struct dir_struct *dir, const char *path, int len, const struct pathspec *pathspec)
{
struct path_simplify *simplify;
if (has_symlink_leading_path(path, len))
return dir->nr;
+ /*
+ * Stay on the safe side. if read_directory() has run once on
+ * "dir", some sticky flag may have been left. Clear them all.
+ */
+ clear_sticky(dir);
+
/*
* exclude patterns are treated like positive ones in
* create_simplify. Usually exclude patterns should be a
struct ondisk_untracked_cache *ouc;
struct write_data wd;
unsigned char varbuf[16];
- int len = 0, varint_len;
- if (untracked->exclude_per_dir)
- len = strlen(untracked->exclude_per_dir);
- ouc = xmalloc(sizeof(*ouc) + len + 1);
+ int varint_len;
+ size_t len = strlen(untracked->exclude_per_dir);
+
+ FLEX_ALLOC_MEM(ouc, exclude_per_dir, untracked->exclude_per_dir, len);
stat_data_to_disk(&ouc->info_exclude_stat, &untracked->ss_info_exclude.stat);
stat_data_to_disk(&ouc->excludes_file_stat, &untracked->ss_excludes_file.stat);
hashcpy(ouc->info_exclude_sha1, untracked->ss_info_exclude.sha1);
hashcpy(ouc->excludes_file_sha1, untracked->ss_excludes_file.sha1);
ouc->dir_flags = htonl(untracked->dir_flags);
- memcpy(ouc->exclude_per_dir, untracked->exclude_per_dir, len + 1);
varint_len = encode_varint(untracked->ident.len, varbuf);
strbuf_add(out, varbuf, varint_len);
ud.untracked_alloc = value;
ud.untracked_nr = value;
if (ud.untracked_nr)
- ud.untracked = xmalloc(sizeof(*ud.untracked) * ud.untracked_nr);
+ ALLOC_ARRAY(ud.untracked, ud.untracked_nr);
data = next;
next = data;
ud.dirs_alloc = ud.dirs_nr = decode_varint(&next);
if (next > end)
return -1;
- ud.dirs = xmalloc(sizeof(*ud.dirs) * ud.dirs_nr);
+ ALLOC_ARRAY(ud.dirs, ud.dirs_nr);
data = next;
len = strlen((const char *)data);
next = data + len + 1;
if (next > rd->end)
return -1;
- *untracked_ = untracked = xmalloc(sizeof(*untracked) + len);
+ *untracked_ = untracked = xmalloc(st_add(sizeof(*untracked), len));
memcpy(untracked, &ud, sizeof(ud));
memcpy(untracked->name, data, len + 1);
data = next;
rd.data = next;
rd.end = end;
rd.index = 0;
- rd.ucd = xmalloc(sizeof(*rd.ucd) * len);
+ ALLOC_ARRAY(rd.ucd, len);
if (read_one_dir(&uc->root, &rd) || rd.index != len)
goto done;