зеркало из https://github.com/microsoft/git.git
Merge branch 'jc/magic-pathspec'
* jc/magic-pathspec: setup.c: Fix some "symbol not declared" sparse warnings t3703: Skip tests using directory name ":" on Windows revision.c: leave a note for "a lone :" enhancement t3703, t4208: add test cases for magic pathspec rev/path disambiguation: further restrict "misspelled index entry" diag fix overslow :/no-such-string-ever-existed diagnostics fix overstrict :<path> diagnosis grep: use get_pathspec() correctly pathspec: drop "lone : means no pathspec" from get_pathspec() Revert "magic pathspec: add ":(icase)path" to match case insensitively" magic pathspec: add ":(icase)path" to match case insensitively magic pathspec: futureproof shorthand form magic pathspec: add tentative ":/path/from/top/level" pathspec support
This commit is contained in:
Коммит
be5ab43566
|
@ -277,7 +277,8 @@ This commit is referred to as a "merge commit", or sometimes just a
|
|||
Pattern used to specify paths.
|
||||
+
|
||||
Pathspecs are used on the command line of "git ls-files", "git
|
||||
ls-tree", "git grep", "git checkout", and many other commands to
|
||||
ls-tree", "git add", "git grep", "git diff", "git checkout",
|
||||
and many other commands to
|
||||
limit the scope of operations to some subset of the tree or
|
||||
worktree. See the documentation of each command for whether
|
||||
paths are relative to the current directory or toplevel. The
|
||||
|
@ -296,6 +297,37 @@ For example, Documentation/*.jpg will match all .jpg files
|
|||
in the Documentation subtree,
|
||||
including Documentation/chapter_1/figure_1.jpg.
|
||||
|
||||
+
|
||||
A pathspec that begins with a colon `:` has special meaning. In the
|
||||
short form, the leading colon `:` is followed by zero or more "magic
|
||||
signature" letters (which optionally is terminated by another colon `:`),
|
||||
and the remainder is the pattern to match against the path. The optional
|
||||
colon that terminates the "magic signature" can be omitted if the pattern
|
||||
begins with a character that cannot be a "magic signature" and is not a
|
||||
colon.
|
||||
+
|
||||
In the long form, the leading colon `:` is followed by a open
|
||||
parenthesis `(`, a comma-separated list of zero or more "magic words",
|
||||
and a close parentheses `)`, and the remainder is the pattern to match
|
||||
against the path.
|
||||
+
|
||||
The "magic signature" consists of an ASCII symbol that is not
|
||||
alphanumeric.
|
||||
+
|
||||
--
|
||||
top `/`;;
|
||||
The magic word `top` (mnemonic: `/`) makes the pattern match
|
||||
from the root of the working tree, even when you are running
|
||||
the command from inside a subdirectory.
|
||||
--
|
||||
+
|
||||
Currently only the slash `/` is recognized as the "magic signature",
|
||||
but it is envisioned that we will support more types of magic in later
|
||||
versions of git.
|
||||
+
|
||||
A pathspec with only a colon means "there is no pathspec". This form
|
||||
should not be combined with other pathspec.
|
||||
|
||||
[[def_parent]]parent::
|
||||
A <<def_commit_object,commit object>> contains a (possibly empty) list
|
||||
of the logical predecessor(s) in the line of development, i.e. its
|
||||
|
|
|
@ -969,13 +969,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
|
|||
verify_filename(prefix, argv[j]);
|
||||
}
|
||||
|
||||
if (i < argc)
|
||||
paths = get_pathspec(prefix, argv + i);
|
||||
else if (prefix) {
|
||||
paths = xcalloc(2, sizeof(const char *));
|
||||
paths[0] = prefix;
|
||||
paths[1] = NULL;
|
||||
}
|
||||
paths = get_pathspec(prefix, argv + i);
|
||||
init_pathspec(&pathspec, paths);
|
||||
pathspec.max_depth = opt.max_depth;
|
||||
pathspec.recursive = 1;
|
||||
|
|
8
cache.h
8
cache.h
|
@ -810,15 +810,15 @@ struct object_context {
|
|||
};
|
||||
|
||||
extern int get_sha1(const char *str, unsigned char *sha1);
|
||||
extern int get_sha1_with_mode_1(const char *str, unsigned char *sha1, unsigned *mode, int gently, const char *prefix);
|
||||
extern int get_sha1_with_mode_1(const char *str, unsigned char *sha1, unsigned *mode, int only_to_die, const char *prefix);
|
||||
static inline int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode)
|
||||
{
|
||||
return get_sha1_with_mode_1(str, sha1, mode, 1, NULL);
|
||||
return get_sha1_with_mode_1(str, sha1, mode, 0, NULL);
|
||||
}
|
||||
extern int get_sha1_with_context_1(const char *name, unsigned char *sha1, struct object_context *orc, int gently, const char *prefix);
|
||||
extern int get_sha1_with_context_1(const char *name, unsigned char *sha1, struct object_context *orc, int only_to_die, const char *prefix);
|
||||
static inline int get_sha1_with_context(const char *str, unsigned char *sha1, struct object_context *orc)
|
||||
{
|
||||
return get_sha1_with_context_1(str, sha1, orc, 1, NULL);
|
||||
return get_sha1_with_context_1(str, sha1, orc, 0, NULL);
|
||||
}
|
||||
extern int get_sha1_hex(const char *hex, unsigned char *sha1);
|
||||
extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */
|
||||
|
|
15
ctype.c
15
ctype.c
|
@ -10,17 +10,18 @@ enum {
|
|||
A = GIT_ALPHA,
|
||||
D = GIT_DIGIT,
|
||||
G = GIT_GLOB_SPECIAL, /* *, ?, [, \\ */
|
||||
R = GIT_REGEX_SPECIAL /* $, (, ), +, ., ^, {, | */
|
||||
R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | */
|
||||
P = GIT_PATHSPEC_MAGIC /* other non-alnum, except for ] and } */
|
||||
};
|
||||
|
||||
unsigned char sane_ctype[256] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, S, S, 0, 0, S, 0, 0, /* 0.. 15 */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16.. 31 */
|
||||
S, 0, 0, 0, R, 0, 0, 0, R, R, G, R, 0, 0, R, 0, /* 32.. 47 */
|
||||
D, D, D, D, D, D, D, D, D, D, 0, 0, 0, 0, 0, G, /* 48.. 63 */
|
||||
0, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */
|
||||
A, A, A, A, A, A, A, A, A, A, A, G, G, 0, R, 0, /* 80.. 95 */
|
||||
0, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */
|
||||
A, A, A, A, A, A, A, A, A, A, A, R, R, 0, 0, 0, /* 112..127 */
|
||||
S, P, P, P, R, P, P, P, R, R, G, R, P, P, R, P, /* 32.. 47 */
|
||||
D, D, D, D, D, D, D, D, D, D, P, P, P, P, P, G, /* 48.. 63 */
|
||||
P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */
|
||||
A, A, A, A, A, A, A, A, A, A, A, G, G, 0, R, P, /* 80.. 95 */
|
||||
P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */
|
||||
A, A, A, A, A, A, A, A, A, A, A, R, R, 0, P, 0, /* 112..127 */
|
||||
/* Nothing in the 128.. range */
|
||||
};
|
||||
|
|
|
@ -463,6 +463,7 @@ extern unsigned char sane_ctype[256];
|
|||
#define GIT_ALPHA 0x04
|
||||
#define GIT_GLOB_SPECIAL 0x08
|
||||
#define GIT_REGEX_SPECIAL 0x10
|
||||
#define GIT_PATHSPEC_MAGIC 0x20
|
||||
#define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0)
|
||||
#define isascii(x) (((x) & ~0x7f) == 0)
|
||||
#define isspace(x) sane_istest(x,GIT_SPACE)
|
||||
|
@ -473,6 +474,7 @@ extern unsigned char sane_ctype[256];
|
|||
#define is_regex_special(x) sane_istest(x,GIT_GLOB_SPECIAL | GIT_REGEX_SPECIAL)
|
||||
#define tolower(x) sane_case((unsigned char)(x), 0x20)
|
||||
#define toupper(x) sane_case((unsigned char)(x), 0)
|
||||
#define is_pathspec_magic(x) sane_istest(x,GIT_PATHSPEC_MAGIC)
|
||||
|
||||
static inline int sane_case(int x, int high)
|
||||
{
|
||||
|
|
14
revision.c
14
revision.c
|
@ -1661,6 +1661,20 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
|
|||
}
|
||||
|
||||
if (prune_data.nr) {
|
||||
/*
|
||||
* If we need to introduce the magic "a lone ':' means no
|
||||
* pathspec whatsoever", here is the place to do so.
|
||||
*
|
||||
* if (prune_data.nr == 1 && !strcmp(prune_data[0], ":")) {
|
||||
* prune_data.nr = 0;
|
||||
* prune_data.alloc = 0;
|
||||
* free(prune_data.path);
|
||||
* prune_data.path = NULL;
|
||||
* } else {
|
||||
* terminate prune_data.alloc with NULL and
|
||||
* call init_pathspec() to set revs->prune_data here.
|
||||
* }
|
||||
*/
|
||||
ALLOC_GROW(prune_data.path, prune_data.nr+1, prune_data.alloc);
|
||||
prune_data.path[prune_data.nr++] = NULL;
|
||||
init_pathspec(&revs->prune_data,
|
||||
|
|
115
setup.c
115
setup.c
|
@ -85,8 +85,17 @@ static void NORETURN die_verify_filename(const char *prefix, const char *arg)
|
|||
{
|
||||
unsigned char sha1[20];
|
||||
unsigned mode;
|
||||
/* try a detailed diagnostic ... */
|
||||
get_sha1_with_mode_1(arg, sha1, &mode, 0, prefix);
|
||||
|
||||
/*
|
||||
* Saying "'(icase)foo' does not exist in the index" when the
|
||||
* user gave us ":(icase)foo" is just stupid. A magic pathspec
|
||||
* begins with a colon and is followed by a non-alnum; do not
|
||||
* let get_sha1_with_mode_1(only_to_die=1) to even trigger.
|
||||
*/
|
||||
if (!(arg[0] == ':' && !isalnum(arg[1])))
|
||||
/* try a detailed diagnostic ... */
|
||||
get_sha1_with_mode_1(arg, sha1, &mode, 1, prefix);
|
||||
|
||||
/* ... or fall back the most general message. */
|
||||
die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
|
||||
"Use '--' to separate paths from revisions", arg);
|
||||
|
@ -126,6 +135,105 @@ void verify_non_filename(const char *prefix, const char *arg)
|
|||
"Use '--' to separate filenames from revisions", arg);
|
||||
}
|
||||
|
||||
/*
|
||||
* Magic pathspec
|
||||
*
|
||||
* NEEDSWORK: These need to be moved to dir.h or even to a new
|
||||
* pathspec.h when we restructure get_pathspec() users to use the
|
||||
* "struct pathspec" interface.
|
||||
*
|
||||
* Possible future magic semantics include stuff like:
|
||||
*
|
||||
* { PATHSPEC_NOGLOB, '!', "noglob" },
|
||||
* { PATHSPEC_ICASE, '\0', "icase" },
|
||||
* { PATHSPEC_RECURSIVE, '*', "recursive" },
|
||||
* { PATHSPEC_REGEXP, '\0', "regexp" },
|
||||
*
|
||||
*/
|
||||
#define PATHSPEC_FROMTOP (1<<0)
|
||||
|
||||
static struct pathspec_magic {
|
||||
unsigned bit;
|
||||
char mnemonic; /* this cannot be ':'! */
|
||||
const char *name;
|
||||
} pathspec_magic[] = {
|
||||
{ PATHSPEC_FROMTOP, '/', "top" },
|
||||
};
|
||||
|
||||
/*
|
||||
* Take an element of a pathspec and check for magic signatures.
|
||||
* Append the result to the prefix.
|
||||
*
|
||||
* For now, we only parse the syntax and throw out anything other than
|
||||
* "top" magic.
|
||||
*
|
||||
* NEEDSWORK: This needs to be rewritten when we start migrating
|
||||
* get_pathspec() users to use the "struct pathspec" interface. For
|
||||
* example, a pathspec element may be marked as case-insensitive, but
|
||||
* the prefix part must always match literally, and a single stupid
|
||||
* string cannot express such a case.
|
||||
*/
|
||||
static const char *prefix_pathspec(const char *prefix, int prefixlen, const char *elt)
|
||||
{
|
||||
unsigned magic = 0;
|
||||
const char *copyfrom = elt;
|
||||
int i;
|
||||
|
||||
if (elt[0] != ':') {
|
||||
; /* nothing to do */
|
||||
} else if (elt[1] == '(') {
|
||||
/* longhand */
|
||||
const char *nextat;
|
||||
for (copyfrom = elt + 2;
|
||||
*copyfrom && *copyfrom != ')';
|
||||
copyfrom = nextat) {
|
||||
size_t len = strcspn(copyfrom, ",)");
|
||||
if (copyfrom[len] == ')')
|
||||
nextat = copyfrom + len;
|
||||
else
|
||||
nextat = copyfrom + len + 1;
|
||||
if (!len)
|
||||
continue;
|
||||
for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
|
||||
if (strlen(pathspec_magic[i].name) == len &&
|
||||
!strncmp(pathspec_magic[i].name, copyfrom, len)) {
|
||||
magic |= pathspec_magic[i].bit;
|
||||
break;
|
||||
}
|
||||
if (ARRAY_SIZE(pathspec_magic) <= i)
|
||||
die("Invalid pathspec magic '%.*s' in '%s'",
|
||||
(int) len, copyfrom, elt);
|
||||
}
|
||||
if (*copyfrom == ')')
|
||||
copyfrom++;
|
||||
} else {
|
||||
/* shorthand */
|
||||
for (copyfrom = elt + 1;
|
||||
*copyfrom && *copyfrom != ':';
|
||||
copyfrom++) {
|
||||
char ch = *copyfrom;
|
||||
|
||||
if (!is_pathspec_magic(ch))
|
||||
break;
|
||||
for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
|
||||
if (pathspec_magic[i].mnemonic == ch) {
|
||||
magic |= pathspec_magic[i].bit;
|
||||
break;
|
||||
}
|
||||
if (ARRAY_SIZE(pathspec_magic) <= i)
|
||||
die("Unimplemented pathspec magic '%c' in '%s'",
|
||||
ch, elt);
|
||||
}
|
||||
if (*copyfrom == ':')
|
||||
copyfrom++;
|
||||
}
|
||||
|
||||
if (magic & PATHSPEC_FROMTOP)
|
||||
return xstrdup(copyfrom);
|
||||
else
|
||||
return prefix_path(prefix, prefixlen, copyfrom);
|
||||
}
|
||||
|
||||
const char **get_pathspec(const char *prefix, const char **pathspec)
|
||||
{
|
||||
const char *entry = *pathspec;
|
||||
|
@ -147,8 +255,7 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
|
|||
dst = pathspec;
|
||||
prefixlen = prefix ? strlen(prefix) : 0;
|
||||
while (*src) {
|
||||
const char *p = prefix_path(prefix, prefixlen, *src);
|
||||
*(dst++) = p;
|
||||
*(dst++) = prefix_pathspec(prefix, prefixlen, *src);
|
||||
src++;
|
||||
}
|
||||
*dst = NULL;
|
||||
|
|
17
sha1_name.c
17
sha1_name.c
|
@ -1083,11 +1083,12 @@ static void diagnose_invalid_index_path(int stage,
|
|||
}
|
||||
|
||||
|
||||
int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, int gently, const char *prefix)
|
||||
int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode,
|
||||
int only_to_die, const char *prefix)
|
||||
{
|
||||
struct object_context oc;
|
||||
int ret;
|
||||
ret = get_sha1_with_context_1(name, sha1, &oc, gently, prefix);
|
||||
ret = get_sha1_with_context_1(name, sha1, &oc, only_to_die, prefix);
|
||||
*mode = oc.mode;
|
||||
return ret;
|
||||
}
|
||||
|
@ -1111,7 +1112,7 @@ static char *resolve_relative_path(const char *rel)
|
|||
|
||||
int get_sha1_with_context_1(const char *name, unsigned char *sha1,
|
||||
struct object_context *oc,
|
||||
int gently, const char *prefix)
|
||||
int only_to_die, const char *prefix)
|
||||
{
|
||||
int ret, bracket_depth;
|
||||
int namelen = strlen(name);
|
||||
|
@ -1133,7 +1134,7 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
|
|||
struct cache_entry *ce;
|
||||
char *new_path = NULL;
|
||||
int pos;
|
||||
if (namelen > 2 && name[1] == '/') {
|
||||
if (!only_to_die && namelen > 2 && name[1] == '/') {
|
||||
struct commit_list *list = NULL;
|
||||
for_each_ref(handle_one_ref, &list);
|
||||
return get_sha1_oneline(name + 2, sha1, list);
|
||||
|
@ -1176,7 +1177,7 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
|
|||
}
|
||||
pos++;
|
||||
}
|
||||
if (!gently)
|
||||
if (only_to_die && name[1] && name[1] != '/')
|
||||
diagnose_invalid_index_path(stage, prefix, cp);
|
||||
free(new_path);
|
||||
return -1;
|
||||
|
@ -1192,7 +1193,7 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
|
|||
if (*cp == ':') {
|
||||
unsigned char tree_sha1[20];
|
||||
char *object_name = NULL;
|
||||
if (!gently) {
|
||||
if (only_to_die) {
|
||||
object_name = xmalloc(cp-name+1);
|
||||
strncpy(object_name, name, cp-name);
|
||||
object_name[cp-name] = '\0';
|
||||
|
@ -1205,7 +1206,7 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
|
|||
if (new_filename)
|
||||
filename = new_filename;
|
||||
ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode);
|
||||
if (!gently) {
|
||||
if (only_to_die) {
|
||||
diagnose_invalid_sha1_path(prefix, filename,
|
||||
tree_sha1, object_name);
|
||||
free(object_name);
|
||||
|
@ -1218,7 +1219,7 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
|
|||
free(new_filename);
|
||||
return ret;
|
||||
} else {
|
||||
if (!gently)
|
||||
if (only_to_die)
|
||||
die("Invalid object name '%s'.", object_name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='magic pathspec tests using git-add'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success 'setup' '
|
||||
mkdir sub anothersub &&
|
||||
: >sub/foo &&
|
||||
: >anothersub/foo
|
||||
'
|
||||
|
||||
test_expect_success 'add :/' "
|
||||
cat >expected <<-EOF &&
|
||||
add 'anothersub/foo'
|
||||
add 'expected'
|
||||
add 'sub/actual'
|
||||
add 'sub/foo'
|
||||
EOF
|
||||
(cd sub && git add -n :/ >actual) &&
|
||||
test_cmp expected sub/actual
|
||||
"
|
||||
|
||||
cat >expected <<EOF
|
||||
add 'anothersub/foo'
|
||||
EOF
|
||||
|
||||
test_expect_success 'add :/anothersub' '
|
||||
(cd sub && git add -n :/anothersub >actual) &&
|
||||
test_cmp expected sub/actual
|
||||
'
|
||||
|
||||
test_expect_success 'add :/non-existent' '
|
||||
(cd sub && test_must_fail git add -n :/non-existent)
|
||||
'
|
||||
|
||||
cat >expected <<EOF
|
||||
add 'sub/foo'
|
||||
EOF
|
||||
|
||||
test_expect_success 'a file with the same (long) magic name exists' '
|
||||
: >":(icase)ha" &&
|
||||
test_must_fail git add -n ":(icase)ha" &&
|
||||
git add -n "./:(icase)ha"
|
||||
'
|
||||
|
||||
if mkdir ":" 2>/dev/null
|
||||
then
|
||||
test_set_prereq COLON_DIR
|
||||
fi
|
||||
|
||||
test_expect_success COLON_DIR 'a file with the same (short) magic name exists' '
|
||||
: >":/bar" &&
|
||||
test_must_fail git add -n :/bar &&
|
||||
git add -n "./:/bar"
|
||||
'
|
||||
|
||||
test_done
|
|
@ -0,0 +1,36 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='magic pathspec tests using git-log'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success 'setup' '
|
||||
test_commit initial &&
|
||||
test_tick &&
|
||||
git commit --allow-empty -m empty &&
|
||||
mkdir sub
|
||||
'
|
||||
|
||||
test_expect_success '"git log :/" should be ambiguous' '
|
||||
test_must_fail git log :/ 2>error &&
|
||||
grep ambiguous error
|
||||
'
|
||||
|
||||
test_expect_success '"git log :" should be ambiguous' '
|
||||
test_must_fail git log : 2>error &&
|
||||
grep ambiguous error
|
||||
'
|
||||
|
||||
test_expect_success 'git log -- :' '
|
||||
git log -- :
|
||||
'
|
||||
|
||||
test_expect_success 'git log HEAD -- :/' '
|
||||
cat >expected <<-EOF &&
|
||||
24b24cf initial
|
||||
EOF
|
||||
(cd sub && git log --oneline HEAD -- :/ >../actual) &&
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_done
|
Загрузка…
Ссылка в новой задаче