summaryrefslogtreecommitdiff
path: root/pathspec.c
diff options
context:
space:
mode:
Diffstat (limited to 'pathspec.c')
-rw-r--r--pathspec.c227
1 files changed, 189 insertions, 38 deletions
diff --git a/pathspec.c b/pathspec.c
index 128f27f..2133b9f 100644
--- a/pathspec.c
+++ b/pathspec.c
@@ -1,10 +1,18 @@
-#include "cache.h"
-#include "config.h"
+#include "git-compat-util.h"
+#include "abspath.h"
+#include "parse.h"
#include "dir.h"
+#include "environment.h"
+#include "gettext.h"
#include "pathspec.h"
#include "attr.h"
-#include "argv-array.h"
+#include "read-cache.h"
+#include "repository.h"
+#include "setup.h"
+#include "strvec.h"
+#include "symlinks.h"
#include "quote.h"
+#include "wildmatch.h"
/*
* Finds which of the given pathspecs match items in the index.
@@ -20,8 +28,9 @@
* to use find_pathspecs_matching_against_index() instead.
*/
void add_pathspec_matches_against_index(const struct pathspec *pathspec,
- const struct index_state *istate,
- char *seen)
+ struct index_state *istate,
+ char *seen,
+ enum ps_skip_worktree_action sw_action)
{
int num_unmatched = 0, i;
@@ -38,6 +47,9 @@ void add_pathspec_matches_against_index(const struct pathspec *pathspec,
return;
for (i = 0; i < istate->cache_nr; i++) {
const struct cache_entry *ce = istate->cache[i];
+ if (sw_action == PS_IGNORE_SKIP_WORKTREE &&
+ (ce_skip_worktree(ce) || !path_in_sparse_checkout(ce->name, istate)))
+ continue;
ce_path_match(istate, ce, pathspec, seen);
}
}
@@ -51,10 +63,26 @@ void add_pathspec_matches_against_index(const struct pathspec *pathspec,
* given pathspecs achieves against all items in the index.
*/
char *find_pathspecs_matching_against_index(const struct pathspec *pathspec,
- const struct index_state *istate)
+ struct index_state *istate,
+ enum ps_skip_worktree_action sw_action)
{
char *seen = xcalloc(pathspec->nr, 1);
- add_pathspec_matches_against_index(pathspec, istate, seen);
+ add_pathspec_matches_against_index(pathspec, istate, seen, sw_action);
+ return seen;
+}
+
+char *find_pathspecs_matching_skip_worktree(const struct pathspec *pathspec)
+{
+ struct index_state *istate = the_repository->index;
+ char *seen = xcalloc(pathspec->nr, 1);
+ int i;
+
+ for (i = 0; i < istate->cache_nr; i++) {
+ struct cache_entry *ce = istate->cache[i];
+ if (ce_skip_worktree(ce) || !path_in_sparse_checkout(ce->name, istate))
+ ce_path_match(istate, ce, pathspec, seen);
+ }
+
return seen;
}
@@ -81,16 +109,37 @@ static struct pathspec_magic {
{ PATHSPEC_ATTR, '\0', "attr" },
};
-static void prefix_magic(struct strbuf *sb, int prefixlen, unsigned magic)
+static void prefix_magic(struct strbuf *sb, int prefixlen,
+ unsigned magic, const char *element)
{
- int i;
- strbuf_addstr(sb, ":(");
- for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
- if (magic & pathspec_magic[i].bit) {
- if (sb->buf[sb->len - 1] != '(')
- strbuf_addch(sb, ',');
- strbuf_addstr(sb, pathspec_magic[i].name);
+ /* No magic was found in element, just add prefix magic */
+ if (!magic) {
+ strbuf_addf(sb, ":(prefix:%d)", prefixlen);
+ return;
+ }
+
+ /*
+ * At this point, we know that parse_element_magic() was able
+ * to extract some pathspec magic from element. So we know
+ * element is correctly formatted in either shorthand or
+ * longhand form
+ */
+ if (element[1] != '(') {
+ /* Process an element in shorthand form (e.g. ":!/<match>") */
+ strbuf_addstr(sb, ":(");
+ for (int i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
+ if ((magic & pathspec_magic[i].bit) &&
+ pathspec_magic[i].mnemonic) {
+ if (sb->buf[sb->len - 1] != '(')
+ strbuf_addch(sb, ',');
+ strbuf_addstr(sb, pathspec_magic[i].name);
+ }
}
+ } else {
+ /* For the longhand form, we copy everything up to the final ')' */
+ size_t len = strchr(element, ')') - element;
+ strbuf_add(sb, element, len);
+ }
strbuf_addf(sb, ",prefix:%d)", prefixlen);
}
@@ -154,7 +203,7 @@ static void parse_pathspec_attr_match(struct pathspec_item *item, const char *va
string_list_remove_empty_items(&list, 0);
item->attr_check = attr_check_alloc();
- item->attr_match = xcalloc(list.nr, sizeof(struct attr_match));
+ CALLOC_ARRAY(item->attr_match, list.nr);
for_each_string_list_item(si, &list) {
size_t attr_len;
@@ -438,8 +487,18 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags,
} else {
match = prefix_path_gently(prefix, prefixlen,
&prefixlen, copyfrom);
- if (!match)
- die(_("%s: '%s' is outside repository"), elt, copyfrom);
+ if (!match) {
+ const char *hint_path;
+
+ if (!have_git_dir())
+ die(_("'%s' is outside the directory tree"),
+ copyfrom);
+ hint_path = get_git_work_tree();
+ if (!hint_path)
+ hint_path = get_git_dir();
+ die(_("%s: '%s' is outside repository at '%s'"), elt,
+ copyfrom, absolute_path(hint_path));
+ }
}
item->match = match;
@@ -455,7 +514,7 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags,
struct strbuf sb = STRBUF_INIT;
/* Preserve the actual prefix length of each pattern */
- prefix_magic(&sb, prefixlen, element_magic);
+ prefix_magic(&sb, prefixlen, element_magic, elt);
strbuf_addstr(&sb, match);
item->original = strbuf_detach(&sb, NULL);
@@ -500,27 +559,32 @@ static int pathspec_item_cmp(const void *a_, const void *b_)
return strcmp(a->match, b->match);
}
-static void NORETURN unsupported_magic(const char *pattern,
- unsigned magic)
+void pathspec_magic_names(unsigned magic, struct strbuf *out)
{
- struct strbuf sb = STRBUF_INIT;
int i;
for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
const struct pathspec_magic *m = pathspec_magic + i;
if (!(magic & m->bit))
continue;
- if (sb.len)
- strbuf_addstr(&sb, ", ");
+ if (out->len)
+ strbuf_addstr(out, ", ");
if (m->mnemonic)
- strbuf_addf(&sb, _("'%s' (mnemonic: '%c')"),
+ strbuf_addf(out, _("'%s' (mnemonic: '%c')"),
m->name, m->mnemonic);
else
- strbuf_addf(&sb, "'%s'", m->name);
+ strbuf_addf(out, "'%s'", m->name);
}
+}
+
+static void NORETURN unsupported_magic(const char *pattern,
+ unsigned magic)
+{
+ struct strbuf sb = STRBUF_INIT;
+ pathspec_magic_names(magic, &sb);
/*
* We may want to substitute "this command" with a command
- * name. E.g. when add--interactive dies when running
+ * name. E.g. when "git add -p" or "git add -i" dies when running
* "checkout -p"
*/
die(_("%s: pathspec magic not supported by this command: %s"),
@@ -556,7 +620,7 @@ void parse_pathspec(struct pathspec *pathspec,
if (!(flags & PATHSPEC_PREFER_CWD))
BUG("PATHSPEC_PREFER_CWD requires arguments");
- pathspec->items = item = xcalloc(1, sizeof(*item));
+ pathspec->items = CALLOC_ARRAY(item, 1);
item->match = xstrdup(prefix);
item->original = xstrdup(prefix);
item->nowildcard_len = item->len = strlen(prefix);
@@ -604,7 +668,7 @@ void parse_pathspec(struct pathspec *pathspec,
*/
if (nr_exclude == n) {
int plen = (!(flags & PATHSPEC_PREFER_CWD)) ? 0 : prefixlen;
- init_pathspec_item(item + n, 0, prefix, plen, "");
+ init_pathspec_item(item + n, 0, prefix, plen, ".");
pathspec->nr++;
}
@@ -619,7 +683,7 @@ void parse_pathspec_file(struct pathspec *pathspec, unsigned magic_mask,
unsigned flags, const char *prefix,
const char *file, int nul_term_line)
{
- struct argv_array parsed_file = ARGV_ARRAY_INIT;
+ struct strvec parsed_file = STRVEC_INIT;
strbuf_getline_fn getline_fn = nul_term_line ? strbuf_getline_nul :
strbuf_getline;
struct strbuf buf = STRBUF_INIT;
@@ -638,7 +702,7 @@ void parse_pathspec_file(struct pathspec *pathspec, unsigned magic_mask,
die(_("line is badly quoted: %s"), buf.buf);
strbuf_swap(&buf, &unquoted);
}
- argv_array_push(&parsed_file, buf.buf);
+ strvec_push(&parsed_file, buf.buf);
strbuf_reset(&buf);
}
@@ -647,8 +711,8 @@ void parse_pathspec_file(struct pathspec *pathspec, unsigned magic_mask,
if (in != stdin)
fclose(in);
- parse_pathspec(pathspec, magic_mask, flags, prefix, parsed_file.argv);
- argv_array_clear(&parsed_file);
+ parse_pathspec(pathspec, magic_mask, flags, prefix, parsed_file.v);
+ strvec_clear(&parsed_file);
}
void copy_pathspec(struct pathspec *dst, const struct pathspec *src)
@@ -656,8 +720,7 @@ void copy_pathspec(struct pathspec *dst, const struct pathspec *src)
int i, j;
*dst = *src;
- ALLOC_ARRAY(dst->items, dst->nr);
- COPY_ARRAY(dst->items, src->items, dst->nr);
+ DUP_ARRAY(dst->items, src->items, dst->nr);
for (i = 0; i < dst->nr; i++) {
struct pathspec_item *d = &dst->items[i];
@@ -666,8 +729,7 @@ void copy_pathspec(struct pathspec *dst, const struct pathspec *src)
d->match = xstrdup(s->match);
d->original = xstrdup(s->original);
- ALLOC_ARRAY(d->attr_match, d->attr_match_nr);
- COPY_ARRAY(d->attr_match, s->attr_match, d->attr_match_nr);
+ DUP_ARRAY(d->attr_match, s->attr_match, d->attr_match_nr);
for (j = 0; j < d->attr_match_nr; j++) {
const char *value = s->attr_match[j].value;
d->attr_match[j].value = xstrdup_or_null(value);
@@ -697,7 +759,7 @@ void clear_pathspec(struct pathspec *pathspec)
pathspec->nr = 0;
}
-int match_pathspec_attrs(const struct index_state *istate,
+int match_pathspec_attrs(struct index_state *istate,
const char *name, int namelen,
const struct pathspec_item *item)
{
@@ -734,3 +796,92 @@ int match_pathspec_attrs(const struct index_state *istate,
return 1;
}
+
+int pathspec_needs_expanded_index(struct index_state *istate,
+ const struct pathspec *pathspec)
+{
+ unsigned int i, pos;
+ int res = 0;
+ char *skip_worktree_seen = NULL;
+
+ /*
+ * If index is not sparse, no index expansion is needed.
+ */
+ if (!istate->sparse_index)
+ return 0;
+
+ /*
+ * When using a magic pathspec, assume for the sake of simplicity that
+ * the index needs to be expanded to match all matchable files.
+ */
+ if (pathspec->magic)
+ return 1;
+
+ for (i = 0; i < pathspec->nr; i++) {
+ struct pathspec_item item = pathspec->items[i];
+
+ /*
+ * If the pathspec item has a wildcard, the index should be expanded
+ * if the pathspec has the possibility of matching a subset of entries inside
+ * of a sparse directory (but not the entire directory).
+ *
+ * If the pathspec item is a literal path, the index only needs to be expanded
+ * if a) the pathspec isn't in the sparse checkout cone (to make sure we don't
+ * expand for in-cone files) and b) it doesn't match any sparse directories
+ * (since we can reset whole sparse directories without expanding them).
+ */
+ if (item.nowildcard_len < item.len) {
+ /*
+ * Special case: if the pattern is a path inside the cone
+ * followed by only wildcards, the pattern cannot match
+ * partial sparse directories, so we know we don't need to
+ * expand the index.
+ *
+ * Examples:
+ * - in-cone/foo***: doesn't need expanded index
+ * - not-in-cone/bar*: may need expanded index
+ * - **.c: may need expanded index
+ */
+ if (strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len &&
+ path_in_cone_mode_sparse_checkout(item.original, istate))
+ continue;
+
+ for (pos = 0; pos < istate->cache_nr; pos++) {
+ struct cache_entry *ce = istate->cache[pos];
+
+ if (!S_ISSPARSEDIR(ce->ce_mode))
+ continue;
+
+ /*
+ * If the pre-wildcard length is longer than the sparse
+ * directory name and the sparse directory is the first
+ * component of the pathspec, need to expand the index.
+ */
+ if (item.nowildcard_len > ce_namelen(ce) &&
+ !strncmp(item.original, ce->name, ce_namelen(ce))) {
+ res = 1;
+ break;
+ }
+
+ /*
+ * If the pre-wildcard length is shorter than the sparse
+ * directory and the pathspec does not match the whole
+ * directory, need to expand the index.
+ */
+ if (!strncmp(item.original, ce->name, item.nowildcard_len) &&
+ wildmatch(item.original, ce->name, 0)) {
+ res = 1;
+ break;
+ }
+ }
+ } else if (!path_in_cone_mode_sparse_checkout(item.original, istate) &&
+ !matches_skip_worktree(pathspec, i, &skip_worktree_seen))
+ res = 1;
+
+ if (res > 0)
+ break;
+ }
+
+ free(skip_worktree_seen);
+ return res;
+}