summaryrefslogtreecommitdiff
path: root/dir.c
diff options
context:
space:
mode:
Diffstat (limited to 'dir.c')
-rw-r--r--dir.c365
1 files changed, 293 insertions, 72 deletions
diff --git a/dir.c b/dir.c
index a71331af18..69e0be6aa2 100644
--- a/dir.c
+++ b/dir.c
@@ -53,6 +53,8 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
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)
{
@@ -503,12 +505,7 @@ void add_exclude(const char *string, const char *base,
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;
@@ -519,6 +516,7 @@ void add_exclude(const char *string, const char *base,
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;
@@ -559,14 +557,14 @@ void clear_exclude_list(struct exclude_list *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);
- el->nr = 0;
- el->excludes = NULL;
- el->filebuf = NULL;
+ memset(el, 0, sizeof(*el));
}
static void trim_trailing_spaces(char *buf)
@@ -627,10 +625,7 @@ static struct untracked_cache_dir *lookup_untracked(struct untracked_cache *uc,
}
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,
@@ -699,7 +694,7 @@ static int add_excludes(const char *fname, const char *base, int baselen,
return 0;
}
if (buf[size-1] != '\n') {
- buf = xrealloc(buf, size+1);
+ buf = xrealloc(buf, st_add(size, 1));
buf[size++] = '\n';
}
} else {
@@ -713,7 +708,7 @@ static int add_excludes(const char *fname, const char *base, int baselen,
close(fd);
return 0;
}
- buf = xmalloc(size+1);
+ buf = xmallocz(size);
if (read_in_full(fd, buf, size) != size) {
free(buf);
close(fd);
@@ -880,7 +875,7 @@ int match_pathname(const char *pathname, int pathlen,
* 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;
}
@@ -889,6 +884,113 @@ int match_pathname(const char *pathname, int pathlen,
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
@@ -901,16 +1003,33 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname,
int *dtype,
struct exclude_list *el)
{
- int i;
+ struct exclude *exc = NULL; /* undecided */
+ 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);
@@ -922,18 +1041,61 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname,
if (match_basename(basename,
pathlen - (basename - pathname),
exclude, prefix, x->patternlen,
- x->flags))
- return x;
+ x->flags)) {
+ exc = x;
+ break;
+ }
continue;
}
assert(x->baselen == 0 || x->base[x->baselen - 1] == '/');
if (match_pathname(pathname, pathlen,
x->base, x->baselen ? x->baselen - 1 : 0,
- exclude, prefix, x->patternlen, x->flags))
- return x;
+ exclude, prefix, x->patternlen, x->flags)) {
+ exc = x;
+ break;
+ }
}
- return NULL; /* undecided */
+
+ 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;
}
/*
@@ -1164,10 +1326,8 @@ static struct dir_entry *dir_entry_new(const char *pathname, int len)
{
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;
}
@@ -1202,29 +1362,15 @@ enum exist_status {
*/
static enum exist_status directory_exists_in_index_icase(const char *dirname, int len)
{
- const struct cache_entry *ce = cache_dir_exists(dirname, len);
- unsigned char endchar;
+ struct cache_entry *ce;
- if (!ce)
- return index_nonexistent;
- endchar = ce->name[len];
-
- /*
- * The cache_entry structure returned will contain this dirname
- * and possibly additional path components.
- */
- if (endchar == '/')
+ if (cache_dir_exists(dirname, len))
return index_directory;
- /*
- * If there are no additional path components, then this cache_entry
- * represents a submodule. Submodules, despite being directories,
- * are stored in the cache without a closing slash.
- */
- if (!endchar && S_ISGITLINK(ce->ce_mode))
+ ce = cache_file_exists(dirname, len, ignore_case);
+ if (ce && S_ISGITLINK(ce->ce_mode))
return index_gitdir;
- /* This should never be hit, but it exists just in case. */
return index_nonexistent;
}
@@ -1519,8 +1665,7 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir,
}
strbuf_addstr(path, cdir->ucd->name);
/* treat_one_path() does this before it calls treat_directory() */
- if (path->buf[path->len - 1] != '/')
- strbuf_addch(path, '/');
+ strbuf_complete(path, '/');
if (cdir->ucd->check_only)
/*
* check_only is set as a result of treat_directory() getting
@@ -1695,9 +1840,13 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
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;
@@ -1761,6 +1910,8 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
}
close_cached_dir(&cdir);
out:
+ trace_printf_key(&trace_exclude, "exclude: [%d] leave '%.*s'\n",
+ --level, baselen, base);
strbuf_release(&path);
return dir_state;
@@ -1851,31 +2002,67 @@ static const char *get_ident_string(void)
return sb.buf;
if (uname(&uts) < 0)
die_errno(_("failed to get kernel name and information"));
- strbuf_addf(&sb, "Location %s, system %s %s %s", get_git_work_tree(),
- uts.sysname, uts.release, uts.version);
+ strbuf_addf(&sb, "Location %s, system %s", get_git_work_tree(),
+ uts.sysname);
return sb.buf;
}
static int ident_in_untracked(const struct untracked_cache *uc)
{
- const char *end = uc->ident.buf + uc->ident.len;
- const char *p = uc->ident.buf;
+ /*
+ * Previous git versions may have saved many NUL separated
+ * strings in the "ident" field, but it is insane to manage
+ * many locations, so just take care of the first one.
+ */
- for (p = uc->ident.buf; p < end; p += strlen(p) + 1)
- if (!strcmp(p, get_ident_string()))
- return 1;
- return 0;
+ return !strcmp(uc->ident.buf, get_ident_string());
}
-void add_untracked_ident(struct untracked_cache *uc)
+static void set_untracked_ident(struct untracked_cache *uc)
{
- if (ident_in_untracked(uc))
- return;
+ strbuf_reset(&uc->ident);
strbuf_addstr(&uc->ident, get_ident_string());
- /* this strbuf contains a list of strings, save NUL too */
+
+ /*
+ * This strbuf used to contain a list of NUL separated
+ * strings, so save NUL too for backward compatibility.
+ */
strbuf_addch(&uc->ident, 0);
}
+static void new_untracked_cache(struct index_state *istate)
+{
+ struct untracked_cache *uc = xcalloc(1, sizeof(*uc));
+ strbuf_init(&uc->ident, 100);
+ uc->exclude_per_dir = ".gitignore";
+ /* should be the same flags used by git-status */
+ uc->dir_flags = DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
+ set_untracked_ident(uc);
+ istate->untracked = uc;
+ istate->cache_changed |= UNTRACKED_CHANGED;
+}
+
+void add_untracked_cache(struct index_state *istate)
+{
+ if (!istate->untracked) {
+ new_untracked_cache(istate);
+ } else {
+ if (!ident_in_untracked(istate->untracked)) {
+ free_untracked_cache(istate->untracked);
+ new_untracked_cache(istate);
+ }
+ }
+}
+
+void remove_untracked_cache(struct index_state *istate)
+{
+ if (istate->untracked) {
+ free_untracked_cache(istate->untracked);
+ istate->untracked = NULL;
+ istate->cache_changed |= UNTRACKED_CHANGED;
+ }
+}
+
static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *dir,
int base_len,
const struct pathspec *pathspec)
@@ -1933,7 +2120,7 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d
return NULL;
if (!ident_in_untracked(dir->untracked)) {
- warning(_("Untracked cache is disabled on this system."));
+ warning(_("Untracked cache is disabled on this system or location."));
return NULL;
}
@@ -1961,6 +2148,25 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d
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;
@@ -1982,6 +2188,12 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru
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
* subset of positive ones, which has no impacts on
@@ -2030,6 +2242,15 @@ int file_exists(const char *f)
return lstat(f, &sb) == 0;
}
+static int cmp_icase(char a, char b)
+{
+ if (a == b)
+ return 0;
+ if (ignore_case)
+ return toupper(a) - toupper(b);
+ return a - b;
+}
+
/*
* Given two normalized paths (a trailing slash is ok), if subdir is
* outside dir, return -1. Otherwise return the offset in subdir that
@@ -2041,7 +2262,7 @@ int dir_inside_of(const char *subdir, const char *dir)
assert(dir && subdir && *dir && *subdir);
- while (*dir && *subdir && *dir == *subdir) {
+ while (*dir && *subdir && !cmp_icase(*dir, *subdir)) {
dir++;
subdir++;
offset++;
@@ -2126,8 +2347,7 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
else
return -1;
}
- if (path->buf[original_len - 1] != '/')
- strbuf_addch(path, '/');
+ strbuf_complete(path, '/');
len = path->len;
while ((e = readdir(dir)) != NULL) {
@@ -2176,6 +2396,8 @@ int remove_dir_recursively(struct strbuf *path, int flag)
return remove_dir_recurse(path, flag, NULL);
}
+static GIT_PATH_FUNC(git_path_info_exclude, "info/exclude")
+
void setup_standard_excludes(struct dir_struct *dir)
{
const char *path;
@@ -2190,7 +2412,7 @@ void setup_standard_excludes(struct dir_struct *dir)
dir->untracked ? &dir->ss_excludes_file : NULL);
/* per repository user preference */
- path = git_path("info/exclude");
+ path = git_path_info_exclude();
if (!access_or_warn(path, R_OK, 0))
add_excludes_from_file_1(dir, path,
dir->untracked ? &dir->ss_info_exclude : NULL);
@@ -2336,16 +2558,15 @@ void write_untracked_extension(struct strbuf *out, struct untracked_cache *untra
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);
@@ -2450,21 +2671,21 @@ static int read_one_dir(struct untracked_cache_dir **untracked_,
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;
@@ -2577,7 +2798,7 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long
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;