};
static enum path_treatment read_directory_recursive(struct dir_struct *dir,
- const char *path, int len,
+ const char *path, int len, struct untracked_cache_dir *untracked,
int check_only, const struct path_simplify *simplify);
static int get_dtype(struct dirent *de, const char *path, int len);
x->el = el;
}
-static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
+static void *read_skip_worktree_file_from_index(const char *path, size_t *size,
+ struct sha1_stat *sha1_stat)
{
int pos, len;
unsigned long sz;
return NULL;
}
*size = xsize_t(sz);
+ if (sha1_stat) {
+ memset(&sha1_stat->stat, 0, sizeof(sha1_stat->stat));
+ hashcpy(sha1_stat->sha1, active_cache[pos]->sha1);
+ }
return data;
}
*last_space = '\0';
}
-int add_excludes_from_file_to_list(const char *fname,
- const char *base,
- int baselen,
- struct exclude_list *el,
- int check_index)
+/*
+ * Given a subdirectory name and "dir" of the current directory,
+ * search the subdir in "dir" and return it, or create a new one if it
+ * does not exist in "dir".
+ *
+ * If "name" has the trailing slash, it'll be excluded in the search.
+ */
+static struct untracked_cache_dir *lookup_untracked(struct untracked_cache *uc,
+ struct untracked_cache_dir *dir,
+ const char *name, int len)
+{
+ int first, last;
+ struct untracked_cache_dir *d;
+ if (!dir)
+ return NULL;
+ if (len && name[len - 1] == '/')
+ len--;
+ first = 0;
+ last = dir->dirs_nr;
+ while (last > first) {
+ int cmp, next = (last + first) >> 1;
+ d = dir->dirs[next];
+ cmp = strncmp(name, d->name, len);
+ if (!cmp && strlen(d->name) > len)
+ cmp = -1;
+ if (!cmp)
+ return d;
+ if (cmp < 0) {
+ last = next;
+ continue;
+ }
+ first = next+1;
+ }
+
+ uc->dir_created++;
+ d = xmalloc(sizeof(*d) + len + 1);
+ memset(d, 0, sizeof(*d));
+ memcpy(d->name, name, len);
+ d->name[len] = '\0';
+
+ ALLOC_GROW(dir->dirs, dir->dirs_nr + 1, dir->dirs_alloc);
+ memmove(dir->dirs + first + 1, dir->dirs + first,
+ (dir->dirs_nr - first) * sizeof(*dir->dirs));
+ dir->dirs_nr++;
+ dir->dirs[first] = d;
+ return d;
+}
+
+static void do_invalidate_gitignore(struct untracked_cache_dir *dir)
+{
+ int i;
+ dir->valid = 0;
+ dir->untracked_nr = 0;
+ for (i = 0; i < dir->dirs_nr; i++)
+ do_invalidate_gitignore(dir->dirs[i]);
+}
+
+static void invalidate_gitignore(struct untracked_cache *uc,
+ struct untracked_cache_dir *dir)
+{
+ uc->gitignore_invalidated++;
+ do_invalidate_gitignore(dir);
+}
+
+/*
+ * Given a file with name "fname", read it (either from disk, or from
+ * the index if "check_index" is non-zero), parse it and store the
+ * exclude rules in "el".
+ *
+ * If "ss" is not NULL, compute SHA-1 of the exclude file and fill
+ * stat data from disk (only valid if add_excludes returns zero). If
+ * ss_valid is non-zero, "ss" must contain good value as input.
+ */
+static int add_excludes(const char *fname, const char *base, int baselen,
+ struct exclude_list *el, int check_index,
+ struct sha1_stat *sha1_stat)
{
struct stat st;
int fd, i, lineno = 1;
if (0 <= fd)
close(fd);
if (!check_index ||
- (buf = read_skip_worktree_file_from_index(fname, &size)) == NULL)
+ (buf = read_skip_worktree_file_from_index(fname, &size, sha1_stat)) == NULL)
return -1;
if (size == 0) {
free(buf);
} else {
size = xsize_t(st.st_size);
if (size == 0) {
+ if (sha1_stat) {
+ fill_stat_data(&sha1_stat->stat, &st);
+ hashcpy(sha1_stat->sha1, EMPTY_BLOB_SHA1_BIN);
+ sha1_stat->valid = 1;
+ }
close(fd);
return 0;
}
}
buf[size++] = '\n';
close(fd);
+ if (sha1_stat) {
+ int pos;
+ if (sha1_stat->valid &&
+ !match_stat_data(&sha1_stat->stat, &st))
+ ; /* no content change, ss->sha1 still good */
+ else if (check_index &&
+ (pos = cache_name_pos(fname, strlen(fname))) >= 0 &&
+ !ce_stage(active_cache[pos]) &&
+ ce_uptodate(active_cache[pos]) &&
+ !would_convert_to_git(fname))
+ hashcpy(sha1_stat->sha1, active_cache[pos]->sha1);
+ else
+ hash_sha1_file(buf, size, "blob", sha1_stat->sha1);
+ fill_stat_data(&sha1_stat->stat, &st);
+ sha1_stat->valid = 1;
+ }
}
el->filebuf = buf;
return 0;
}
+int add_excludes_from_file_to_list(const char *fname, const char *base,
+ int baselen, struct exclude_list *el,
+ int check_index)
+{
+ return add_excludes(fname, base, baselen, el, check_index, NULL);
+}
+
struct exclude_list *add_exclude_list(struct dir_struct *dir,
int group_type, const char *src)
{
/*
* Used to set up core.excludesfile and .git/info/exclude lists.
*/
-void add_excludes_from_file(struct dir_struct *dir, const char *fname)
+static void add_excludes_from_file_1(struct dir_struct *dir, const char *fname,
+ struct sha1_stat *sha1_stat)
{
struct exclude_list *el;
+ /*
+ * catch setup_standard_excludes() that's called before
+ * dir->untracked is assigned. That function behaves
+ * differently when dir->untracked is non-NULL.
+ */
+ if (!dir->untracked)
+ dir->unmanaged_exclude_files++;
el = add_exclude_list(dir, EXC_FILE, fname);
- if (add_excludes_from_file_to_list(fname, "", 0, el, 0) < 0)
+ if (add_excludes(fname, "", 0, el, 0, sha1_stat) < 0)
die("cannot use %s as an exclude file", fname);
}
+void add_excludes_from_file(struct dir_struct *dir, const char *fname)
+{
+ dir->unmanaged_exclude_files++; /* see validate_untracked_cache() */
+ add_excludes_from_file_1(dir, fname, NULL);
+}
+
int match_basename(const char *basename, int basenamelen,
const char *pattern, int prefix, int patternlen,
int flags)
struct exclude_list_group *group;
struct exclude_list *el;
struct exclude_stack *stk = NULL;
+ struct untracked_cache_dir *untracked;
int current;
group = &dir->exclude_list_group[EXC_DIRS];
/* Read from the parent directories and push them down. */
current = stk ? stk->baselen : -1;
strbuf_setlen(&dir->basebuf, current < 0 ? 0 : current);
+ if (dir->untracked)
+ untracked = stk ? stk->ucd : dir->untracked->root;
+ else
+ untracked = NULL;
+
while (current < baselen) {
const char *cp;
+ struct sha1_stat sha1_stat;
stk = xcalloc(1, sizeof(*stk));
if (current < 0) {
if (!cp)
die("oops in prep_exclude");
cp++;
+ untracked =
+ lookup_untracked(dir->untracked, untracked,
+ base + current,
+ cp - base - current);
}
stk->prev = dir->exclude_stack;
stk->baselen = cp - base;
stk->exclude_ix = group->nr;
+ stk->ucd = untracked;
el = add_exclude_list(dir, EXC_DIRS, NULL);
strbuf_add(&dir->basebuf, base + current, stk->baselen - current);
assert(stk->baselen == dir->basebuf.len);
}
/* Try to read per-directory file */
+ hashclr(sha1_stat.sha1);
+ sha1_stat.valid = 0;
if (dir->exclude_per_dir) {
/*
* dir->basebuf gets reused by the traversal, but we
strbuf_addbuf(&sb, &dir->basebuf);
strbuf_addstr(&sb, dir->exclude_per_dir);
el->src = strbuf_detach(&sb, NULL);
- add_excludes_from_file_to_list(el->src, el->src,
- stk->baselen, el, 1);
+ add_excludes(el->src, el->src, stk->baselen, el, 1,
+ untracked ? &sha1_stat : NULL);
+ }
+ if (untracked) {
+ hashcpy(untracked->exclude_sha1, sha1_stat.sha1);
}
dir->exclude_stack = stk;
current = stk->baselen;
* (c) otherwise, we recurse into it.
*/
static enum path_treatment treat_directory(struct dir_struct *dir,
+ struct untracked_cache_dir *untracked,
const char *dirname, int len, int exclude,
const struct path_simplify *simplify)
{
if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
return exclude ? path_excluded : path_untracked;
- return read_directory_recursive(dir, dirname, len, 1, simplify);
+ untracked = lookup_untracked(dir->untracked, untracked, dirname, len);
+ return read_directory_recursive(dir, dirname, len,
+ untracked, 1, simplify);
}
/*
}
static enum path_treatment treat_one_path(struct dir_struct *dir,
+ struct untracked_cache_dir *untracked,
struct strbuf *path,
const struct path_simplify *simplify,
int dtype, struct dirent *de)
return path_none;
case DT_DIR:
strbuf_addch(path, '/');
- return treat_directory(dir, path->buf, path->len, exclude,
+ return treat_directory(dir, untracked, path->buf, path->len, exclude,
simplify);
case DT_REG:
case DT_LNK:
}
static enum path_treatment treat_path(struct dir_struct *dir,
+ struct untracked_cache_dir *untracked,
struct dirent *de,
struct strbuf *path,
int baselen,
return path_none;
dtype = DTYPE(de);
- return treat_one_path(dir, path, simplify, dtype, de);
+ return treat_one_path(dir, untracked, path, simplify, dtype, de);
+}
+
+static void add_untracked(struct untracked_cache_dir *dir, const char *name)
+{
+ if (!dir)
+ return;
+ ALLOC_GROW(dir->untracked, dir->untracked_nr + 1,
+ dir->untracked_alloc);
+ dir->untracked[dir->untracked_nr++] = xstrdup(name);
}
/*
*/
static enum path_treatment read_directory_recursive(struct dir_struct *dir,
const char *base, int baselen,
- int check_only,
+ struct untracked_cache_dir *untracked, int check_only,
const struct path_simplify *simplify)
{
DIR *fdir;
if (!fdir)
goto out;
+ if (untracked)
+ untracked->check_only = !!check_only;
+
while ((de = readdir(fdir)) != NULL) {
/* check how the file or directory should be treated */
- state = treat_path(dir, de, &path, baselen, simplify);
+ state = treat_path(dir, untracked, de, &path, baselen, simplify);
+
if (state > dir_state)
dir_state = state;
/* recurse into subdir if instructed by treat_path */
if (state == path_recurse) {
- subdir_state = read_directory_recursive(dir, path.buf,
- path.len, check_only, simplify);
+ struct untracked_cache_dir *ud;
+ ud = lookup_untracked(dir->untracked, untracked,
+ path.buf + baselen,
+ path.len - baselen);
+ subdir_state =
+ read_directory_recursive(dir, path.buf, path.len,
+ ud, check_only, simplify);
if (subdir_state > dir_state)
dir_state = subdir_state;
}
if (check_only) {
/* abort early if maximum state has been reached */
- if (dir_state == path_untracked)
+ if (dir_state == path_untracked) {
+ if (untracked)
+ add_untracked(untracked, path.buf + baselen);
break;
+ }
/* skip the dir_add_* part */
continue;
}
break;
case path_untracked:
- if (!(dir->flags & DIR_SHOW_IGNORED))
- dir_add_name(dir, path.buf, path.len);
+ if (dir->flags & DIR_SHOW_IGNORED)
+ break;
+ dir_add_name(dir, path.buf, path.len);
+ if (untracked)
+ add_untracked(untracked, path.buf + baselen);
break;
default:
break;
if (simplify_away(sb.buf, sb.len, simplify))
break;
- if (treat_one_path(dir, &sb, simplify,
+ if (treat_one_path(dir, NULL, &sb, simplify,
DT_DIR, NULL) == path_none)
break; /* do not recurse into it */
if (len <= baselen) {
return rc;
}
+static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *dir,
+ int base_len,
+ const struct pathspec *pathspec)
+{
+ struct untracked_cache_dir *root;
+
+ if (!dir->untracked)
+ return NULL;
+
+ /*
+ * We only support $GIT_DIR/info/exclude and core.excludesfile
+ * as the global ignore rule files. Any other additions
+ * (e.g. from command line) invalidate the cache. This
+ * condition also catches running setup_standard_excludes()
+ * before setting dir->untracked!
+ */
+ if (dir->unmanaged_exclude_files)
+ return NULL;
+
+ /*
+ * Optimize for the main use case only: whole-tree git
+ * status. More work involved in treat_leading_path() if we
+ * use cache on just a subset of the worktree. pathspec
+ * support could make the matter even worse.
+ */
+ if (base_len || (pathspec && pathspec->nr))
+ return NULL;
+
+ /* Different set of flags may produce different results */
+ if (dir->flags != dir->untracked->dir_flags ||
+ /*
+ * See treat_directory(), case index_nonexistent. Without
+ * this flag, we may need to also cache .git file content
+ * for the resolve_gitlink_ref() call, which we don't.
+ */
+ !(dir->flags & DIR_SHOW_OTHER_DIRECTORIES) ||
+ /* We don't support collecting ignore files */
+ (dir->flags & (DIR_SHOW_IGNORED | DIR_SHOW_IGNORED_TOO |
+ DIR_COLLECT_IGNORED)))
+ return NULL;
+
+ /*
+ * If we use .gitignore in the cache and now you change it to
+ * .gitexclude, everything will go wrong.
+ */
+ if (dir->exclude_per_dir != dir->untracked->exclude_per_dir &&
+ strcmp(dir->exclude_per_dir, dir->untracked->exclude_per_dir))
+ return NULL;
+
+ /*
+ * EXC_CMDL is not considered in the cache. If people set it,
+ * skip the cache.
+ */
+ if (dir->exclude_list_group[EXC_CMDL].nr)
+ return NULL;
+
+ if (!dir->untracked->root) {
+ const int len = sizeof(*dir->untracked->root);
+ dir->untracked->root = xmalloc(len);
+ memset(dir->untracked->root, 0, len);
+ }
+
+ /* Validate $GIT_DIR/info/exclude and core.excludesfile */
+ root = dir->untracked->root;
+ if (hashcmp(dir->ss_info_exclude.sha1,
+ dir->untracked->ss_info_exclude.sha1)) {
+ invalidate_gitignore(dir->untracked, root);
+ dir->untracked->ss_info_exclude = dir->ss_info_exclude;
+ }
+ if (hashcmp(dir->ss_excludes_file.sha1,
+ dir->untracked->ss_excludes_file.sha1)) {
+ invalidate_gitignore(dir->untracked, root);
+ dir->untracked->ss_excludes_file = dir->ss_excludes_file;
+ }
+ return root;
+}
+
int read_directory(struct dir_struct *dir, const char *path, int len, const struct pathspec *pathspec)
{
struct path_simplify *simplify;
+ struct untracked_cache_dir *untracked;
/*
* Check out create_simplify()
* create_simplify().
*/
simplify = create_simplify(pathspec ? pathspec->_raw : NULL);
+ untracked = validate_untracked_cache(dir, len, pathspec);
+ if (!untracked)
+ /*
+ * make sure untracked cache code path is disabled,
+ * e.g. prep_exclude()
+ */
+ dir->untracked = NULL;
if (!len || treat_leading_path(dir, path, len, simplify))
- read_directory_recursive(dir, path, len, 0, simplify);
+ read_directory_recursive(dir, path, len, untracked, 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);
excludes_file = xdg_path;
}
if (!access_or_warn(path, R_OK, 0))
- add_excludes_from_file(dir, path);
+ add_excludes_from_file_1(dir, path,
+ dir->untracked ? &dir->ss_info_exclude : NULL);
if (excludes_file && !access_or_warn(excludes_file, R_OK, 0))
- add_excludes_from_file(dir, excludes_file);
+ add_excludes_from_file_1(dir, excludes_file,
+ dir->untracked ? &dir->ss_excludes_file : NULL);
}
int remove_path(const char *name)