зеркало из https://github.com/microsoft/git.git
Support pathspec magic :(exclude) and its short form :!
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Родитель
8b7cb51a9d
Коммит
ef79b1f870
|
@ -379,6 +379,11 @@ full pathname may have special meaning:
|
|||
- Other consecutive asterisks are considered invalid.
|
||||
+
|
||||
Glob magic is incompatible with literal magic.
|
||||
|
||||
exclude;;
|
||||
After a path matches any non-exclude pathspec, it will be run
|
||||
through all exclude pathspec (magic signature: `!`). If it
|
||||
matches, the path is ignored.
|
||||
--
|
||||
|
||||
[[def_parent]]parent::
|
||||
|
|
|
@ -540,10 +540,13 @@ int cmd_add(int argc, const char **argv, const char *prefix)
|
|||
PATHSPEC_FROMTOP |
|
||||
PATHSPEC_LITERAL |
|
||||
PATHSPEC_GLOB |
|
||||
PATHSPEC_ICASE);
|
||||
PATHSPEC_ICASE |
|
||||
PATHSPEC_EXCLUDE);
|
||||
|
||||
for (i = 0; i < pathspec.nr; i++) {
|
||||
const char *path = pathspec.items[i].match;
|
||||
if (pathspec.items[i].magic & PATHSPEC_EXCLUDE)
|
||||
continue;
|
||||
if (!seen[i] &&
|
||||
((pathspec.items[i].magic &
|
||||
(PATHSPEC_GLOB | PATHSPEC_ICASE)) ||
|
||||
|
|
47
dir.c
47
dir.c
|
@ -126,10 +126,13 @@ static size_t common_prefix_len(const struct pathspec *pathspec)
|
|||
PATHSPEC_MAXDEPTH |
|
||||
PATHSPEC_LITERAL |
|
||||
PATHSPEC_GLOB |
|
||||
PATHSPEC_ICASE);
|
||||
PATHSPEC_ICASE |
|
||||
PATHSPEC_EXCLUDE);
|
||||
|
||||
for (n = 0; n < pathspec->nr; n++) {
|
||||
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
|
||||
|
@ -279,9 +282,10 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix,
|
|||
* 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 match_pathspec_depth_1(const struct pathspec *ps,
|
||||
const char *name, int namelen,
|
||||
int prefix, char *seen,
|
||||
int exclude)
|
||||
{
|
||||
int i, retval = 0;
|
||||
|
||||
|
@ -290,7 +294,8 @@ int match_pathspec_depth(const struct pathspec *ps,
|
|||
PATHSPEC_MAXDEPTH |
|
||||
PATHSPEC_LITERAL |
|
||||
PATHSPEC_GLOB |
|
||||
PATHSPEC_ICASE);
|
||||
PATHSPEC_ICASE |
|
||||
PATHSPEC_EXCLUDE);
|
||||
|
||||
if (!ps->nr) {
|
||||
if (!ps->recursive ||
|
||||
|
@ -309,8 +314,19 @@ int match_pathspec_depth(const struct pathspec *ps,
|
|||
|
||||
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;
|
||||
/*
|
||||
* 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);
|
||||
if (ps->recursive &&
|
||||
(ps->magic & PATHSPEC_MAXDEPTH) &&
|
||||
|
@ -334,6 +350,18 @@ int match_pathspec_depth(const struct pathspec *ps,
|
|||
return retval;
|
||||
}
|
||||
|
||||
int match_pathspec_depth(const struct pathspec *ps,
|
||||
const char *name, int namelen,
|
||||
int prefix, char *seen)
|
||||
{
|
||||
int positive, negative;
|
||||
positive = match_pathspec_depth_1(ps, name, namelen, prefix, seen, 0);
|
||||
if (!(ps->magic & PATHSPEC_EXCLUDE) || !positive)
|
||||
return positive;
|
||||
negative = match_pathspec_depth_1(ps, name, namelen, prefix, seen, 1);
|
||||
return negative ? 0 : positive;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the length of the "simple" part of a path match limiter.
|
||||
*/
|
||||
|
@ -1375,11 +1403,18 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru
|
|||
PATHSPEC_MAXDEPTH |
|
||||
PATHSPEC_LITERAL |
|
||||
PATHSPEC_GLOB |
|
||||
PATHSPEC_ICASE);
|
||||
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);
|
||||
|
|
|
@ -71,6 +71,7 @@ static struct pathspec_magic {
|
|||
{ PATHSPEC_LITERAL, 0, "literal" },
|
||||
{ PATHSPEC_GLOB, '\0', "glob" },
|
||||
{ PATHSPEC_ICASE, '\0', "icase" },
|
||||
{ PATHSPEC_EXCLUDE, '!', "exclude" },
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -355,7 +356,7 @@ void parse_pathspec(struct pathspec *pathspec,
|
|||
{
|
||||
struct pathspec_item *item;
|
||||
const char *entry = argv ? *argv : NULL;
|
||||
int i, n, prefixlen;
|
||||
int i, n, prefixlen, nr_exclude = 0;
|
||||
|
||||
memset(pathspec, 0, sizeof(*pathspec));
|
||||
|
||||
|
@ -412,6 +413,8 @@ void parse_pathspec(struct pathspec *pathspec,
|
|||
if ((flags & PATHSPEC_LITERAL_PATH) &&
|
||||
!(magic_mask & PATHSPEC_LITERAL))
|
||||
item[i].magic |= PATHSPEC_LITERAL;
|
||||
if (item[i].magic & PATHSPEC_EXCLUDE)
|
||||
nr_exclude++;
|
||||
if (item[i].magic & magic_mask)
|
||||
unsupported_magic(entry,
|
||||
item[i].magic & magic_mask,
|
||||
|
@ -427,6 +430,10 @@ void parse_pathspec(struct pathspec *pathspec,
|
|||
pathspec->magic |= item[i].magic;
|
||||
}
|
||||
|
||||
if (nr_exclude == n)
|
||||
die(_("There is nothing to exclude from by :(exclude) patterns.\n"
|
||||
"Perhaps you forgot to add either ':/' or '.' ?"));
|
||||
|
||||
|
||||
if (pathspec->magic & PATHSPEC_MAXDEPTH) {
|
||||
if (flags & PATHSPEC_KEEP_ORDER)
|
||||
|
|
|
@ -7,12 +7,14 @@
|
|||
#define PATHSPEC_LITERAL (1<<2)
|
||||
#define PATHSPEC_GLOB (1<<3)
|
||||
#define PATHSPEC_ICASE (1<<4)
|
||||
#define PATHSPEC_EXCLUDE (1<<5)
|
||||
#define PATHSPEC_ALL_MAGIC \
|
||||
(PATHSPEC_FROMTOP | \
|
||||
PATHSPEC_MAXDEPTH | \
|
||||
PATHSPEC_LITERAL | \
|
||||
PATHSPEC_GLOB | \
|
||||
PATHSPEC_ICASE)
|
||||
PATHSPEC_ICASE | \
|
||||
PATHSPEC_EXCLUDE)
|
||||
|
||||
#define PATHSPEC_ONESTAR 1 /* the pathspec pattern satisfies GFNM_ONESTAR */
|
||||
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='test case exclude pathspec'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success 'setup' '
|
||||
for p in file sub/file sub/sub/file sub/file2 sub/sub/sub/file sub2/file; do
|
||||
if echo $p | grep /; then
|
||||
mkdir -p `dirname $p`
|
||||
fi &&
|
||||
: >$p &&
|
||||
git add $p &&
|
||||
git commit -m $p
|
||||
done &&
|
||||
git log --oneline --format=%s >actual &&
|
||||
cat <<EOF >expect &&
|
||||
sub2/file
|
||||
sub/sub/sub/file
|
||||
sub/file2
|
||||
sub/sub/file
|
||||
sub/file
|
||||
file
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'exclude only should error out' '
|
||||
test_must_fail git log --oneline --format=%s -- ":(exclude)sub"
|
||||
'
|
||||
|
||||
test_expect_success 't_e_i() exclude sub' '
|
||||
git log --oneline --format=%s -- . ":(exclude)sub" >actual
|
||||
cat <<EOF >expect &&
|
||||
sub2/file
|
||||
file
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 't_e_i() exclude sub/sub/file' '
|
||||
git log --oneline --format=%s -- . ":(exclude)sub/sub/file" >actual
|
||||
cat <<EOF >expect &&
|
||||
sub2/file
|
||||
sub/sub/sub/file
|
||||
sub/file2
|
||||
sub/file
|
||||
file
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 't_e_i() exclude sub using mnemonic' '
|
||||
git log --oneline --format=%s -- . ":!sub" >actual
|
||||
cat <<EOF >expect &&
|
||||
sub2/file
|
||||
file
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 't_e_i() exclude :(icase)SUB' '
|
||||
git log --oneline --format=%s -- . ":(exclude,icase)SUB" >actual
|
||||
cat <<EOF >expect &&
|
||||
sub2/file
|
||||
file
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 't_e_i() exclude sub2 from sub' '
|
||||
(
|
||||
cd sub &&
|
||||
git log --oneline --format=%s -- :/ ":/!sub2" >actual
|
||||
cat <<EOF >expect &&
|
||||
sub/sub/sub/file
|
||||
sub/file2
|
||||
sub/sub/file
|
||||
sub/file
|
||||
file
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 't_e_i() exclude sub/*file' '
|
||||
git log --oneline --format=%s -- . ":(exclude)sub/*file" >actual
|
||||
cat <<EOF >expect &&
|
||||
sub2/file
|
||||
sub/file2
|
||||
file
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 't_e_i() exclude :(glob)sub/*/file' '
|
||||
git log --oneline --format=%s -- . ":(exclude,glob)sub/*/file" >actual
|
||||
cat <<EOF >expect &&
|
||||
sub2/file
|
||||
sub/sub/sub/file
|
||||
sub/file2
|
||||
sub/file
|
||||
file
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'm_p_d() exclude sub' '
|
||||
git ls-files -- . ":(exclude)sub" >actual
|
||||
cat <<EOF >expect &&
|
||||
file
|
||||
sub2/file
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'm_p_d() exclude sub/sub/file' '
|
||||
git ls-files -- . ":(exclude)sub/sub/file" >actual
|
||||
cat <<EOF >expect &&
|
||||
file
|
||||
sub/file
|
||||
sub/file2
|
||||
sub/sub/sub/file
|
||||
sub2/file
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'm_p_d() exclude sub using mnemonic' '
|
||||
git ls-files -- . ":!sub" >actual
|
||||
cat <<EOF >expect &&
|
||||
file
|
||||
sub2/file
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'm_p_d() exclude :(icase)SUB' '
|
||||
git ls-files -- . ":(exclude,icase)SUB" >actual
|
||||
cat <<EOF >expect &&
|
||||
file
|
||||
sub2/file
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'm_p_d() exclude sub2 from sub' '
|
||||
(
|
||||
cd sub &&
|
||||
git ls-files -- :/ ":/!sub2" >actual
|
||||
cat <<EOF >expect &&
|
||||
../file
|
||||
file
|
||||
file2
|
||||
sub/file
|
||||
sub/sub/file
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'm_p_d() exclude sub/*file' '
|
||||
git ls-files -- . ":(exclude)sub/*file" >actual
|
||||
cat <<EOF >expect &&
|
||||
file
|
||||
sub/file2
|
||||
sub2/file
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'm_p_d() exclude :(glob)sub/*/file' '
|
||||
git ls-files -- . ":(exclude,glob)sub/*/file" >actual
|
||||
cat <<EOF >expect &&
|
||||
file
|
||||
sub/file
|
||||
sub/file2
|
||||
sub/sub/sub/file
|
||||
sub2/file
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_done
|
83
tree-walk.c
83
tree-walk.c
|
@ -662,9 +662,10 @@ static int match_wildcard_base(const struct pathspec_item *item,
|
|||
* Pre-condition: either baselen == base_offset (i.e. empty path)
|
||||
* or base[baselen-1] == '/' (i.e. with trailing slash).
|
||||
*/
|
||||
enum interesting tree_entry_interesting(const struct name_entry *entry,
|
||||
struct strbuf *base, int base_offset,
|
||||
const struct pathspec *ps)
|
||||
static enum interesting do_match(const struct name_entry *entry,
|
||||
struct strbuf *base, int base_offset,
|
||||
const struct pathspec *ps,
|
||||
int exclude)
|
||||
{
|
||||
int i;
|
||||
int pathlen, baselen = base->len - base_offset;
|
||||
|
@ -676,7 +677,8 @@ enum interesting tree_entry_interesting(const struct name_entry *entry,
|
|||
PATHSPEC_MAXDEPTH |
|
||||
PATHSPEC_LITERAL |
|
||||
PATHSPEC_GLOB |
|
||||
PATHSPEC_ICASE);
|
||||
PATHSPEC_ICASE |
|
||||
PATHSPEC_EXCLUDE);
|
||||
|
||||
if (!ps->nr) {
|
||||
if (!ps->recursive ||
|
||||
|
@ -697,6 +699,10 @@ enum interesting tree_entry_interesting(const struct name_entry *entry,
|
|||
const char *base_str = base->buf + base_offset;
|
||||
int matchlen = item->len, matched = 0;
|
||||
|
||||
if ((!exclude && item->magic & PATHSPEC_EXCLUDE) ||
|
||||
( exclude && !(item->magic & PATHSPEC_EXCLUDE)))
|
||||
continue;
|
||||
|
||||
if (baselen >= matchlen) {
|
||||
/* If it doesn't match, move along... */
|
||||
if (!match_dir_prefix(item, base_str, match, matchlen))
|
||||
|
@ -782,3 +788,72 @@ match_wildcards:
|
|||
}
|
||||
return never_interesting; /* No matches */
|
||||
}
|
||||
|
||||
/*
|
||||
* Is a tree entry interesting given the pathspec we have?
|
||||
*
|
||||
* Pre-condition: either baselen == base_offset (i.e. empty path)
|
||||
* or base[baselen-1] == '/' (i.e. with trailing slash).
|
||||
*/
|
||||
enum interesting tree_entry_interesting(const struct name_entry *entry,
|
||||
struct strbuf *base, int base_offset,
|
||||
const struct pathspec *ps)
|
||||
{
|
||||
enum interesting positive, negative;
|
||||
positive = do_match(entry, base, base_offset, ps, 0);
|
||||
|
||||
/*
|
||||
* case | entry | positive | negative | result
|
||||
* -----+-------+----------+----------+-------
|
||||
* 1 | file | -1 | -1..2 | -1
|
||||
* 2 | file | 0 | -1..2 | 0
|
||||
* 3 | file | 1 | -1 | 1
|
||||
* 4 | file | 1 | 0 | 1
|
||||
* 5 | file | 1 | 1 | 0
|
||||
* 6 | file | 1 | 2 | 0
|
||||
* 7 | file | 2 | -1 | 2
|
||||
* 8 | file | 2 | 0 | 2
|
||||
* 9 | file | 2 | 1 | 0
|
||||
* 10 | file | 2 | 2 | -1
|
||||
* -----+-------+----------+----------+-------
|
||||
* 11 | dir | -1 | -1..2 | -1
|
||||
* 12 | dir | 0 | -1..2 | 0
|
||||
* 13 | dir | 1 | -1 | 1
|
||||
* 14 | dir | 1 | 0 | 1
|
||||
* 15 | dir | 1 | 1 | 1 (*)
|
||||
* 16 | dir | 1 | 2 | 0
|
||||
* 17 | dir | 2 | -1 | 2
|
||||
* 18 | dir | 2 | 0 | 2
|
||||
* 19 | dir | 2 | 1 | 1 (*)
|
||||
* 20 | dir | 2 | 2 | -1
|
||||
*
|
||||
* (*) An exclude pattern interested in a directory does not
|
||||
* necessarily mean it will exclude all of the directory. In
|
||||
* wildcard case, it can't decide until looking at individual
|
||||
* files inside. So don't write such directories off yet.
|
||||
*/
|
||||
|
||||
if (!(ps->magic & PATHSPEC_EXCLUDE) ||
|
||||
positive <= entry_not_interesting) /* #1, #2, #11, #12 */
|
||||
return positive;
|
||||
|
||||
negative = do_match(entry, base, base_offset, ps, 1);
|
||||
|
||||
/* #3, #4, #7, #8, #13, #14, #17, #18 */
|
||||
if (negative <= entry_not_interesting)
|
||||
return positive;
|
||||
|
||||
/* #15, #19 */
|
||||
if (S_ISDIR(entry->mode) &&
|
||||
positive >= entry_interesting &&
|
||||
negative == entry_interesting)
|
||||
return entry_interesting;
|
||||
|
||||
if ((positive == entry_interesting &&
|
||||
negative >= entry_interesting) || /* #5, #6, #16 */
|
||||
(positive == all_entries_interesting &&
|
||||
negative == entry_interesting)) /* #9 */
|
||||
return entry_not_interesting;
|
||||
|
||||
return all_entries_not_interesting; /* #10, #20 */
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче