git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 6/6] attr: more matching optimizations from .gitignore

From
Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Date
Oct 15, 2012, 06:24 UTC
Message-ID
<1350282279-4377-6-git-send-email-pclouds@gmail.com>
In-Reply-To
<1350282279-4377-1-git-send-email-pclouds@gmail.com>

.gitattributes and .gitignore share the same pattern syntax but has separate matching implementation. Over the years, ignore's implementation accumulates more optimizations while attr's stays the same.

This patch reuses the core matching functions that are also used by excluded_from_list. excluded_from_list and path_matches can't be merged due to differences in exclude and attr, for example:

* "!pattern" syntax is forbidden in .gitattributes.  As an attribute
  can be unset (i.e. set to a special value "false") or made back to
  unspecified (i.e. not even set to "false"), "!pattern attr" is unclear
  which one it means.
* we support attaching attributes to directories, but git-core
  internally does not currently make use of attributes on
  directories.
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/gitattributes.txt |  1 +
 attr.c                          | 52 ++++++++++++++++++++++++-----------------
 dir.c                           | 22 ++++++++---------
 dir.h                           | 11 +++++++++
 t/t0003-attributes.sh           | 10 ++++++++
 5 files changed, 64 insertions(+), 32 deletions(-)
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index 80120ea..b7c0e65 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -56,6 +56,7 @@ When more than one pattern matches the path, a later line
 overrides an earlier line.  This overriding is done per
 attribute.  The rules how the pattern matches paths are the
 same as in `.gitignore` files; see linkgit:gitignore[5].
+Unlike `.gitignore`, negative patterns are forbidden.
 
 When deciding what attributes are assigned to a path, git
 consults `$GIT_DIR/info/attributes` file (which has the highest
diff --git a/attr.c b/attr.c
index e7caee4..2fc6353 100644
--- a/attr.c
+++ b/attr.c
@@ -115,6 +115,13 @@ struct attr_state {
 	const char *setto;
 };
 
+struct pattern {
+	const char *pattern;
+	int patternlen;
+	int nowildcardlen;
+	int flags;		/* EXC_FLAG_* */
+};
+
 /*
  * One rule, as from a .gitattributes file.
  *
@@ -131,7 +138,7 @@ struct attr_state {
  */
 struct match_attr {
 	union {
-		char *pattern;
+		struct pattern pat;
 		struct git_attr *attr;
 	} u;
 	char is_macro;
@@ -241,9 +248,16 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
 	if (is_macro)
 		res->u.attr = git_attr_internal(name, namelen);
 	else {
-		res->u.pattern = (char *)&(res->state[num_attr]);
-		memcpy(res->u.pattern, name, namelen);
-		res->u.pattern[namelen] = 0;
+		char *p = (char *)&(res->state[num_attr]);
+		memcpy(p, name, namelen);
+		res->u.pat.pattern = p;
+		parse_exclude_pattern(&res->u.pat.pattern,
+				      &res->u.pat.patternlen,
+				      &res->u.pat.flags,
+				      &res->u.pat.nowildcardlen);
+		if (res->u.pat.flags & EXC_FLAG_NEGATIVE)
+			die(_("Negative patterns are forbidden in git attributes\n"
+			      "Use '\\!' for literal leading exclamation."));
 	}
 	res->is_macro = is_macro;
 	res->num_attr = num_attr;
@@ -640,25 +654,21 @@ static void prepare_attr_stack(const char *path)
 
 static int path_matches(const char *pathname, int pathlen,
 			const char *basename,
-			const char *pattern,
+			const struct pattern *pat,
 			const char *base, int baselen)
 {
-	if (!strchr(pattern, '/')) {
-		return (fnmatch_icase(pattern, basename, 0) == 0);
+	const char *pattern = pat->pattern;
+	int prefix = pat->nowildcardlen;
+
+	if (pat->flags & EXC_FLAG_NODIR) {
+		return match_basename(basename,
+				      pathlen - (basename - pathname),
+				      pattern, prefix,
+				      pat->patternlen, pat->flags);
 	}
-	/*
-	 * match with FNM_PATHNAME; the pattern has base implicitly
-	 * in front of it.
-	 */
-	if (*pattern == '/')
-		pattern++;
-	if (pathlen < baselen ||
-	    (baselen && pathname[baselen] != '/') ||
-	    strncmp(pathname, base, baselen))
-		return 0;
-	if (baselen != 0)
-		baselen++;
-	return fnmatch_icase(pattern, pathname + baselen, FNM_PATHNAME) == 0;
+	return match_pathname(pathname, pathlen,
+			      base, baselen,
+			      pattern, prefix, pat->patternlen, pat->flags);
 }
 
 static int macroexpand_one(int attr_nr, int rem);
@@ -696,7 +706,7 @@ static int fill(const char *path, int pathlen, const char *basename,
 		if (a->is_macro)
 			continue;
 		if (path_matches(path, pathlen, basename,
-				 a->u.pattern, base, stk->originlen))
+				 &a->u.pat, base, stk->originlen))
 			rem = fill_one("fill", a, rem);
 	}
 	return rem;
diff --git a/dir.c b/dir.c
index c4e64a5..ee8e711 100644
--- a/dir.c
+++ b/dir.c
@@ -308,10 +308,10 @@ static int no_wildcard(const char *string)
 	return string[simple_length(string)] == '\0';
 }
 
-static void parse_exclude_pattern(const char **pattern,
-				  int *patternlen,
-				  int *flags,
-				  int *nowildcardlen)
+void parse_exclude_pattern(const char **pattern,
+			   int *patternlen,
+			   int *flags,
+			   int *nowildcardlen)
 {
 	const char *p = *pattern;
 	size_t i, len;
@@ -530,9 +530,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
 	dir->basebuf[baselen] = '\0';
 }
 
-static int match_basename(const char *basename, int basenamelen,
-			  const char *pattern, int prefix, int patternlen,
-			  int flags)
+int match_basename(const char *basename, int basenamelen,
+		   const char *pattern, int prefix, int patternlen,
+		   int flags)
 {
 	if (prefix == patternlen) {
 		if (!strcmp_icase(pattern, basename))
@@ -549,10 +549,10 @@ static int match_basename(const char *basename, int basenamelen,
 	return 0;
 }
 
-static int match_pathname(const char *pathname, int pathlen,
-			  const char *base, int baselen,
-			  const char *pattern, int prefix, int patternlen,
-			  int flags)
+int match_pathname(const char *pathname, int pathlen,
+		   const char *base, int baselen,
+		   const char *pattern, int prefix, int patternlen,
+		   int flags)
 {
 	const char *name;
 	int namelen;
diff --git a/dir.h b/dir.h
index 41ea32d..f5c89e3 100644
--- a/dir.h
+++ b/dir.h
@@ -81,6 +81,16 @@ extern int excluded_from_list(const char *pathname, int pathlen, const char *bas
 struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len);
 
 /*
+ * these implement the matching logic for dir.c:excluded_from_list and
+ * attr.c:path_matches()
+ */
+extern int match_basename(const char *, int,
+			  const char *, int, int, int);
+extern int match_pathname(const char *, int,
+			  const char *, int,
+			  const char *, int, int, int);
+
+/*
  * The excluded() API is meant for callers that check each level of leading
  * directory hierarchies with excluded() to avoid recursing into excluded
  * directories.  Callers that do not do so should use this API instead.
@@ -97,6 +107,7 @@ extern int path_excluded(struct path_exclude_check *, const char *, int namelen,
 extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
 					  char **buf_p, struct exclude_list *which, int check_index);
 extern void add_excludes_from_file(struct dir_struct *, const char *fname);
+extern void parse_exclude_pattern(const char **string, int *patternlen, int *flags, int *nowildcardlen);
 extern void add_exclude(const char *string, const char *base,
 			int baselen, struct exclude_list *which);
 extern void free_excludes(struct exclude_list *el);
diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
index 51f3045..f6c21ea 100755
--- a/t/t0003-attributes.sh
+++ b/t/t0003-attributes.sh
@@ -206,6 +206,16 @@ test_expect_success 'root subdir attribute test' '
 	attr_check subdir/a/i unspecified
 '
 
+test_expect_success 'negative patterns' '
+	echo "!f test=bar" >.gitattributes &&
+	test_must_fail git check-attr test -- f
+'
+
+test_expect_success 'patterns starting with exclamation' '
+	echo "\!f test=foo" >.gitattributes &&
+	attr_check "!f" foo
+'
+
 test_expect_success 'setup bare' '
 	git clone --bare . bare.git &&
 	cd bare.git
-- 
1.8.0.rc0.29.g1fdd78f
Previous: Nguyễn Thái Ngọc DuyNext: Nguyễn Thái Ngọc Duy
Message 7 of 55 in “nd/attr-match-more-optim, nd/wildmatch and as/check-ignore”
  1. Nguyễn Thái Ngọc DuyOct 15, 2012
  2. 1/6 exclude: stricten a length check in EXC_FLAG_ENDSWITH caseNguyễn Thái Ngọc Duy, Oct 15, 2012
  3. 2/6 exclude: split basename matching code into a separate functionNguyễn Thái Ngọc Duy, Oct 15, 2012
  4. 3/6 exclude: fix a bug in prefix compare optimizationNguyễn Thái Ngọc Duy, Oct 15, 2012
  5. 4/6 exclude: split pathname matching code into a separate functionNguyễn Thái Ngọc Duy, Oct 15, 2012
  6. 5/6 gitignore: make pattern parsing code a separate functionNguyễn Thái Ngọc Duy, Oct 15, 2012
  7. 6/6 attr: more matching optimizations from .gitignoreNguyễn Thái Ngọc Duy, Oct 15, 2012
  8. 01/13 ctype: make sane_ctype[] const arrayNguyễn Thái Ngọc Duy, Oct 15, 2012
  9. 02/13 ctype: support iscntrl, ispunct, isxdigit and isprintNguyễn Thái Ngọc Duy, Oct 15, 2012
  10. 03/13 Import wildmatch from rsyncNguyễn Thái Ngọc Duy, Oct 15, 2012
  11. 04/13 wildmatch: remove unnecessary functionsNguyễn Thái Ngọc Duy, Oct 15, 2012
  12. 05/13 wildmatch: follow Git's coding conventionNguyễn Thái Ngọc Duy, Oct 15, 2012
  13. 06/13 Integrate wildmatch to gitNguyễn Thái Ngọc Duy, Oct 15, 2012
  14. 07/13 t3070: disable unreliable fnmatch testsNguyễn Thái Ngọc Duy, Oct 15, 2012
  15. 08/13 wildmatch: make wildmatch's return value compatible with fnmatchNguyễn Thái Ngọc Duy, Oct 15, 2012
  16. 09/13 wildmatch: remove static variable force_lower_caseNguyễn Thái Ngọc Duy, Oct 15, 2012
  17. 10/13 wildmatch: fix case-insensitive matchingNguyễn Thái Ngọc Duy, Oct 15, 2012
  18. 11/13 wildmatch: adjust "**" behaviorNguyễn Thái Ngọc Duy, Oct 15, 2012
  19. 12/13 wildmatch: make /**/ match zero or more directoriesNguyễn Thái Ngọc Duy, Oct 15, 2012
  20. 13/13 Support "**" wildcard in .gitignore and .gitattributesNguyễn Thái Ngọc Duy, Oct 15, 2012
  21. 14/13 wildmatch: fix tests that fail on Windows due to path manglingJohannes Sixt, Nov 4, 2012
  22. Nguyen Thai Ngoc DuyNov 6, 2012
  23. Johannes SixtNov 7, 2012
  24. 14/13 test-wildmatch: fix tests that fail on Windows due to path manglingNguyễn Thái Ngọc Duy, Nov 11, 2012
  25. 15/13 compat/fnmatch: fix off-by-one character class's length checkNguyễn Thái Ngọc Duy, Nov 11, 2012
  26. Johannes SixtNov 13, 2012
  27. Johannes SixtNov 20, 2012
  28. Junio C HamanoNov 11, 2012
  29. 14/13 test-wildmatch: avoid Windows path manglingNguyễn Thái Ngọc Duy, Nov 13, 2012
  30. Johannes SixtNov 13, 2012
  31. 14/13 test-wildmatch: avoid Windows path manglingJohannes Sixt, Nov 20, 2012
  32. Junio C HamanoNov 20, 2012
  33. 14/13 test-wildmatch: avoid Windows path manglingJohannes Sixt, Nov 21, 2012
  34. 01/12 dir.c: rename cryptic 'which' variable to more consistent nameNguyễn Thái Ngọc Duy, Oct 15, 2012
  35. 02/12 dir.c: rename path_excluded() to is_path_excluded()Nguyễn Thái Ngọc Duy, Oct 15, 2012
  36. 03/12 dir.c: rename excluded_from_list() to is_excluded_from_list()Nguyễn Thái Ngọc Duy, Oct 15, 2012
  37. 04/12 dir.c: rename excluded() to is_excluded()Nguyễn Thái Ngọc Duy, Oct 15, 2012
  38. 05/12 dir.c: refactor is_excluded_from_list()Nguyễn Thái Ngọc Duy, Oct 15, 2012
  39. 06/12 dir.c: refactor is_excluded()Nguyễn Thái Ngọc Duy, Oct 15, 2012
  40. 07/12 dir.c: refactor is_path_excluded()Nguyễn Thái Ngọc Duy, Oct 15, 2012
  41. 08/12 dir.c: keep track of where patterns came fromNguyễn Thái Ngọc Duy, Oct 15, 2012
  42. 09/12 dir.c: refactor treat_gitlinks()Nguyễn Thái Ngọc Duy, Oct 15, 2012
  43. 10/12 pathspec.c: move reusable code from builtin/add.cNguyễn Thái Ngọc Duy, Oct 15, 2012
  44. 11/12 dir.c: provide free_directory() for reclaiming dir_struct memoryNguyễn Thái Ngọc Duy, Oct 15, 2012
  45. 12/12 Add git-check-ignore sub-commandNguyễn Thái Ngọc Duy, Oct 15, 2012
  46. Junio C HamanoOct 15, 2012
  47. Nguyen Thai Ngoc DuyOct 16, 2012
  48. Adam SpiersOct 16, 2012
  49. Nguyen Thai Ngoc DuyOct 16, 2012
  50. Adam SpiersOct 16, 2012
  51. Junio C HamanoOct 16, 2012
  52. Adam SpiersDec 17, 2012
  53. t0007: fix tests on WindowsJohannes Sixt, Nov 4, 2012
  54. Jeff KingNov 8, 2012
  55. Junio C HamanoOct 15, 2012

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.