#include "refs.h"
#include "wildmatch.h"
#include "pathspec.h"
+#include "utf8.h"
struct path_simplify {
int len;
int fnmatch_icase(const char *pattern, const char *string, int flags)
{
- return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0));
+ return wildmatch(pattern, string,
+ flags | (ignore_case ? WM_CASEFOLD : 0),
+ NULL);
}
-inline int git_fnmatch(const struct pathspec_item *item,
- const char *pattern, const char *string,
- int prefix)
+int git_fnmatch(const struct pathspec_item *item,
+ const char *pattern, const char *string,
+ int prefix)
{
if (prefix > 0) {
- if (strncmp(pattern, string, prefix))
- return FNM_NOMATCH;
+ if (ps_strncmp(item, pattern, string, prefix))
+ return WM_NOMATCH;
pattern += prefix;
string += prefix;
}
int pattern_len = strlen(++pattern);
int string_len = strlen(string);
return string_len < pattern_len ||
- strcmp(pattern,
- string + string_len - pattern_len);
+ ps_strcmp(item, pattern,
+ string + string_len - pattern_len);
}
if (item->magic & PATHSPEC_GLOB)
- return wildmatch(pattern, string, WM_PATHNAME, NULL);
+ return wildmatch(pattern, string,
+ WM_PATHNAME |
+ (item->magic & PATHSPEC_ICASE ? WM_CASEFOLD : 0),
+ NULL);
else
/* wildmatch has not learned no FNM_PATHNAME mode yet */
- return fnmatch(pattern, string, 0);
+ return wildmatch(pattern, string,
+ item->magic & PATHSPEC_ICASE ? WM_CASEFOLD : 0,
+ NULL);
}
static int fnmatch_icase_mem(const char *pattern, int patternlen,
int n;
size_t max = 0;
+ /*
+ * ":(icase)path" is treated as a pathspec full of
+ * wildcard. In other words, only prefix is considered common
+ * prefix. If the pathspec is abc/foo abc/bar, running in
+ * subdir xyz, the common prefix is still xyz, not xuz/abc as
+ * in non-:(icase).
+ */
GUARD_PATHSPEC(pathspec,
PATHSPEC_FROMTOP |
PATHSPEC_MAXDEPTH |
PATHSPEC_LITERAL |
- PATHSPEC_GLOB);
+ PATHSPEC_GLOB |
+ PATHSPEC_ICASE |
+ PATHSPEC_EXCLUDE);
for (n = 0; n < pathspec->nr; n++) {
- size_t i = 0, len = 0;
- while (i < pathspec->items[n].nowildcard_len &&
- (n == 0 || i < max)) {
+ size_t i = 0, len = 0, item_len;
+ if (pathspec->items[n].magic & PATHSPEC_EXCLUDE)
+ continue;
+ if (pathspec->items[n].magic & PATHSPEC_ICASE)
+ item_len = pathspec->items[n].prefix;
+ else
+ item_len = pathspec->items[n].nowildcard_len;
+ while (i < item_len && (n == 0 || i < max)) {
char c = pathspec->items[n].match[i];
if (c != pathspec->items[0].match[i])
break;
return 1;
}
+#define DO_MATCH_EXCLUDE 1
+#define DO_MATCH_DIRECTORY 2
+
/*
* Does 'match' match the given name?
* A match is found if
* It returns 0 when there is no match.
*/
static int match_pathspec_item(const struct pathspec_item *item, int prefix,
- const char *name, int namelen)
+ const char *name, int namelen, unsigned flags)
{
/* name/namelen has prefix cut off by caller */
const char *match = item->match + prefix;
int matchlen = item->len - prefix;
+ /*
+ * The normal call pattern is:
+ * 1. prefix = common_prefix_len(ps);
+ * 2. prune something, or fill_directory
+ * 3. match_pathspec()
+ *
+ * 'prefix' at #1 may be shorter than the command's prefix and
+ * it's ok for #2 to match extra files. Those extras will be
+ * trimmed at #3.
+ *
+ * Suppose the pathspec is 'foo' and '../bar' running from
+ * subdir 'xyz'. The common prefix at #1 will be empty, thanks
+ * to "../". We may have xyz/foo _and_ XYZ/foo after #2. The
+ * user does not want XYZ/foo, only the "foo" part should be
+ * case-insensitive. We need to filter out XYZ/foo here. In
+ * other words, we do not trust the caller on comparing the
+ * prefix part when :(icase) is involved. We do exact
+ * comparison ourselves.
+ *
+ * Normally the caller (common_prefix_len() in fact) does
+ * _exact_ matching on name[-prefix+1..-1] and we do not need
+ * to check that part. Be defensive and check it anyway, in
+ * case common_prefix_len is changed, or a new caller is
+ * introduced that does not use common_prefix_len.
+ *
+ * If the penalty turns out too high when prefix is really
+ * long, maybe change it to
+ * strncmp(match, name, item->prefix - prefix)
+ */
+ if (item->prefix && (item->magic & PATHSPEC_ICASE) &&
+ strncmp(item->match, name - prefix, item->prefix))
+ return 0;
+
/* If the match was just the prefix, we matched */
if (!*match)
return MATCHED_RECURSIVELY;
- if (matchlen <= namelen && !strncmp(match, name, matchlen)) {
+ if (matchlen <= namelen && !ps_strncmp(item, match, name, matchlen)) {
if (matchlen == namelen)
return MATCHED_EXACTLY;
if (match[matchlen-1] == '/' || name[matchlen] == '/')
return MATCHED_RECURSIVELY;
- }
+ } else if ((flags & DO_MATCH_DIRECTORY) &&
+ match[matchlen - 1] == '/' &&
+ namelen == matchlen - 1 &&
+ !ps_strncmp(item, match, name, namelen))
+ return MATCHED_EXACTLY;
if (item->nowildcard_len < item->len &&
!git_fnmatch(item, match, name,
* 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,
- int prefix, char *seen)
+static int do_match_pathspec(const struct pathspec *ps,
+ const char *name, int namelen,
+ int prefix, char *seen,
+ unsigned flags)
{
- int i, retval = 0;
+ int i, retval = 0, exclude = flags & DO_MATCH_EXCLUDE;
GUARD_PATHSPEC(ps,
PATHSPEC_FROMTOP |
PATHSPEC_MAXDEPTH |
PATHSPEC_LITERAL |
- PATHSPEC_GLOB);
+ PATHSPEC_GLOB |
+ PATHSPEC_ICASE |
+ PATHSPEC_EXCLUDE);
if (!ps->nr) {
if (!ps->recursive ||
for (i = ps->nr - 1; i >= 0; i--) {
int how;
+
+ if ((!exclude && ps->items[i].magic & PATHSPEC_EXCLUDE) ||
+ ( exclude && !(ps->items[i].magic & PATHSPEC_EXCLUDE)))
+ continue;
+
if (seen && seen[i] == MATCHED_EXACTLY)
continue;
- how = match_pathspec_item(ps->items+i, prefix, name, namelen);
+ /*
+ * Make exclude patterns optional and never report
+ * "pathspec ':(exclude)foo' matches no files"
+ */
+ if (seen && ps->items[i].magic & PATHSPEC_EXCLUDE)
+ seen[i] = MATCHED_FNMATCH;
+ how = match_pathspec_item(ps->items+i, prefix, name,
+ namelen, flags);
if (ps->recursive &&
(ps->magic & PATHSPEC_MAXDEPTH) &&
ps->max_depth != -1 &&
return retval;
}
+int match_pathspec(const struct pathspec *ps,
+ const char *name, int namelen,
+ int prefix, char *seen, int is_dir)
+{
+ int positive, negative;
+ unsigned flags = is_dir ? DO_MATCH_DIRECTORY : 0;
+ positive = do_match_pathspec(ps, name, namelen,
+ prefix, seen, flags);
+ if (!(ps->magic & PATHSPEC_EXCLUDE) || !positive)
+ return positive;
+ negative = do_match_pathspec(ps, name, namelen,
+ prefix, seen,
+ flags | DO_MATCH_EXCLUDE);
+ return negative ? 0 : positive;
+}
+
+int report_path_error(const char *ps_matched,
+ const struct pathspec *pathspec,
+ const char *prefix)
+{
+ /*
+ * Make sure all pathspec matched; otherwise it is an error.
+ */
+ int num, errors = 0;
+ for (num = 0; num < pathspec->nr; num++) {
+ int other, found_dup;
+
+ if (ps_matched[num])
+ continue;
+ /*
+ * The caller might have fed identical pathspec
+ * twice. Do not barf on such a mistake.
+ * FIXME: parse_pathspec should have eliminated
+ * duplicate pathspec.
+ */
+ for (found_dup = other = 0;
+ !found_dup && other < pathspec->nr;
+ other++) {
+ if (other == num || !ps_matched[other])
+ continue;
+ if (!strcmp(pathspec->items[other].original,
+ pathspec->items[num].original))
+ /*
+ * Ok, we have a match already.
+ */
+ found_dup = 1;
+ }
+ if (found_dup)
+ continue;
+
+ error("pathspec '%s' did not match any file(s) known to git.",
+ pathspec->items[num].original);
+ errors++;
+ }
+ return errors;
+}
+
/*
* Return the length of the "simple" part of a path match limiter.
*/
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);
+ pos = cache_name_pos(path, len);
if (pos < 0)
return NULL;
- if (!ce_skip_worktree(istate->cache[pos]))
+ if (!ce_skip_worktree(active_cache[pos]))
return NULL;
- data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz);
+ data = read_sha1_file(active_cache[pos]->sha1, &type, &sz);
if (!data || type != OBJ_BLOB) {
free(data);
return NULL;
el->filebuf = NULL;
}
+static void trim_trailing_spaces(char *buf)
+{
+ char *p, *last_space = NULL;
+
+ for (p = buf; *p; p++)
+ switch (*p) {
+ case ' ':
+ if (!last_space)
+ last_space = p;
+ break;
+ case '\\':
+ p++;
+ if (!*p)
+ return;
+ /* fallthrough */
+ default:
+ last_space = NULL;
+ }
+
+ if (last_space)
+ *last_space = '\0';
+}
+
int add_excludes_from_file_to_list(const char *fname,
const char *base,
int baselen,
buf = xrealloc(buf, size+1);
buf[size++] = '\n';
}
- }
- else {
+ } else {
size = xsize_t(st.st_size);
if (size == 0) {
close(fd);
}
el->filebuf = buf;
+
+ if (skip_utf8_bom(&buf, size))
+ size -= buf - el->filebuf;
+
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;
+ trim_trailing_spaces(entry);
add_exclude(entry, base, baselen, el, lineno);
}
lineno++;
group = &dir->exclude_list_group[EXC_DIRS];
- /* Pop the exclude lists from the EXCL_DIRS exclude_list_group
+ /*
+ * Pop the exclude lists from the EXCL_DIRS exclude_list_group
* which originate from directories not in the prefix of the
- * path being checked. */
+ * path being checked.
+ */
while ((stk = dir->exclude_stack) != NULL) {
if (stk->baselen <= baselen &&
- !strncmp(dir->basebuf, base, stk->baselen))
+ !strncmp(dir->basebuf.buf, base, stk->baselen))
break;
el = &group->el[dir->exclude_stack->exclude_ix];
dir->exclude_stack = stk->prev;
dir->exclude = NULL;
- free((char *)el->src); /* see strdup() below */
+ free((char *)el->src); /* see strbuf_detach() below */
clear_exclude_list(el);
free(stk);
group->nr--;
if (dir->exclude)
return;
+ /*
+ * Lazy initialization. All call sites currently just
+ * memset(dir, 0, sizeof(*dir)) before use. Changing all of
+ * them seems lots of work for little benefit.
+ */
+ if (!dir->basebuf.buf)
+ strbuf_init(&dir->basebuf, PATH_MAX);
+
/* Read from the parent directories and push them down. */
current = stk ? stk->baselen : -1;
+ strbuf_setlen(&dir->basebuf, current < 0 ? 0 : current);
while (current < baselen) {
- struct exclude_stack *stk = xcalloc(1, sizeof(*stk));
const char *cp;
+ stk = xcalloc(1, sizeof(*stk));
if (current < 0) {
cp = base;
current = 0;
- }
- else {
+ } else {
cp = strchr(base + current + 1, '/');
if (!cp)
die("oops in prep_exclude");
stk->baselen = cp - base;
stk->exclude_ix = group->nr;
el = add_exclude_list(dir, EXC_DIRS, NULL);
- memcpy(dir->basebuf + current, base + current,
- stk->baselen - current);
+ strbuf_add(&dir->basebuf, base + current, stk->baselen - current);
+ assert(stk->baselen == dir->basebuf.len);
/* Abort if the directory is excluded */
if (stk->baselen) {
int dt = DT_DIR;
- dir->basebuf[stk->baselen - 1] = 0;
+ dir->basebuf.buf[stk->baselen - 1] = 0;
dir->exclude = last_exclude_matching_from_lists(dir,
- dir->basebuf, stk->baselen - 1,
- dir->basebuf + current, &dt);
- dir->basebuf[stk->baselen - 1] = '/';
+ dir->basebuf.buf, stk->baselen - 1,
+ dir->basebuf.buf + current, &dt);
+ dir->basebuf.buf[stk->baselen - 1] = '/';
if (dir->exclude &&
dir->exclude->flags & EXC_FLAG_NEGATIVE)
dir->exclude = NULL;
if (dir->exclude) {
- dir->basebuf[stk->baselen] = 0;
dir->exclude_stack = stk;
return;
}
}
- /* Try to read per-directory file unless path is too long */
- if (dir->exclude_per_dir &&
- stk->baselen + strlen(dir->exclude_per_dir) < PATH_MAX) {
- strcpy(dir->basebuf + stk->baselen,
- dir->exclude_per_dir);
+ /* Try to read per-directory file */
+ if (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.
+ * strbuf_detach() and free() here in the caller.
*/
- el->src = strdup(dir->basebuf);
- add_excludes_from_file_to_list(dir->basebuf,
- dir->basebuf, stk->baselen, el, 1);
+ struct strbuf sb = STRBUF_INIT;
+ 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);
}
dir->exclude_stack = stk;
current = stk->baselen;
}
- dir->basebuf[baselen] = '\0';
+ strbuf_setlen(&dir->basebuf, baselen);
}
/*
static struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)
{
- if (cache_name_exists(pathname, len, ignore_case))
+ if (cache_file_exists(pathname, len, ignore_case))
return NULL;
ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc);
};
/*
- * Do not use the alphabetically stored index to look up
+ * Do not use the alphabetically sorted index to look up
* the directory name; instead, use the case insensitive
- * name hash.
+ * directory hash.
*/
static enum exist_status directory_exists_in_index_icase(const char *dirname, int len)
{
- struct cache_entry *ce = index_name_exists(&the_index, dirname, len + 1, ignore_case);
+ const struct cache_entry *ce = cache_dir_exists(dirname, len);
unsigned char endchar;
if (!ce)
if (pos < 0)
pos = -pos-1;
while (pos < active_nr) {
- struct cache_entry *ce = active_cache[pos++];
+ const struct cache_entry *ce = active_cache[pos++];
unsigned char endchar;
if (strncmp(ce->name, dirname, len))
static int get_index_dtype(const char *path, int len)
{
int pos;
- struct cache_entry *ce;
+ const struct cache_entry *ce;
- ce = cache_name_exists(path, len, 0);
+ ce = cache_file_exists(path, len, 0);
if (ce) {
if (!ce_uptodate(ce))
return DT_UNKNOWN;
int dtype, struct dirent *de)
{
int exclude;
+ int has_path_in_index = !!cache_file_exists(path->buf, path->len, ignore_case);
+
if (dtype == DT_UNKNOWN)
dtype = get_dtype(de, path->buf, path->len);
/* Always exclude indexed files */
- if (dtype != DT_DIR &&
- cache_name_exists(path->buf, path->len, ignore_case))
+ if (dtype != DT_DIR && has_path_in_index)
+ return path_none;
+
+ /*
+ * When we are looking at a directory P in the working tree,
+ * there are three cases:
+ *
+ * (1) P exists in the index. Everything inside the directory P in
+ * the working tree needs to go when P is checked out from the
+ * index.
+ *
+ * (2) P does not exist in the index, but there is P/Q in the index.
+ * We know P will stay a directory when we check out the contents
+ * of the index, but we do not know yet if there is a directory
+ * P/Q in the working tree to be killed, so we need to recurse.
+ *
+ * (3) P does not exist in the index, and there is no P/Q in the index
+ * to require P to be a directory, either. Only in this case, we
+ * know that everything inside P will not be killed without
+ * recursing.
+ */
+ if ((dir->flags & DIR_COLLECT_KILLED_ONLY) &&
+ (dtype == DT_DIR) &&
+ !has_path_in_index &&
+ (directory_exists_in_index(path->buf, path->len) == index_nonexistent))
return path_none;
exclude = is_excluded(dir, path->buf, &dtype);
const struct dir_entry *e1 = *(const struct dir_entry **)p1;
const struct dir_entry *e2 = *(const struct dir_entry **)p2;
- return cache_name_compare(e1->name, e1->len,
- e2->name, e2->len);
+ return name_compare(e1->name, e1->len, e2->name, e2->len);
}
static struct path_simplify *create_simplify(const char **pathspec)
for (nr = 0 ; ; nr++) {
const char *match;
- if (nr >= alloc) {
- alloc = alloc_nr(alloc);
- simplify = xrealloc(simplify, alloc * sizeof(*simplify));
- }
+ ALLOC_GROW(simplify, nr + 1, alloc);
match = *pathspec++;
if (!match)
break;
PATHSPEC_FROMTOP |
PATHSPEC_MAXDEPTH |
PATHSPEC_LITERAL |
- PATHSPEC_GLOB);
+ PATHSPEC_GLOB |
+ PATHSPEC_ICASE |
+ PATHSPEC_EXCLUDE);
if (has_symlink_leading_path(path, len))
return dir->nr;
+ /*
+ * 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
+ * create_simplify().
+ */
simplify = create_simplify(pathspec ? pathspec->_raw : NULL);
if (!len || treat_leading_path(dir, path, len, simplify))
read_directory_recursive(dir, path, len, 0, simplify);
int is_inside_dir(const char *dir)
{
- char cwd[PATH_MAX];
+ char *cwd;
+ int rc;
+
if (!dir)
return 0;
- if (!getcwd(cwd, sizeof(cwd)))
- die_errno("can't find the current directory");
- return dir_inside_of(cwd, dir) >= 0;
+
+ cwd = xgetcwd();
+ rc = (dir_inside_of(cwd, dir) >= 0);
+ free(cwd);
+ return rc;
}
int is_empty_dir(const char *path)
flag &= ~REMOVE_DIR_KEEP_TOPLEVEL;
dir = opendir(path->buf);
if (!dir) {
- /* an empty dir could be removed even if it is unreadble */
- if (!keep_toplevel)
+ if (errno == ENOENT)
+ return keep_toplevel ? -1 : 0;
+ else if (errno == EACCES && !keep_toplevel)
+ /*
+ * An empty dir could be removable even if it
+ * is unreadable:
+ */
return rmdir(path->buf);
else
return -1;
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 (lstat(path->buf, &st)) {
+ if (errno == ENOENT)
+ /*
+ * file disappeared, which is what we
+ * wanted anyway
+ */
+ continue;
+ /* fall thru */
+ } else if (S_ISDIR(st.st_mode)) {
if (!remove_dir_recurse(path, flag, &kept_down))
continue; /* happy */
- } else if (!only_empty && !unlink(path->buf))
+ } else if (!only_empty &&
+ (!unlink(path->buf) || errno == ENOENT)) {
continue; /* happy, too */
+ }
/* path too long, stat fails, or non-directory still exists */
ret = -1;
strbuf_setlen(path, original_len);
if (!ret && !keep_toplevel && !kept_down)
- ret = rmdir(path->buf);
+ ret = (!rmdir(path->buf) || errno == ENOENT) ? 0 : -1;
else if (kept_up)
/*
* report the uplevel that it is not an error that we
void setup_standard_excludes(struct dir_struct *dir)
{
const char *path;
- char *xdg_path;
dir->exclude_per_dir = ".gitignore";
+
+ /* core.excludefile defaulting to $XDG_HOME/git/ignore */
+ if (!excludes_file)
+ excludes_file = xdg_config_home("ignore");
+ if (excludes_file && !access_or_warn(excludes_file, R_OK, 0))
+ add_excludes_from_file(dir, excludes_file);
+
+ /* per repository user preference */
path = git_path("info/exclude");
- if (!excludes_file) {
- home_config_paths(NULL, &xdg_path, "ignore");
- excludes_file = xdg_path;
- }
if (!access_or_warn(path, R_OK, 0))
add_excludes_from_file(dir, path);
- if (excludes_file && !access_or_warn(excludes_file, R_OK, 0))
- add_excludes_from_file(dir, excludes_file);
}
int remove_path(const char *name)
free(stk);
stk = prev;
}
+ strbuf_release(&dir->basebuf);
}