refs.c: optimize check_refname_component()

In a repository with many refs, check_refname_component can be a major contributor to the runtime of some git commands. One such command is git rev-parse HEAD Timings for one particular repo, with about 60k refs, almost all packed, are: Old: 35 ms New: 29 ms Many other commands which read refs are also sped up. Signed-off-by: David Turner <dturner@twitter.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

David Turner committed Jun 3, 2014 at 23:38 UTC dde8a902c774268628015705a7767f61af18b865
2 files changed +44 -29
refs.c
+39 -28
@@ -6,8 +6,29 @@
6 #include "string-list.h"
7
8 /*
9 - * Make sure "ref" is something reasonable to have under ".git/refs/";
10 - * We do not like it if:
9 + * How to handle various characters in refnames:
10 + * 0: An acceptable character for refs
11 + * 1: End-of-component
12 + * 2: ., look for a preceding . to reject .. in refs
13 + * 3: {, look for a preceding @ to reject @{ in refs
14 + * 4: A bad character: ASCII control characters, "~", "^", ":" or SP
15 + */
16 +static unsigned char refname_disposition[256] = {
17 + 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
18 + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
19 + 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2, 1,
20 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4,
21 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
22 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 4, 0,
23 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
24 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 4, 4
25 +};
26 +
27 +/*
28 + * Try to read one refname component from the front of refname.
29 + * Return the length of the component found, or -1 if the component is
30 + * not legal. It is legal if it is something reasonable to have under
31 + * ".git/refs/"; We do not like it if:
32 *
33 * - any path component of it begins with ".", or
34 * - it has double dots "..", or
@@ -16,41 +37,31 @@
37 * - it ends with ".lock"
38 * - it contains a "\" (backslash)
39 */
19 -
20 -/* Return true iff ch is not allowed in reference names. */
21 -static inline int bad_ref_char(int ch)
22 -{
23 - if (((unsigned) ch) <= ' ' || ch == 0x7f ||
24 - ch == '~' || ch == '^' || ch == ':' || ch == '\\')
25 - return 1;
26 - /* 2.13 Pattern Matching Notation */
27 - if (ch == '*' || ch == '?' || ch == '[') /* Unsupported */
28 - return 1;
29 - return 0;
30 -}
31 -
32 -/*
33 - * Try to read one refname component from the front of refname. Return
34 - * the length of the component found, or -1 if the component is not
35 - * legal.
36 - */
40 static int check_refname_component(const char *refname, int flags)
41 {
42 const char *cp;
43 char last = '\0';
44
45 for (cp = refname; ; cp++) {
43 - char ch = *cp;
44 - if (ch == '\0' || ch == '/')
46 + int ch = *cp & 255;
47 + unsigned char disp = refname_disposition[ch];
48 + switch (disp) {
49 + case 1:
50 + goto out;
51 + case 2:
52 + if (last == '.')
53 + return -1; /* Refname contains "..". */
54 + break;
55 + case 3:
56 + if (last == '@')
57 + return -1; /* Refname contains "@{". */
58 break;
46 - if (bad_ref_char(ch))
47 - return -1; /* Illegal character in refname. */
48 - if (last == '.' && ch == '.')
49 - return -1; /* Refname contains "..". */
50 - if (last == '@' && ch == '{')
51 - return -1; /* Refname contains "@{". */
59 + case 4:
60 + return -1;
61 + }
62 last = ch;
63 }
64 +out:
65 if (cp == refname)
66 return 0; /* Component has zero length. */
67 if (refname[0] == '.') {
t/t5511-refspec.sh
+5 -1
@@ -5,7 +5,6 @@ test_description='refspec parsing'
5 . ./test-lib.sh
6
7 test_refspec () {
8 -
8 kind=$1 refspec=$2 expect=$3
9 git config remote.frotz.url "." &&
10 git config --remove-section remote.frotz &&
@@ -84,4 +83,9 @@ test_refspec push 'refs/heads/*/*/for-linus:refs/remotes/mine/*' invalid
83 test_refspec fetch 'refs/heads/*/for-linus:refs/remotes/mine/*'
84 test_refspec push 'refs/heads/*/for-linus:refs/remotes/mine/*'
85
86 +good=$(printf '\303\204')
87 +test_refspec fetch "refs/heads/${good}"
88 +bad=$(printf '\011tab')
89 +test_refspec fetch "refs/heads/${bad}" invalid
90 +
91 test_done