diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index 0ca029b738..8f62d1abee 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -319,14 +319,14 @@ 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 +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 <> contains a (possibly empty) list diff --git a/builtin/grep.c b/builtin/grep.c index 3ee2ec51de..931eee0d75 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -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; diff --git a/cache.h b/cache.h index 2b34116624..049ab23ed6 100644 --- a/cache.h +++ b/cache.h @@ -800,15 +800,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! */ diff --git a/setup.c b/setup.c index 4f6b2dac5f..58dc16c8c4 100644 --- a/setup.c +++ b/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); @@ -136,12 +145,12 @@ void verify_non_filename(const char *prefix, const char *arg) * 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) -#define PATHSPEC_ICASE (1<<1) struct pathspec_magic { unsigned bit; @@ -149,7 +158,6 @@ struct pathspec_magic { const char *name; } pathspec_magic[] = { { PATHSPEC_FROMTOP, '/', "top" }, - { PATHSPEC_ICASE, '\0', "icase" }, }; /* @@ -169,8 +177,7 @@ 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; + int i; if (elt[0] != ':') { ; /* nothing to do */ @@ -199,9 +206,6 @@ const char *prefix_pathspec(const char *prefix, int prefixlen, const char *elt) } if (*copyfrom == ')') copyfrom++; - } else if (!elt[1]) { - /* Just ':' -- no element! */ - return NULL; } else { /* shorthand */ for (copyfrom = elt + 1; @@ -224,31 +228,10 @@ const char *prefix_pathspec(const char *prefix, int prefixlen, const char *elt) 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); + return xstrdup(copyfrom); else - retval = prefix_path(prefix, prefixlen, copyfrom); - if (free_source) - free((char *)copyfrom); - return retval; + return prefix_path(prefix, prefixlen, copyfrom); } const char **get_pathspec(const char *prefix, const char **pathspec) diff --git a/sha1_name.c b/sha1_name.c index 69cd6c815d..ff5992acc9 100644 --- a/sha1_name.c +++ b/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); } } diff --git a/t/t3703-add-magic-pathspec.sh b/t/t3703-add-magic-pathspec.sh new file mode 100755 index 0000000000..ce5585ebb1 --- /dev/null +++ b/t/t3703-add-magic-pathspec.sh @@ -0,0 +1,54 @@ +#!/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 <actual) && + test_cmp expected sub/actual +' + +test_expect_success 'add :/non-existent' ' + (cd sub && test_must_fail git add -n :/non-existent) +' + +cat >expected <":(icase)ha" && + test_must_fail git add -n ":(icase)ha" && + git add -n "./:(icase)ha" +' + +test_expect_success 'a file with the same (short) magic name exists' ' + mkdir ":" && + : >":/bar" && + test_must_fail git add -n :/bar && + git add -n "./:/bar" +' + +test_done diff --git a/t/t4208-log-magic-pathspec.sh b/t/t4208-log-magic-pathspec.sh new file mode 100755 index 0000000000..2c482b622b --- /dev/null +++ b/t/t4208-log-magic-pathspec.sh @@ -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