Subject: [PATCH] attr: always treat the leading exclamation mark as literal

From: Nguyễn Thái Ngọc Duy <[email-masked]>
Date: Sat, 2 Mar 2013 11:18:33 +0700

8b1bd02 (Make !pattern in .gitattributes non-fatal - 2013-03-01)
raises the fact that even though '!' should have been a negative
pattern indicator, people have been misusing for plain '!' and old Git
versions accept that. This makes the leading '!' a de facto
literal. We can't ever make it negative indicator again because we
can't distinguish the literal '!' in existing repos and the negative
'!'  in future repos.

Acknowledge it in document and code that the leading '!' is forever
literal.

Signed-off-by: Nguyễn Thái Ngọc Duy <[email-masked]>
---
 Perhaps this on top of Thomas' patch? I see no way else that we can
 change the meaning of '!' again if such patterns are already stored
 in repositories.

 Documentation/gitattributes.txt | 3 ++-
 attr.c                          | 8 ++------
 dir.c                           | 7 ++++---
 dir.h                           | 6 +++++-
 t/t0003-attributes.sh           | 5 ++---
 5 files changed, 15 insertions(+), 14 deletions(-)

diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index 2698f63..02bff9d 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -56,7 +56,8 @@ 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.
+Unlike `.gitignore`, the leading `!` is treated as a literal
+exclamation, not an indicator of negative patterns.
 
 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 d181d98..838ec21 100644
--- a/attr.c
+++ b/attr.c
@@ -254,12 +254,8 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
 		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) {
-			warning(_("Negative patterns are ignored in git attributes\n"
-				  "Use '\\!' for literal leading exclamation."));
-			return NULL;
-		}
+				      &res->u.pat.nowildcardlen,
+				      1);
 	}
 	res->is_macro = is_macro;
 	res->num_attr = num_attr;
diff --git a/dir.c b/dir.c
index a473ca2..609ab99 100644
--- a/dir.c
+++ b/dir.c
@@ -311,13 +311,14 @@ static int no_wildcard(const char *string)
 void parse_exclude_pattern(const char **pattern,
 			   int *patternlen,
 			   int *flags,
-			   int *nowildcardlen)
+			   int *nowildcardlen,
+			   int gitattributes)
 {
 	const char *p = *pattern;
 	size_t i, len;
 
 	*flags = 0;
-	if (*p == '!') {
+	if (!gitattributes && *p == '!') {
 		*flags |= EXC_FLAG_NEGATIVE;
 		p++;
 	}
@@ -354,7 +355,7 @@ void add_exclude(const char *string, const char *base,
 	int flags;
 	int nowildcardlen;
 
-	parse_exclude_pattern(&string, &patternlen, &flags, &nowildcardlen);
+	parse_exclude_pattern(&string, &patternlen, &flags, &nowildcardlen, 0);
 	if (flags & EXC_FLAG_MUSTBEDIR) {
 		char *s;
 		x = xmalloc(sizeof(*x) + patternlen + 1);
diff --git a/dir.h b/dir.h
index f5c89e3..3aeb519 100644
--- a/dir.h
+++ b/dir.h
@@ -107,7 +107,11 @@ 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 parse_exclude_pattern(const char **string,
+				  int *patternlen,
+				  int *flags,
+				  int *nowildcardlen,
+				  int gitattributes);
 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 1035a14..7ca283f 100755
--- a/t/t0003-attributes.sh
+++ b/t/t0003-attributes.sh
@@ -197,9 +197,8 @@ test_expect_success 'root subdir attribute test' '
 '
 
 test_expect_success 'negative patterns' '
-	echo "!f test=bar" >.gitattributes &&
-	git check-attr test -- '"'"'!f'"'"' 2>errors &&
-	test_i18ngrep "Negative patterns are ignored" errors
+	echo "!f test=foo" >.gitattributes &&
+	attr_check "!f" foo
 '
 
 test_expect_success 'patterns starting with exclamation' '
-- 
1.8.1.2.536.gf441e6d