mirror of
https://github.com/git/git.git
synced 2026-03-15 11:10:07 +01:00
Merge branch 'jc/magic-pathspec' into next
* jc/magic-pathspec: 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:
@@ -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.
|
||||
icase;;
|
||||
The magic word `icase` (there is no mnemonic for it) makes the
|
||||
pattern match case insensitively. E.g. `:(icase)makefile` matches
|
||||
both `Makefile` and `makefile`.
|
||||
--
|
||||
+
|
||||
It is envisioned that we will support more types of magic in later
|
||||
versions of git.
|
||||
|
||||
[[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
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
128
setup.c
128
setup.c
@@ -126,6 +126,131 @@ 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_RECURSIVE, '*', "recursive" },
|
||||
* { PATHSPEC_REGEXP, '\0', "regexp" },
|
||||
*
|
||||
*/
|
||||
#define PATHSPEC_FROMTOP (1<<0)
|
||||
#define PATHSPEC_ICASE (1<<1)
|
||||
|
||||
struct pathspec_magic {
|
||||
unsigned bit;
|
||||
char mnemonic; /* this cannot be ':'! */
|
||||
const char *name;
|
||||
} pathspec_magic[] = {
|
||||
{ PATHSPEC_FROMTOP, '/', "top" },
|
||||
{ PATHSPEC_ICASE, '\0', "icase" },
|
||||
};
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
const char *prefix_pathspec(const char *prefix, int prefixlen, const char *elt)
|
||||
{
|
||||
unsigned magic = 0;
|
||||
const char *copyfrom = elt;
|
||||
const char *retval;
|
||||
int i, free_source = 0;
|
||||
|
||||
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 if (!elt[1]) {
|
||||
/* Just ':' -- no element! */
|
||||
return NULL;
|
||||
} 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_ICASE) {
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
for (i = 0; copyfrom[i]; i++) {
|
||||
int ch = copyfrom[i];
|
||||
if (('a' <= ch && ch <= 'z') ||
|
||||
('A' <= ch && ch <= 'Z')) {
|
||||
strbuf_addf(&sb, "[%c%c]",
|
||||
tolower(ch), toupper(ch));
|
||||
} else {
|
||||
strbuf_addch(&sb, ch);
|
||||
}
|
||||
}
|
||||
if (sb.len) {
|
||||
free_source = 1;
|
||||
copyfrom = strbuf_detach(&sb, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
if (magic & PATHSPEC_FROMTOP)
|
||||
retval = xstrdup(copyfrom);
|
||||
else
|
||||
retval = prefix_path(prefix, prefixlen, copyfrom);
|
||||
if (free_source)
|
||||
free((char *)copyfrom);
|
||||
return retval;
|
||||
}
|
||||
|
||||
const char **get_pathspec(const char *prefix, const char **pathspec)
|
||||
{
|
||||
const char *entry = *pathspec;
|
||||
@@ -147,8 +272,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;
|
||||
|
||||
Reference in New Issue
Block a user