refs: loosen restriction on wildcard "*" refspecs

Loosen restrictions on refspecs by allowing patterns that have a "*" within a component instead of only as the whole component. Remove the logic to accept a single "*" as a whole component from check_refname_format(), and implement an extended form of that logic in check_refname_component(). Pass the pointer to the flags argument to the latter, as it has to clear REFNAME_REFSPEC_PATTERN bit when it sees "*". Teach check_refname_component() function to allow an asterisk "*" only when REFNAME_REFSPEC_PATTERN is set in the flags, and drop the bit after seeing a "*", to ensure that one side of a refspec contains at most one asterisk. This will allow us to accept refspecs such as `for/bar*:foo/baz*`. Any refspec which functioned before shall continue functioning with the new logic. Signed-off-by: Jacob Keller <jacob.keller@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jacob Keller committed Jul 22, 2015 at 14:05 UTC cd377f45c9d06e8ab3e87d5a687ed22ab34e1c48
5 files changed +36 -27
Documentation/git-check-ref-format.txt
+2 -2
@@ -94,8 +94,8 @@ OPTIONS
94 Interpret <refname> as a reference name pattern for a refspec
95 (as used with remote repositories). If this option is
96 enabled, <refname> is allowed to contain a single `*`
97 - in place of a one full pathname component (e.g.,
98 - `foo/*/bar` but not `foo/bar*`).
97 + in the refspec (e.g., `foo/bar*/baz` or `foo/bar*baz/`
98 + but not `foo/bar*/baz*`).
99
100 --normalize::
101 Normalize 'refname' by removing any leading slash (`/`)
refs.c
+20 -16
@@ -21,12 +21,13 @@ struct ref_lock {
21 * 2: ., look for a preceding . to reject .. in refs
22 * 3: {, look for a preceding @ to reject @{ in refs
23 * 4: A bad character: ASCII control characters, and
24 - * "*", ":", "?", "[", "\", "^", "~", SP, or TAB
24 + * ":", "?", "[", "\", "^", "~", SP, or TAB
25 + * 5: *, reject unless REFNAME_REFSPEC_PATTERN is set
26 */
27 static unsigned char refname_disposition[256] = {
28 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
29 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
29 - 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2, 1,
30 + 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 2, 1,
31 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4,
32 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
33 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 4, 0,
@@ -73,12 +74,13 @@ static unsigned char refname_disposition[256] = {
74 * - any path component of it begins with ".", or
75 * - it has double dots "..", or
76 * - it has ASCII control characters, or
76 - * - it has "*", ":", "?", "[", "\", "^", "~", SP, or TAB anywhere, or
77 + * - it has ":", "?", "[", "\", "^", "~", SP, or TAB anywhere, or
78 + * - it has "*" anywhere unless REFNAME_REFSPEC_PATTERN is set, or
79 * - it ends with a "/", or
80 * - it ends with ".lock", or
81 * - it contains a "@{" portion
82 */
81 -static int check_refname_component(const char *refname, int flags)
83 +static int check_refname_component(const char *refname, int *flags)
84 {
85 const char *cp;
86 char last = '\0';
@@ -99,6 +101,16 @@ static int check_refname_component(const char *refname, int flags)
101 break;
102 case 4:
103 return -1;
104 + case 5:
105 + if (!(*flags & REFNAME_REFSPEC_PATTERN))
106 + return -1; /* refspec can't be a pattern */
107 +
108 + /*
109 + * Unset the pattern flag so that we only accept
110 + * a single asterisk for one side of refspec.
111 + */
112 + *flags &= ~ REFNAME_REFSPEC_PATTERN;
113 + break;
114 }
115 last = ch;
116 }
@@ -123,18 +135,10 @@ int check_refname_format(const char *refname, int flags)
135
136 while (1) {
137 /* We are at the start of a path component. */
126 - component_len = check_refname_component(refname, flags);
127 - if (component_len <= 0) {
128 - if ((flags & REFNAME_REFSPEC_PATTERN) &&
129 - refname[0] == '*' &&
130 - (refname[1] == '\0' || refname[1] == '/')) {
131 - /* Accept one wildcard as a full refname component. */
132 - flags &= ~REFNAME_REFSPEC_PATTERN;
133 - component_len = 1;
134 - } else {
135 - return -1;
136 - }
137 - }
138 + component_len = check_refname_component(refname, &flags);
139 + if (component_len <= 0)
140 + return -1;
141 +
142 component_count++;
143 if (refname[component_len] == '\0')
144 break;
refs.h
+2 -2
@@ -224,8 +224,8 @@ extern int for_each_reflog(each_ref_fn, void *);
224 * to the rules described in Documentation/git-check-ref-format.txt.
225 * If REFNAME_ALLOW_ONELEVEL is set in flags, then accept one-level
226 * reference names. If REFNAME_REFSPEC_PATTERN is set in flags, then
227 - * allow a "*" wildcard character in place of one of the name
228 - * components. No leading or repeated slashes are accepted.
227 + * allow a single "*" wildcard character in the refspec. No leading or
228 + * repeated slashes are accepted.
229 */
230 extern int check_refname_format(const char *refname, int flags);
231
t/t1402-check-ref-format.sh
+5 -3
@@ -62,9 +62,11 @@ invalid_ref 'heads/foo\bar'
62 invalid_ref "$(printf 'heads/foo\t')"
63 invalid_ref "$(printf 'heads/foo\177')"
64 valid_ref "$(printf 'heads/fu\303\237')"
65 -invalid_ref 'heads/*foo/bar' --refspec-pattern
66 -invalid_ref 'heads/foo*/bar' --refspec-pattern
67 -invalid_ref 'heads/f*o/bar' --refspec-pattern
65 +valid_ref 'heads/*foo/bar' --refspec-pattern
66 +valid_ref 'heads/foo*/bar' --refspec-pattern
67 +valid_ref 'heads/f*o/bar' --refspec-pattern
68 +invalid_ref 'heads/f*o*/bar' --refspec-pattern
69 +invalid_ref 'heads/foo*/bar*' --refspec-pattern
70
71 ref='foo'
72 invalid_ref "$ref"
t/t5511-refspec.sh
+7 -4
@@ -71,15 +71,18 @@ test_refspec fetch ':refs/remotes/frotz/HEAD-to-me'
71 test_refspec push ':refs/remotes/frotz/delete me' invalid
72 test_refspec fetch ':refs/remotes/frotz/HEAD to me' invalid
73
74 -test_refspec fetch 'refs/heads/*/for-linus:refs/remotes/mine/*-blah' invalid
75 -test_refspec push 'refs/heads/*/for-linus:refs/remotes/mine/*-blah' invalid
74 +test_refspec fetch 'refs/heads/*/for-linus:refs/remotes/mine/*-blah'
75 +test_refspec push 'refs/heads/*/for-linus:refs/remotes/mine/*-blah'
76
77 -test_refspec fetch 'refs/heads*/for-linus:refs/remotes/mine/*' invalid
78 -test_refspec push 'refs/heads*/for-linus:refs/remotes/mine/*' invalid
77 +test_refspec fetch 'refs/heads*/for-linus:refs/remotes/mine/*'
78 +test_refspec push 'refs/heads*/for-linus:refs/remotes/mine/*'
79
80 test_refspec fetch 'refs/heads/*/*/for-linus:refs/remotes/mine/*' invalid
81 test_refspec push 'refs/heads/*/*/for-linus:refs/remotes/mine/*' invalid
82
83 +test_refspec fetch 'refs/heads/*g*/for-linus:refs/remotes/mine/*' invalid
84 +test_refspec push 'refs/heads/*g*/for-linus:refs/remotes/mine/*' invalid
85 +
86 test_refspec fetch 'refs/heads/*/for-linus:refs/remotes/mine/*'
87 test_refspec push 'refs/heads/*/for-linus:refs/remotes/mine/*'
88