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>
This commit is contained in:
David Turner 2014-06-03 23:38:10 -04:00 committed by Junio C Hamano
parent 79dcccc503
commit dde8a902c7
2 changed files with 44 additions and 29 deletions

67
refs.c
View file

@ -6,8 +6,29 @@
#include "string-list.h" #include "string-list.h"
/* /*
* Make sure "ref" is something reasonable to have under ".git/refs/"; * How to handle various characters in refnames:
* We do not like it if: * 0: An acceptable character for refs
* 1: End-of-component
* 2: ., look for a preceding . to reject .. in refs
* 3: {, look for a preceding @ to reject @{ in refs
* 4: A bad character: ASCII control characters, "~", "^", ":" or SP
*/
static unsigned char refname_disposition[256] = {
1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 4, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 4, 4
};
/*
* Try to read one refname component from the front of refname.
* Return the length of the component found, or -1 if the component is
* not legal. It is legal if it is something reasonable to have under
* ".git/refs/"; We do not like it if:
* *
* - any path component of it begins with ".", or * - any path component of it begins with ".", or
* - it has double dots "..", or * - it has double dots "..", or
@ -16,41 +37,31 @@
* - it ends with ".lock" * - it ends with ".lock"
* - it contains a "\" (backslash) * - it contains a "\" (backslash)
*/ */
/* Return true iff ch is not allowed in reference names. */
static inline int bad_ref_char(int ch)
{
if (((unsigned) ch) <= ' ' || ch == 0x7f ||
ch == '~' || ch == '^' || ch == ':' || ch == '\\')
return 1;
/* 2.13 Pattern Matching Notation */
if (ch == '*' || ch == '?' || ch == '[') /* Unsupported */
return 1;
return 0;
}
/*
* Try to read one refname component from the front of refname. Return
* the length of the component found, or -1 if the component is not
* legal.
*/
static int check_refname_component(const char *refname, int flags) static int check_refname_component(const char *refname, int flags)
{ {
const char *cp; const char *cp;
char last = '\0'; char last = '\0';
for (cp = refname; ; cp++) { for (cp = refname; ; cp++) {
char ch = *cp; int ch = *cp & 255;
if (ch == '\0' || ch == '/') unsigned char disp = refname_disposition[ch];
switch (disp) {
case 1:
goto out;
case 2:
if (last == '.')
return -1; /* Refname contains "..". */
break; break;
if (bad_ref_char(ch)) case 3:
return -1; /* Illegal character in refname. */ if (last == '@')
if (last == '.' && ch == '.') return -1; /* Refname contains "@{". */
return -1; /* Refname contains "..". */ break;
if (last == '@' && ch == '{') case 4:
return -1; /* Refname contains "@{". */ return -1;
}
last = ch; last = ch;
} }
out:
if (cp == refname) if (cp == refname)
return 0; /* Component has zero length. */ return 0; /* Component has zero length. */
if (refname[0] == '.') { if (refname[0] == '.') {

View file

@ -5,7 +5,6 @@ test_description='refspec parsing'
. ./test-lib.sh . ./test-lib.sh
test_refspec () { test_refspec () {
kind=$1 refspec=$2 expect=$3 kind=$1 refspec=$2 expect=$3
git config remote.frotz.url "." && git config remote.frotz.url "." &&
git config --remove-section remote.frotz && git config --remove-section remote.frotz &&
@ -84,4 +83,9 @@ test_refspec push 'refs/heads/*/*/for-linus:refs/remotes/mine/*' invalid
test_refspec fetch 'refs/heads/*/for-linus:refs/remotes/mine/*' test_refspec fetch 'refs/heads/*/for-linus:refs/remotes/mine/*'
test_refspec push 'refs/heads/*/for-linus:refs/remotes/mine/*' test_refspec push 'refs/heads/*/for-linus:refs/remotes/mine/*'
good=$(printf '\303\204')
test_refspec fetch "refs/heads/${good}"
bad=$(printf '\011tab')
test_refspec fetch "refs/heads/${bad}" invalid
test_done test_done