git check-ref-format: add options --allow-onelevel and --refspec-pattern

Also add tests of the new options. (Actually, one big reason to add the new options is to make it easy to test check_ref_format(), though the options should also be useful to other scripts.) Interpret the result of check_ref_format() based on which types of refnames are allowed. However, because check_ref_format() can only return a single value, one test case is still broken. Specifically, the case "git check-ref-format --onelevel '*'" incorrectly succeeds because check_ref_format() returns CHECK_REF_FORMAT_ONELEVEL for this refname even though the refname is also CHECK_REF_FORMAT_WILDCARD. The type of check that leads to this failure is used elsewhere in "real" code and could lead to bugs; it will be fixed over the next few commits. Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Michael Haggerty committed Sep 15, 2011 at 23:10 UTC e4ed6105ec4a8507d4bd9f6355647fa911e4731f
3 files changed +152 -21
Documentation/git-check-ref-format.txt
+24 -5
@@ -8,8 +8,8 @@ git-check-ref-format - Ensures that a reference name is well formed
8 SYNOPSIS
9 --------
10 [verse]
11 -'git check-ref-format' <refname>
12 -'git check-ref-format' --print <refname>
11 +'git check-ref-format' [--print]
12 + [--[no-]allow-onelevel] [--refspec-pattern] <refname>
13 'git check-ref-format' --branch <branchname-shorthand>
14
15 DESCRIPTION
@@ -32,14 +32,18 @@ git imposes the following rules on how references are named:
32
33 . They must contain at least one `/`. This enforces the presence of a
34 category like `heads/`, `tags/` etc. but the actual names are not
35 - restricted.
35 + restricted. If the `--allow-onelevel` option is used, this rule
36 + is waived.
37
38 . They cannot have two consecutive dots `..` anywhere.
39
40 . They cannot have ASCII control characters (i.e. bytes whose
41 values are lower than \040, or \177 `DEL`), space, tilde `~`,
41 - caret `{caret}`, colon `:`, question-mark `?`, asterisk `*`,
42 - or open bracket `[` anywhere.
42 + caret `{caret}`, or colon `:` anywhere.
43 +
44 +. They cannot have question-mark `?`, asterisk `{asterisk}`, or open
45 + bracket `[` anywhere. See the `--refspec-pattern` option below for
46 + an exception to this rule.
47
48 . They cannot end with a slash `/` nor a dot `.`.
49
@@ -78,6 +82,21 @@ were on. This option should be used by porcelains to accept this
82 syntax anywhere a branch name is expected, so they can act as if you
83 typed the branch name.
84
85 +OPTIONS
86 +-------
87 +--allow-onelevel::
88 +--no-allow-onelevel::
89 + Controls whether one-level refnames are accepted (i.e.,
90 + refnames that do not contain multiple `/`-separated
91 + components). The default is `--no-allow-onelevel`.
92 +
93 +--refspec-pattern::
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 `{asterisk}`
97 + in place of a one full pathname component (e.g.,
98 + `foo/{asterisk}/bar` but not `foo/bar{asterisk}`).
99 +
100 EXAMPLES
101 --------
102
builtin/check-ref-format.c
+47 -9
@@ -8,7 +8,7 @@
8 #include "strbuf.h"
9
10 static const char builtin_check_ref_format_usage[] =
11 -"git check-ref-format [--print] <refname>\n"
11 +"git check-ref-format [--print] [options] <refname>\n"
12 " or: git check-ref-format --branch <branchname-shorthand>";
13
14 /*
@@ -45,27 +45,65 @@ static int check_ref_format_branch(const char *arg)
45 return 0;
46 }
47
48 -static int check_ref_format_print(const char *arg)
48 +static void refname_format_print(const char *arg)
49 {
50 char *refname = xmalloc(strlen(arg) + 1);
51
52 - if (check_ref_format(arg))
53 - return 1;
52 collapse_slashes(refname, arg);
53 printf("%s\n", refname);
56 - return 0;
54 }
55
56 +#define REFNAME_ALLOW_ONELEVEL 1
57 +#define REFNAME_REFSPEC_PATTERN 2
58 +
59 int cmd_check_ref_format(int argc, const char **argv, const char *prefix)
60 {
61 + int i;
62 + int print = 0;
63 + int flags = 0;
64 +
65 if (argc == 2 && !strcmp(argv[1], "-h"))
66 usage(builtin_check_ref_format_usage);
67
68 if (argc == 3 && !strcmp(argv[1], "--branch"))
69 return check_ref_format_branch(argv[2]);
66 - if (argc == 3 && !strcmp(argv[1], "--print"))
67 - return check_ref_format_print(argv[2]);
68 - if (argc != 2)
70 +
71 + for (i = 1; i < argc && argv[i][0] == '-'; i++) {
72 + if (!strcmp(argv[i], "--print"))
73 + print = 1;
74 + else if (!strcmp(argv[i], "--allow-onelevel"))
75 + flags |= REFNAME_ALLOW_ONELEVEL;
76 + else if (!strcmp(argv[i], "--no-allow-onelevel"))
77 + flags &= ~REFNAME_ALLOW_ONELEVEL;
78 + else if (!strcmp(argv[i], "--refspec-pattern"))
79 + flags |= REFNAME_REFSPEC_PATTERN;
80 + else
81 + usage(builtin_check_ref_format_usage);
82 + }
83 + if (! (i == argc - 1))
84 usage(builtin_check_ref_format_usage);
70 - return !!check_ref_format(argv[1]);
85 +
86 + switch (check_ref_format(argv[i])) {
87 + case CHECK_REF_FORMAT_OK:
88 + break;
89 + case CHECK_REF_FORMAT_ERROR:
90 + return 1;
91 + case CHECK_REF_FORMAT_ONELEVEL:
92 + if (!(flags & REFNAME_ALLOW_ONELEVEL))
93 + return 1;
94 + else
95 + break;
96 + case CHECK_REF_FORMAT_WILDCARD:
97 + if (!(flags & REFNAME_REFSPEC_PATTERN))
98 + return 1;
99 + else
100 + break;
101 + default:
102 + die("internal error: unexpected value from check_ref_format()");
103 + }
104 +
105 + if (print)
106 + refname_format_print(argv[i]);
107 +
108 + return 0;
109 }
t/t1402-check-ref-format.sh
+81 -7
@@ -5,25 +5,38 @@ test_description='Test git check-ref-format'
5 . ./test-lib.sh
6
7 valid_ref() {
8 - test_expect_success "ref name '$1' is valid" \
9 - "git check-ref-format '$1'"
8 + if test "$#" = 1
9 + then
10 + test_expect_success "ref name '$1' is valid" \
11 + "git check-ref-format '$1'"
12 + else
13 + test_expect_success "ref name '$1' is valid with options $2" \
14 + "git check-ref-format $2 '$1'"
15 + fi
16 }
17 invalid_ref() {
12 - test_expect_success "ref name '$1' is not valid" \
13 - "test_must_fail git check-ref-format '$1'"
18 + if test "$#" = 1
19 + then
20 + test_expect_success "ref name '$1' is invalid" \
21 + "test_must_fail git check-ref-format '$1'"
22 + else
23 + test_expect_success "ref name '$1' is invalid with options $2" \
24 + "test_must_fail git check-ref-format $2 '$1'"
25 + fi
26 }
27
28 invalid_ref ''
29 invalid_ref '/'
18 -valid_ref 'heads/foo'
19 -invalid_ref 'foo'
30 +invalid_ref '/' --allow-onelevel
31 valid_ref 'foo/bar/baz'
32 valid_ref 'refs///heads/foo'
33 invalid_ref 'heads/foo/'
34 valid_ref '/heads/foo'
35 valid_ref '///heads/foo'
25 -invalid_ref '/foo'
36 invalid_ref './foo'
37 +invalid_ref './foo/bar'
38 +invalid_ref 'foo/./bar'
39 +invalid_ref 'foo/bar/.'
40 invalid_ref '.refs/foo'
41 invalid_ref 'heads/foo..bar'
42 invalid_ref 'heads/foo?bar'
@@ -38,6 +51,67 @@ invalid_ref 'heads/foo\bar'
51 invalid_ref "$(printf 'heads/foo\t')"
52 invalid_ref "$(printf 'heads/foo\177')"
53 valid_ref "$(printf 'heads/fu\303\237')"
54 +invalid_ref 'heads/*foo/bar' --refspec-pattern
55 +invalid_ref 'heads/foo*/bar' --refspec-pattern
56 +invalid_ref 'heads/f*o/bar' --refspec-pattern
57 +
58 +ref='foo'
59 +invalid_ref "$ref"
60 +valid_ref "$ref" --allow-onelevel
61 +invalid_ref "$ref" --refspec-pattern
62 +valid_ref "$ref" '--refspec-pattern --allow-onelevel'
63 +
64 +ref='foo/bar'
65 +valid_ref "$ref"
66 +valid_ref "$ref" --allow-onelevel
67 +valid_ref "$ref" --refspec-pattern
68 +valid_ref "$ref" '--refspec-pattern --allow-onelevel'
69 +
70 +ref='foo/*'
71 +invalid_ref "$ref"
72 +invalid_ref "$ref" --allow-onelevel
73 +valid_ref "$ref" --refspec-pattern
74 +valid_ref "$ref" '--refspec-pattern --allow-onelevel'
75 +
76 +ref='*/foo'
77 +invalid_ref "$ref"
78 +invalid_ref "$ref" --allow-onelevel
79 +valid_ref "$ref" --refspec-pattern
80 +valid_ref "$ref" '--refspec-pattern --allow-onelevel'
81 +
82 +ref='foo/*/bar'
83 +invalid_ref "$ref"
84 +invalid_ref "$ref" --allow-onelevel
85 +valid_ref "$ref" --refspec-pattern
86 +valid_ref "$ref" '--refspec-pattern --allow-onelevel'
87 +
88 +ref='*'
89 +invalid_ref "$ref"
90 +
91 +#invalid_ref "$ref" --allow-onelevel
92 +test_expect_failure "ref name '$ref' is invalid with options --allow-onelevel" \
93 + "test_must_fail git check-ref-format --allow-onelevel '$ref'"
94 +
95 +invalid_ref "$ref" --refspec-pattern
96 +valid_ref "$ref" '--refspec-pattern --allow-onelevel'
97 +
98 +ref='foo/*/*'
99 +invalid_ref "$ref" --refspec-pattern
100 +invalid_ref "$ref" '--refspec-pattern --allow-onelevel'
101 +
102 +ref='*/foo/*'
103 +invalid_ref "$ref" --refspec-pattern
104 +invalid_ref "$ref" '--refspec-pattern --allow-onelevel'
105 +
106 +ref='*/*/foo'
107 +invalid_ref "$ref" --refspec-pattern
108 +invalid_ref "$ref" '--refspec-pattern --allow-onelevel'
109 +
110 +ref='/foo'
111 +invalid_ref "$ref"
112 +valid_ref "$ref" --allow-onelevel
113 +invalid_ref "$ref" --refspec-pattern
114 +valid_ref "$ref" '--refspec-pattern --allow-onelevel'
115
116 test_expect_success "check-ref-format --branch @{-1}" '
117 T=$(git write-tree) &&