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
 
        const char *setto;
 };
 
+struct pattern {
+       const char *pattern;
+       int patternlen;
+       int nowildcardlen;
+       int flags;              /* EXC_FLAG_* */
+};
+
 /*
  * One rule, as from a .gitattributes file.
  *
  */
 struct match_attr {
        union {
-               char *pattern;
+               struct pattern pat;
                struct git_attr *attr;
        } u;
        char is_macro;
        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;
 
 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);
                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;
 
        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;
        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))
        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;
 
                              int *dtype, struct exclude_list *el);
 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
 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);
 
        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