pathspec: apply "*.c" optimization from exclude

When a pattern contains only a single asterisk as wildcard, e.g. "foo*bar", after literally comparing the leading part "foo" with the string, we can compare the tail of the string and make sure it matches "bar", instead of running fnmatch() on "*bar" against the remainder of the string. -O2 build on linux-2.6, without the patch: $ time git rev-list --quiet HEAD -- '*.c' real 0m40.770s user 0m40.290s sys 0m0.256s With the patch $ time ~/w/git/git rev-list --quiet HEAD -- '*.c' real 0m34.288s user 0m33.997s sys 0m0.205s The above command is not supposed to be widely popular. It's chosen because it exercises pathspec matching a lot. The point is it cuts down matching time for popular patterns like *.c, which could be used as pathspec in other places. Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Nguyễn Thái Ngọc Duy committed Nov 24, 2012 at 11:33 UTC 8c6abbcd2720dd80b1c9b6db8104c4dcc7cba6c7
4 files changed +24 -4
cache.h
+3
@@ -473,6 +473,8 @@ extern int index_name_is_other(const struct index_state *, const char *, int);
473 extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
474 extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
475
476 +#define PATHSPEC_ONESTAR 1 /* the pathspec pattern sastisfies GFNM_ONESTAR */
477 +
478 struct pathspec {
479 const char **raw; /* get_pathspec() result, not freed by free_pathspec() */
480 int nr;
@@ -483,6 +485,7 @@ struct pathspec {
485 const char *match;
486 int len;
487 int nowildcard_len;
488 + int flags;
489 } *items;
490 };
491
dir.c
+16 -2
@@ -46,6 +46,13 @@ inline int git_fnmatch(const char *pattern, const char *string,
46 pattern += prefix;
47 string += prefix;
48 }
49 + if (flags & GFNM_ONESTAR) {
50 + int pattern_len = strlen(++pattern);
51 + int string_len = strlen(string);
52 + return string_len < pattern_len ||
53 + strcmp(pattern,
54 + string + string_len - pattern_len);
55 + }
56 return fnmatch(pattern, string, fnm_flags);
57 }
58
@@ -246,7 +253,9 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix,
253 }
254
255 if (item->nowildcard_len < item->len &&
249 - !git_fnmatch(match, name, 0, item->nowildcard_len - prefix))
256 + !git_fnmatch(match, name,
257 + item->flags & PATHSPEC_ONESTAR ? GFNM_ONESTAR : 0,
258 + item->nowildcard_len - prefix))
259 return MATCHED_FNMATCH;
260
261 return 0;
@@ -1446,8 +1455,13 @@ int init_pathspec(struct pathspec *pathspec, const char **paths)
1455 item->match = path;
1456 item->len = strlen(path);
1457 item->nowildcard_len = simple_length(path);
1449 - if (item->nowildcard_len < item->len)
1458 + item->flags = 0;
1459 + if (item->nowildcard_len < item->len) {
1460 pathspec->has_wildcard = 1;
1461 + if (path[item->nowildcard_len] == '*' &&
1462 + no_wildcard(path + item->nowildcard_len + 1))
1463 + item->flags |= PATHSPEC_ONESTAR;
1464 + }
1465 }
1466
1467 qsort(pathspec->items, pathspec->nr,
dir.h
+1
@@ -143,6 +143,7 @@ extern int fnmatch_icase(const char *pattern, const char *string, int flags);
143 * The prefix part of pattern must not contains wildcards.
144 */
145 #define GFNM_PATHNAME 1 /* similar to FNM_PATHNAME */
146 +#define GFNM_ONESTAR 2 /* there is only _one_ wildcard, a star */
147
148 extern int git_fnmatch(const char *pattern, const char *string,
149 int flags, int prefix);
tree-walk.c
+4 -2
@@ -628,7 +628,8 @@ enum interesting tree_entry_interesting(const struct name_entry *entry,
628
629 if (item->nowildcard_len < item->len) {
630 if (!git_fnmatch(match + baselen, entry->path,
631 - 0, item->nowildcard_len - baselen))
631 + item->flags & PATHSPEC_ONESTAR ? GFNM_ONESTAR : 0,
632 + item->nowildcard_len - baselen))
633 return entry_interesting;
634
635 /*
@@ -654,7 +655,8 @@ match_wildcards:
655 strbuf_add(base, entry->path, pathlen);
656
657 if (!git_fnmatch(match, base->buf + base_offset,
657 - 0, item->nowildcard_len)) {
658 + item->flags & PATHSPEC_ONESTAR ? GFNM_ONESTAR : 0,
659 + item->nowildcard_len)) {
660 strbuf_setlen(base, base_offset + baselen);
661 return entry_interesting;
662 }