Skip to content

Commit

Permalink
Merge branch 'jc/magic-pathspec'
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Junio C Hamano committed May 23, 2011
2 parents b7aba2e + 488201c commit be5ab43
Show file tree
Hide file tree
Showing 10 changed files with 276 additions and 31 deletions.
34 changes: 33 additions & 1 deletion Documentation/glossary-content.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
8 changes: 1 addition & 7 deletions builtin/grep.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -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! */
Expand Down
15 changes: 8 additions & 7 deletions ctype.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
};
2 changes: 2 additions & 0 deletions git-compat-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
{
Expand Down
14 changes: 14 additions & 0 deletions revision.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
115 changes: 111 additions & 4 deletions setup.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
17 changes: 9 additions & 8 deletions sha1_name.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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';
Expand All @@ -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);
Expand All @@ -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);
}
}
Expand Down
Loading

0 comments on commit be5ab43

Please sign in to comment.