Merge the attributes fix in from maint-1.6.6 branch
authorJunio C Hamano <gitster@pobox.com>
Tue, 10 Jan 2012 22:14:26 +0000 (14:14 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 10 Jan 2012 22:14:26 +0000 (14:14 -0800)
Signed-off-by: Junio C Hamano <gitster@pobox.com>
1  2 
Documentation/config.txt
attr.c
t/t0003-attributes.sh
diff --combined Documentation/config.txt
index bdd4cb83d347e1044d250ed9c2acab4545efaf13,27b57d226bc15e6246875fe5a8ae00df533c1c9d..3bcf660e0256b3706d8d78e3cb152dcf3b1b740a
@@@ -147,7 -147,7 +147,7 @@@ advice.*:
  
  core.fileMode::
        If false, the executable bit differences between the index and
 -      the working copy are ignored; useful on broken filesystems like FAT.
 +      the working tree are ignored; useful on broken filesystems like FAT.
        See linkgit:git-update-index[1].
  +
  The default is true, except linkgit:git-clone[1] or linkgit:git-init[1]
@@@ -179,7 -179,7 +179,7 @@@ is created
  
  core.trustctime::
        If false, the ctime differences between the index and the
 -      working copy are ignored; useful when the inode change time
 +      working tree are ignored; useful when the inode change time
        is regularly modified by something outside Git (file system
        crawlers and some backup systems).
        See linkgit:git-update-index[1]. True by default.
@@@ -292,7 -292,7 +292,7 @@@ core.ignoreStat:
        If true, commands which modify both the working tree and the index
        will mark the updated paths with the "assume unchanged" bit in the
        index. These marked files are then assumed to stay unchanged in the
 -      working copy, until you mark them otherwise manually - Git will not
 +      working tree, until you mark them otherwise manually - Git will not
        detect the file changes by lstat() calls. This is useful on systems
        where those are very slow, such as Microsoft Windows.
        See linkgit:git-update-index[1].
@@@ -1198,14 -1198,6 +1198,14 @@@ http.proxy:
        environment variable (see linkgit:curl[1]).  This can be overridden
        on a per-remote basis; see remote.<name>.proxy
  
 +http.cookiefile::
 +      File containing previously stored cookie lines which should be used
 +      in the git http session, if they match the server. The file format
 +      of the file to read cookies from should be plain HTTP headers or
 +      the Netscape/Mozilla cookie file format (see linkgit:curl[1]).
 +      NOTE that the file specified with http.cookiefile is only used as
 +      input. No cookies will be stored in the file.
 +
  http.sslVerify::
        Whether to verify the SSL certificate when fetching or pushing
        over HTTPS. Can be overridden by the 'GIT_SSL_NO_VERIFY' environment
@@@ -1711,7 -1703,8 +1711,8 @@@ rerere.enabled:
        conflict hunks can be resolved automatically, should they be
        encountered again.  By default, linkgit:git-rerere[1] is
        enabled if there is an `rr-cache` directory under the
-       `$GIT_DIR`.
+       `$GIT_DIR`, e.g. if "rerere" was previously used in the
+       repository.
  
  sendemail.identity::
        A configuration identity. When given, causes values in the
diff --combined attr.c
index 76b079f0f530e1372b2866f40cce21ec5266394c,2ce73651381c8ebfa6dfed6c2013b72059861b2b..96eda0ef078f998c020666f89eea29ac4b1bfb53
--- 1/attr.c
--- 2/attr.c
+++ b/attr.c
@@@ -1,17 -1,7 +1,17 @@@
 +/*
 + * Handle git attributes.  See gitattributes(5) for a description of
 + * the file syntax, and Documentation/technical/api-gitattributes.txt
 + * for a description of the API.
 + *
 + * One basic design decision here is that we are not going to support
 + * an insanely large number of attributes.
 + */
 +
  #define NO_THE_INDEX_COMPATIBILITY_MACROS
  #include "cache.h"
  #include "exec_cmd.h"
  #include "attr.h"
 +#include "dir.h"
  
  const char git_attr__true[] = "(builtin)true";
  const char git_attr__false[] = "\0(builtin)false";
@@@ -21,7 -11,14 +21,7 @@@ static const char git_attr__unknown[] 
  #define ATTR__UNSET NULL
  #define ATTR__UNKNOWN git_attr__unknown
  
 -static const char *attributes_file;
 -
 -/*
 - * The basic design decision here is that we are not going to have
 - * insanely large number of attributes.
 - *
 - * This is a randomly chosen prime.
 - */
 +/* This is a randomly chosen prime. */
  #define HASHSIZE 257
  
  #ifndef DEBUG_ATTR
@@@ -39,11 -36,6 +39,11 @@@ static int attr_nr
  static struct git_attr_check *check_all_attr;
  static struct git_attr *(git_attr_hash[HASHSIZE]);
  
 +char *git_attr_name(struct git_attr *attr)
 +{
 +      return attr->name;
 +}
 +
  static unsigned hash_name(const char *name, int namelen)
  {
        unsigned val = 0, c;
  static int invalid_attr_name(const char *name, int namelen)
  {
        /*
 -       * Attribute name cannot begin with '-' and from
 -       * [-A-Za-z0-9_.].  We'd specifically exclude '=' for now,
 -       * as we might later want to allow non-binary value for
 -       * attributes, e.g. "*.svg      merge=special-merge-program-for-svg"
 +       * Attribute name cannot begin with '-' and must consist of
 +       * characters from [-A-Za-z0-9_.].
         */
 -      if (*name == '-')
 +      if (namelen <= 0 || *name == '-')
                return -1;
        while (namelen--) {
                char ch = *name++;
@@@ -109,26 -103,22 +109,26 @@@ struct git_attr *git_attr(const char *n
        return git_attr_internal(name, strlen(name));
  }
  
 -/*
 - * .gitattributes file is one line per record, each of which is
 - *
 - * (1) glob pattern.
 - * (2) whitespace
 - * (3) whitespace separated list of attribute names, each of which
 - *     could be prefixed with '-' to mean "set to false", '!' to mean
 - *     "unset".
 - */
 -
  /* What does a matched pattern decide? */
  struct attr_state {
        struct git_attr *attr;
        const char *setto;
  };
  
 +/*
 + * One rule, as from a .gitattributes file.
 + *
 + * If is_macro is true, then u.attr is a pointer to the git_attr being
 + * defined.
 + *
 + * If is_macro is false, then u.pattern points at the filename pattern
 + * to which the rule applies.  (The memory pointed to is part of the
 + * memory block allocated for the match_attr instance.)
 + *
 + * In either case, num_attr is the number of attributes affected by
 + * this rule, and state is an array listing them.  The attributes are
 + * listed as they appear in the file (macros unexpanded).
 + */
  struct match_attr {
        union {
                char *pattern;
  
  static const char blank[] = " \t\r\n";
  
 +/*
 + * Parse a whitespace-delimited attribute state (i.e., "attr",
 + * "-attr", "!attr", or "attr=value") from the string starting at src.
 + * If e is not NULL, write the results to *e.  Return a pointer to the
 + * remainder of the string (with leading whitespace removed), or NULL
 + * if there was an error.
 + */
  static const char *parse_attr(const char *src, int lineno, const char *cp,
 -                            int *num_attr, struct match_attr *res)
 +                            struct attr_state *e)
  {
        const char *ep, *equals;
        int len;
                len = equals - cp;
        else
                len = ep - cp;
 -      if (!res) {
 +      if (!e) {
                if (*cp == '-' || *cp == '!') {
                        cp++;
                        len--;
                        return NULL;
                }
        } else {
 -              struct attr_state *e;
 -
 -              e = &(res->state[*num_attr]);
                if (*cp == '-' || *cp == '!') {
                        e->setto = (*cp == '-') ? ATTR__FALSE : ATTR__UNSET;
                        cp++;
                }
                e->attr = git_attr_internal(cp, len);
        }
 -      (*num_attr)++;
        return ep + strspn(ep, blank);
  }
  
@@@ -193,9 -180,10 +193,9 @@@ static struct match_attr *parse_attr_li
                                          int lineno, int macro_ok)
  {
        int namelen;
 -      int num_attr;
 -      const char *cp, *name;
 +      int num_attr, i;
 +      const char *cp, *name, *states;
        struct match_attr *res = NULL;
 -      int pass;
        int is_macro;
  
        cp = line + strspn(line, blank);
        else
                is_macro = 0;
  
 -      for (pass = 0; pass < 2; pass++) {
 -              /* pass 0 counts and allocates, pass 1 fills */
 -              num_attr = 0;
 -              cp = name + namelen;
 -              cp = cp + strspn(cp, blank);
 -              while (*cp) {
 -                      cp = parse_attr(src, lineno, cp, &num_attr, res);
 -                      if (!cp)
 -                              return NULL;
 -              }
 -              if (pass)
 -                      break;
 -              res = xcalloc(1,
 -                            sizeof(*res) +
 -                            sizeof(struct attr_state) * num_attr +
 -                            (is_macro ? 0 : namelen + 1));
 -              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;
 -              }
 -              res->is_macro = is_macro;
 -              res->num_attr = num_attr;
 +      states = name + namelen;
 +      states += strspn(states, blank);
 +
 +      /* First pass to count the attr_states */
 +      for (cp = states, num_attr = 0; *cp; num_attr++) {
 +              cp = parse_attr(src, lineno, cp, NULL);
 +              if (!cp)
 +                      return NULL;
        }
 +
 +      res = xcalloc(1,
 +                    sizeof(*res) +
 +                    sizeof(struct attr_state) * num_attr +
 +                    (is_macro ? 0 : namelen + 1));
 +      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;
 +      }
 +      res->is_macro = is_macro;
 +      res->num_attr = num_attr;
 +
 +      /* Second pass to fill the attr_states */
 +      for (cp = states, i = 0; *cp; i++) {
 +              cp = parse_attr(src, lineno, cp, &(res->state[i]));
 +      }
 +
        return res;
  }
  
@@@ -493,62 -478,68 +493,63 @@@ static int git_attr_system(void
        return !git_env_bool("GIT_ATTR_NOSYSTEM", 0);
  }
  
 -static int git_attr_config(const char *var, const char *value, void *dummy)
 -{
 -      if (!strcmp(var, "core.attributesfile"))
 -              return git_config_pathname(&attributes_file, var, value);
 -
 -      return 0;
 -}
 -
  static void bootstrap_attr_stack(void)
  {
-       if (!attr_stack) {
-               struct attr_stack *elem;
+       struct attr_stack *elem;
  
-               elem = read_attr_from_array(builtin_attr);
-               elem->origin = NULL;
-               elem->prev = attr_stack;
-               attr_stack = elem;
+       if (attr_stack)
+               return;
  
-               if (git_attr_system()) {
-                       elem = read_attr_from_file(git_etc_gitattributes(), 1);
-                       if (elem) {
-                               elem->origin = NULL;
-                               elem->prev = attr_stack;
-                               attr_stack = elem;
-                       }
-               }
+       elem = read_attr_from_array(builtin_attr);
+       elem->origin = NULL;
+       elem->prev = attr_stack;
+       attr_stack = elem;
  
-               if (git_attributes_file) {
-                       elem = read_attr_from_file(git_attributes_file, 1);
-                       if (elem) {
-                               elem->origin = NULL;
-                               elem->prev = attr_stack;
-                               attr_stack = elem;
-                       }
+       if (git_attr_system()) {
+               elem = read_attr_from_file(git_etc_gitattributes(), 1);
+               if (elem) {
+                       elem->origin = NULL;
+                       elem->prev = attr_stack;
+                       attr_stack = elem;
                }
+       }
  
-               if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
-                       elem = read_attr(GITATTRIBUTES_FILE, 1);
-                       elem->origin = xstrdup("");
 -      git_config(git_attr_config, NULL);
 -      if (attributes_file) {
 -              elem = read_attr_from_file(attributes_file, 1);
++      if (git_attributes_file) {
++              elem = read_attr_from_file(git_attributes_file, 1);
+               if (elem) {
+                       elem->origin = NULL;
                        elem->prev = attr_stack;
                        attr_stack = elem;
-                       debug_push(elem);
                }
+       }
  
-               elem = read_attr_from_file(git_path(INFOATTRIBUTES_FILE), 1);
-               if (!elem)
-                       elem = xcalloc(1, sizeof(*elem));
-               elem->origin = NULL;
+       if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
+               elem = read_attr(GITATTRIBUTES_FILE, 1);
 -              elem->origin = strdup("");
++              elem->origin = xstrdup("");
                elem->prev = attr_stack;
                attr_stack = elem;
+               debug_push(elem);
        }
+       elem = read_attr_from_file(git_path(INFOATTRIBUTES_FILE), 1);
+       if (!elem)
+               elem = xcalloc(1, sizeof(*elem));
+       elem->origin = NULL;
+       elem->prev = attr_stack;
+       attr_stack = elem;
  }
  
 -static void prepare_attr_stack(const char *path, int dirlen)
 +static void prepare_attr_stack(const char *path)
  {
        struct attr_stack *elem, *info;
 -      int len;
 -      struct strbuf pathbuf;
 +      int dirlen, len;
 +      const char *cp;
  
 -      strbuf_init(&pathbuf, dirlen+2+strlen(GITATTRIBUTES_FILE));
 +      cp = strrchr(path, '/');
 +      if (!cp)
 +              dirlen = 0;
 +      else
 +              dirlen = cp - path;
  
        /*
         * At the bottom of the attribute stack is the built-in
         * .gitattributes in deeper directories to shallower ones,
         * and finally use the built-in set as the default.
         */
 -      if (!attr_stack)
 -              bootstrap_attr_stack();
 +      bootstrap_attr_stack();
  
        /*
         * Pop the "info" one that is always at the top of the stack.
  
        /*
         * Pop the ones from directories that are not the prefix of
-        * the path we are checking.
+        * the path we are checking. Break out of the loop when we see
+        * the root one (whose origin is an empty string "") or the builtin
+        * one (whose origin is NULL) without popping it.
         */
-       while (attr_stack && attr_stack->origin) {
+       while (attr_stack->origin) {
                int namelen = strlen(attr_stack->origin);
  
                elem = attr_stack;
                if (namelen <= dirlen &&
-                   !strncmp(elem->origin, path, namelen))
+                   !strncmp(elem->origin, path, namelen) &&
+                   (!namelen || path[namelen] == '/'))
                        break;
  
                debug_pop(elem);
         * Read from parent directories and push them down
         */
        if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
+               /*
+                * bootstrap_attr_stack() should have added, and the
+                * above loop should have stopped before popping, the
+                * root element whose attr_stack->origin is set to an
+                * empty string.
+                */
 +              struct strbuf pathbuf = STRBUF_INIT;
 +
+               assert(attr_stack->origin);
                while (1) {
 -                      char *cp;
 -
                        len = strlen(attr_stack->origin);
                        if (dirlen <= len)
                                break;
 -                      strbuf_reset(&pathbuf);
 -                      strbuf_add(&pathbuf, path, dirlen);
 +                      cp = memchr(path + len + 1, '/', dirlen - len - 1);
 +                      if (!cp)
 +                              cp = path + dirlen;
 +                      strbuf_add(&pathbuf, path, cp - path);
                        strbuf_addch(&pathbuf, '/');
 -                      cp = strchr(pathbuf.buf + len + 1, '/');
 -                      strcpy(cp + 1, GITATTRIBUTES_FILE);
 +                      strbuf_addstr(&pathbuf, GITATTRIBUTES_FILE);
                        elem = read_attr(pathbuf.buf, 0);
 -                      *cp = '\0';
 -                      elem->origin = strdup(pathbuf.buf);
 +                      strbuf_setlen(&pathbuf, cp - path);
 +                      elem->origin = strbuf_detach(&pathbuf, NULL);
                        elem->prev = attr_stack;
                        attr_stack = elem;
                        debug_push(elem);
                }
 -      }
  
 -      strbuf_release(&pathbuf);
 +              strbuf_release(&pathbuf);
 +      }
  
        /*
         * Finally push the "info" one at the top of the stack.
@@@ -632,7 -633,7 +643,7 @@@ static int path_matches(const char *pat
                /* match basename */
                const char *basename = strrchr(pathname, '/');
                basename = basename ? basename + 1 : pathname;
 -              return (fnmatch(pattern, basename, 0) == 0);
 +              return (fnmatch_icase(pattern, basename, 0) == 0);
        }
        /*
         * match with FNM_PATHNAME; the pattern has base implicitly
                return 0;
        if (baselen != 0)
                baselen++;
 -      return fnmatch(pattern, pathname + baselen, FNM_PATHNAME) == 0;
 +      return fnmatch_icase(pattern, pathname + baselen, FNM_PATHNAME) == 0;
  }
  
  static int macroexpand_one(int attr_nr, int rem);
@@@ -713,30 -714,26 +724,30 @@@ static int macroexpand_one(int attr_nr
        return rem;
  }
  
 -int git_checkattr(const char *path, int num, struct git_attr_check *check)
 +/*
 + * Collect all attributes for path into the array pointed to by
 + * check_all_attr.
 + */
 +static void collect_all_attrs(const char *path)
  {
        struct attr_stack *stk;
 -      const char *cp;
 -      int dirlen, pathlen, i, rem;
 +      int i, pathlen, rem;
  
 -      bootstrap_attr_stack();
 +      prepare_attr_stack(path);
        for (i = 0; i < attr_nr; i++)
                check_all_attr[i].value = ATTR__UNKNOWN;
  
        pathlen = strlen(path);
 -      cp = strrchr(path, '/');
 -      if (!cp)
 -              dirlen = 0;
 -      else
 -              dirlen = cp - path;
 -      prepare_attr_stack(path, dirlen);
        rem = attr_nr;
        for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
                rem = fill(path, pathlen, stk, rem);
 +}
 +
 +int git_check_attr(const char *path, int num, struct git_attr_check *check)
 +{
 +      int i;
 +
 +      collect_all_attrs(path);
  
        for (i = 0; i < num; i++) {
                const char *value = check_all_attr[check[i].attr->attr_nr].value;
        return 0;
  }
  
 +int git_all_attrs(const char *path, int *num, struct git_attr_check **check)
 +{
 +      int i, count, j;
 +
 +      collect_all_attrs(path);
 +
 +      /* Count the number of attributes that are set. */
 +      count = 0;
 +      for (i = 0; i < attr_nr; i++) {
 +              const char *value = check_all_attr[i].value;
 +              if (value != ATTR__UNSET && value != ATTR__UNKNOWN)
 +                      ++count;
 +      }
 +      *num = count;
 +      *check = xmalloc(sizeof(**check) * count);
 +      j = 0;
 +      for (i = 0; i < attr_nr; i++) {
 +              const char *value = check_all_attr[i].value;
 +              if (value != ATTR__UNSET && value != ATTR__UNKNOWN) {
 +                      (*check)[j].attr = check_all_attr[i].attr;
 +                      (*check)[j].value = value;
 +                      ++j;
 +              }
 +      }
 +
 +      return 0;
 +}
 +
  void git_attr_set_direction(enum git_attr_direction new, struct index_state *istate)
  {
        enum git_attr_direction old = direction;
diff --combined t/t0003-attributes.sh
index 6946c4b82f9291f6618d49de2e3892a2c203d337,61b5a2eba633d52ebf53efb3c0539f8abf4094ea..19265c77cf0b1df828eea188d5f880cbb7497c63
@@@ -9,17 -9,16 +9,17 @@@ attr_check () 
        path="$1"
        expect="$2"
  
 -      git check-attr test -- "$path" >actual &&
 +      git $3 check-attr test -- "$path" >actual 2>err &&
        echo "$path: test: $2" >expect &&
 -      test_cmp expect actual
 +      test_cmp expect actual &&
 +      test_line_count = 0 err
  
  }
  
  
  test_expect_success 'setup' '
  
 -      mkdir -p a/b/d a/c &&
 +      mkdir -p a/b/d a/c &&
        (
                echo "[attr]notest !test"
                echo "f test=f"
@@@ -27,7 -26,6 +27,7 @@@
                echo "onoff test -test"
                echo "offon -test test"
                echo "no notest"
 +              echo "A/e/F test=A/e/F"
        ) >.gitattributes &&
        (
                echo "g test=a/g" &&
                echo "h test=a/b/h" &&
                echo "d/* test=a/b/d/*"
                echo "d/yes notest"
 -      ) >a/b/.gitattributes
 +      ) >a/b/.gitattributes &&
        (
                echo "global test=global"
 -      ) >"$HOME"/global-gitattributes
 +      ) >"$HOME"/global-gitattributes &&
 +      cat <<EOF >expect-all
 +f: test: f
 +a/f: test: f
 +a/c/f: test: f
 +a/g: test: a/g
 +a/b/g: test: a/b/g
 +b/g: test: unspecified
 +a/b/h: test: a/b/h
 +a/b/d/g: test: a/b/d/*
 +onoff: test: unset
 +offon: test: set
 +no: notest: set
 +no: test: unspecified
 +a/b/d/no: notest: set
 +a/b/d/no: test: a/b/d/*
 +a/b/d/yes: notest: set
 +a/b/d/yes: test: unspecified
 +EOF
 +
 +'
 +
 +test_expect_success 'command line checks' '
 +
 +      test_must_fail git check-attr &&
 +      test_must_fail git check-attr -- &&
 +      test_must_fail git check-attr test &&
 +      test_must_fail git check-attr test -- &&
 +      test_must_fail git check-attr -- f &&
 +      echo "f" | test_must_fail git check-attr --stdin &&
 +      echo "f" | test_must_fail git check-attr --stdin -- f &&
 +      echo "f" | test_must_fail git check-attr --stdin test -- f &&
 +      test_must_fail git check-attr "" -- f
  
  '
  
@@@ -94,84 -60,16 +94,94 @@@ test_expect_success 'attribute test' 
  
  '
  
 +test_expect_success 'attribute matching is case sensitive when core.ignorecase=0' '
 +
 +      test_must_fail attr_check F f "-c core.ignorecase=0" &&
 +      test_must_fail attr_check a/F f "-c core.ignorecase=0" &&
 +      test_must_fail attr_check a/c/F f "-c core.ignorecase=0" &&
 +      test_must_fail attr_check a/G a/g "-c core.ignorecase=0" &&
 +      test_must_fail attr_check a/B/g a/b/g "-c core.ignorecase=0" &&
 +      test_must_fail attr_check a/b/G a/b/g "-c core.ignorecase=0" &&
 +      test_must_fail attr_check a/b/H a/b/h "-c core.ignorecase=0" &&
 +      test_must_fail attr_check a/b/D/g "a/b/d/*" "-c core.ignorecase=0" &&
 +      test_must_fail attr_check oNoFf unset "-c core.ignorecase=0" &&
 +      test_must_fail attr_check oFfOn set "-c core.ignorecase=0" &&
 +      attr_check NO unspecified "-c core.ignorecase=0" &&
 +      test_must_fail attr_check a/b/D/NO "a/b/d/*" "-c core.ignorecase=0" &&
 +      attr_check a/b/d/YES a/b/d/* "-c core.ignorecase=0" &&
 +      test_must_fail attr_check a/E/f "A/e/F" "-c core.ignorecase=0"
 +
 +'
 +
 +test_expect_success 'attribute matching is case insensitive when core.ignorecase=1' '
 +
 +      attr_check F f "-c core.ignorecase=1" &&
 +      attr_check a/F f "-c core.ignorecase=1" &&
 +      attr_check a/c/F f "-c core.ignorecase=1" &&
 +      attr_check a/G a/g "-c core.ignorecase=1" &&
 +      attr_check a/B/g a/b/g "-c core.ignorecase=1" &&
 +      attr_check a/b/G a/b/g "-c core.ignorecase=1" &&
 +      attr_check a/b/H a/b/h "-c core.ignorecase=1" &&
 +      attr_check a/b/D/g "a/b/d/*" "-c core.ignorecase=1" &&
 +      attr_check oNoFf unset "-c core.ignorecase=1" &&
 +      attr_check oFfOn set "-c core.ignorecase=1" &&
 +      attr_check NO unspecified "-c core.ignorecase=1" &&
 +      attr_check a/b/D/NO "a/b/d/*" "-c core.ignorecase=1" &&
 +      attr_check a/b/d/YES unspecified "-c core.ignorecase=1" &&
 +      attr_check a/E/f "A/e/F" "-c core.ignorecase=1"
 +
 +'
 +
 +test_expect_success 'check whether FS is case-insensitive' '
 +      mkdir junk &&
 +      echo good >junk/CamelCase &&
 +      echo bad >junk/camelcase &&
 +      if test "$(cat junk/CamelCase)" != good
 +      then
 +              test_set_prereq CASE_INSENSITIVE_FS
 +      fi
 +'
 +
 +test_expect_success CASE_INSENSITIVE_FS 'additional case insensitivity tests' '
 +      test_must_fail attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=0" &&
 +      test_must_fail attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=0" &&
 +      attr_check A/b/h a/b/h "-c core.ignorecase=1" &&
 +      attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=1" &&
 +      attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=1"
 +'
 +
 +test_expect_success 'unnormalized paths' '
 +
 +      attr_check ./f f &&
 +      attr_check ./a/g a/g &&
 +      attr_check a/./g a/g &&
 +      attr_check a/c/../b/g a/b/g
 +
 +'
 +
 +test_expect_success 'relative paths' '
 +
 +      (cd a && attr_check ../f f) &&
 +      (cd a && attr_check f f) &&
 +      (cd a && attr_check i a/i) &&
 +      (cd a && attr_check g a/g) &&
 +      (cd a && attr_check b/g a/b/g) &&
 +      (cd b && attr_check ../a/f f) &&
 +      (cd b && attr_check ../a/g a/g) &&
 +      (cd b && attr_check ../a/b/g a/b/g)
 +
 +'
 +
+ test_expect_success 'prefixes are not confused with leading directories' '
+       attr_check a_plus/g unspecified &&
+       cat >expect <<-\EOF &&
+       a/g: test: a/g
+       a_plus/g: test: unspecified
+       EOF
+       git check-attr test a/g a_plus/g >actual &&
+       test_cmp expect actual
+ '
  test_expect_success 'core.attributesfile' '
        attr_check global unspecified &&
        git config core.attributesfile "$HOME/global-gitattributes" &&
  
  test_expect_success 'attribute test: read paths from stdin' '
  
 -      cat <<EOF > expect &&
 -f: test: f
 -a/f: test: f
 -a/c/f: test: f
 -a/g: test: a/g
 -a/b/g: test: a/b/g
 -b/g: test: unspecified
 -a/b/h: test: a/b/h
 -a/b/d/g: test: a/b/d/*
 -onoff: test: unset
 -offon: test: set
 -no: test: unspecified
 -a/b/d/no: test: a/b/d/*
 -a/b/d/yes: test: unspecified
 -EOF
 -
 +      grep -v notest < expect-all > expect &&
        sed -e "s/:.*//" < expect | git check-attr --stdin test > actual &&
        test_cmp expect actual
  '
  
 +test_expect_success 'attribute test: --all option' '
 +
 +      grep -v unspecified < expect-all | sort > expect &&
 +      sed -e "s/:.*//" < expect-all | uniq |
 +              git check-attr --stdin --all | sort > actual &&
 +      test_cmp expect actual
 +'
 +
  test_expect_success 'root subdir attribute test' '
  
        attr_check a/i a/i &&