Merge branch 'as/dir-c-cleanup'
authorJunio C Hamano <gitster@pobox.com>
Thu, 10 Jan 2013 21:47:25 +0000 (13:47 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 10 Jan 2013 21:47:25 +0000 (13:47 -0800)
Refactor and generally clean up the directory traversal API
implementation.

* as/dir-c-cleanup:
dir.c: rename free_excludes() to clear_exclude_list()
dir.c: refactor is_path_excluded()
dir.c: refactor is_excluded()
dir.c: refactor is_excluded_from_list()
dir.c: rename excluded() to is_excluded()
dir.c: rename excluded_from_list() to is_excluded_from_list()
dir.c: rename path_excluded() to is_path_excluded()
dir.c: rename cryptic 'which' variable to more consistent name
Improve documentation and comments regarding directory traversal API
api-directory-listing.txt: update to match code

1  2 
attr.c
builtin/add.c
builtin/ls-files.c
dir.c
dir.h
unpack-trees.c
diff --combined attr.c
index 466c93fa50976dc6a1a3019bdf47accc406acc32,53625630882fb6c3ba772f2307ae34950a60a4a0..d6d71901b24855a1d33ef2edeb8fbc6ce8e969dc
--- 1/attr.c
--- 2/attr.c
+++ b/attr.c
@@@ -284,7 -284,7 +284,7 @@@ static struct match_attr *parse_attr_li
   * (reading the file from top to bottom), .gitattribute of the root
   * directory (again, reading the file from top to bottom) down to the
   * current directory, and then scan the list backwards to find the first match.
-  * This is exactly the same as what excluded() does in dir.c to deal with
+  * This is exactly the same as what is_excluded() does in dir.c to deal with
   * .gitignore
   */
  
@@@ -321,7 -321,7 +321,7 @@@ static void free_attr_elem(struct attr_
  }
  
  static const char *builtin_attr[] = {
 -      "[attr]binary -diff -text",
 +      "[attr]binary -diff -merge -text",
        NULL,
  };
  
@@@ -367,11 -367,8 +367,11 @@@ static struct attr_stack *read_attr_fro
        char buf[2048];
        int lineno = 0;
  
 -      if (!fp)
 +      if (!fp) {
 +              if (errno != ENOENT && errno != ENOTDIR)
 +                      warn_on_inaccessible(path);
                return NULL;
 +      }
        res = xcalloc(1, sizeof(*res));
        while (fgets(buf, sizeof(buf), fp))
                handle_attr_line(res, buf, path, ++lineno, macro_ok);
@@@ -515,7 -512,6 +515,7 @@@ static int git_attr_system(void
  static void bootstrap_attr_stack(void)
  {
        struct attr_stack *elem;
 +      char *xdg_attributes_file;
  
        if (attr_stack)
                return;
                }
        }
  
 +      if (!git_attributes_file) {
 +              home_config_paths(NULL, &xdg_attributes_file, "attributes");
 +              git_attributes_file = xdg_attributes_file;
 +      }
        if (git_attributes_file) {
                elem = read_attr_from_file(git_attributes_file, 1);
                if (elem) {
        attr_stack = elem;
  }
  
 +static const char *find_basename(const char *path)
 +{
 +      const char *cp, *last_slash = NULL;
 +
 +      for (cp = path; *cp; cp++) {
 +              if (*cp == '/' && cp[1])
 +                      last_slash = cp;
 +      }
 +      return last_slash ? last_slash + 1 : path;
 +}
 +
  static void prepare_attr_stack(const char *path)
  {
        struct attr_stack *elem, *info;
        int dirlen, len;
        const char *cp;
  
 -      cp = strrchr(path, '/');
 -      if (!cp)
 -              dirlen = 0;
 -      else
 -              dirlen = cp - path;
 +      dirlen = find_basename(path) - path;
  
        /*
         * At the bottom of the attribute stack is the built-in
@@@ -675,10 -660,6 +675,10 @@@ static int path_matches(const char *pat
        const char *pattern = pat->pattern;
        int prefix = pat->nowildcardlen;
  
 +      if ((pat->flags & EXC_FLAG_MUSTBEDIR) &&
 +          ((!pathlen) || (pathname[pathlen-1] != '/')))
 +              return 0;
 +
        if (pat->flags & EXC_FLAG_NODIR) {
                return match_basename(basename,
                                      pathlen - (basename - pathname),
@@@ -769,7 -750,9 +769,7 @@@ static void collect_all_attrs(const cha
        for (i = 0; i < attr_nr; i++)
                check_all_attr[i].value = ATTR__UNKNOWN;
  
 -      basename = strrchr(path, '/');
 -      basename = basename ? basename + 1 : path;
 -
 +      basename = find_basename(path);
        pathlen = strlen(path);
        rem = attr_nr;
        for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
diff --combined builtin/add.c
index e664100c7122d6c4116763716c2260756feffac2,c689f3725e84e88765ff7a0b79fc5ad338048fce..075312afcd8813c3a7dbf08f6be519a37498fe4f
@@@ -16,7 -16,7 +16,7 @@@
  #include "bulk-checkin.h"
  
  static const char * const builtin_add_usage[] = {
 -      "git add [options] [--] <filepattern>...",
 +      N_("git add [options] [--] <filepattern>..."),
        NULL
  };
  static int patch_interactive, add_interactive, edit_interactive;
@@@ -260,7 -260,7 +260,7 @@@ int interactive_add(int argc, const cha
  
  static int edit_patch(int argc, const char **argv, const char *prefix)
  {
 -      char *file = xstrdup(git_path("ADD_EDIT.patch"));
 +      char *file = git_pathdup("ADD_EDIT.patch");
        const char *apply_argv[] = { "apply", "--recount", "--cached",
                NULL, NULL };
        struct child_process child;
                die (_("Could not apply '%s'"), file);
  
        unlink(file);
 +      free(file);
        return 0;
  }
  
@@@ -316,19 -315,19 +316,19 @@@ static int verbose = 0, show_only = 0, 
  static int ignore_add_errors, addremove, intent_to_add, ignore_missing = 0;
  
  static struct option builtin_add_options[] = {
 -      OPT__DRY_RUN(&show_only, "dry run"),
 -      OPT__VERBOSE(&verbose, "be verbose"),
 +      OPT__DRY_RUN(&show_only, N_("dry run")),
 +      OPT__VERBOSE(&verbose, N_("be verbose")),
        OPT_GROUP(""),
 -      OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"),
 -      OPT_BOOLEAN('p', "patch", &patch_interactive, "select hunks interactively"),
 -      OPT_BOOLEAN('e', "edit", &edit_interactive, "edit current diff and apply"),
 -      OPT__FORCE(&ignored_too, "allow adding otherwise ignored files"),
 -      OPT_BOOLEAN('u', "update", &take_worktree_changes, "update tracked files"),
 -      OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, "record only the fact that the path will be added later"),
 -      OPT_BOOLEAN('A', "all", &addremove, "add changes from all tracked and untracked files"),
 -      OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"),
 -      OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, "just skip files which cannot be added because of errors"),
 -      OPT_BOOLEAN( 0 , "ignore-missing", &ignore_missing, "check if - even missing - files are ignored in dry run"),
 +      OPT_BOOLEAN('i', "interactive", &add_interactive, N_("interactive picking")),
 +      OPT_BOOLEAN('p', "patch", &patch_interactive, N_("select hunks interactively")),
 +      OPT_BOOLEAN('e', "edit", &edit_interactive, N_("edit current diff and apply")),
 +      OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files")),
 +      OPT_BOOLEAN('u', "update", &take_worktree_changes, N_("update tracked files")),
 +      OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, N_("record only the fact that the path will be added later")),
 +      OPT_BOOLEAN('A', "all", &addremove, N_("add changes from all tracked and untracked files")),
 +      OPT_BOOLEAN( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")),
 +      OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")),
 +      OPT_BOOLEAN( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")),
        OPT_END(),
  };
  
@@@ -454,7 -453,7 +454,7 @@@ int cmd_add(int argc, const char **argv
                            && !file_exists(pathspec[i])) {
                                if (ignore_missing) {
                                        int dtype = DT_UNKNOWN;
-                                       if (path_excluded(&check, pathspec[i], -1, &dtype))
+                                       if (is_path_excluded(&check, pathspec[i], -1, &dtype))
                                                dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i]));
                                } else
                                        die(_("pathspec '%s' did not match any files"),
diff --combined builtin/ls-files.c
index 4a9ee690c7dcbaaf90f9511489b8f3b7b17b3c87,ef7f99a9ed12944c2de3541e45b160967a73c869..373c573449b5771f9b56d7d4ce04e4c0b3957cd9
@@@ -203,7 -203,7 +203,7 @@@ static void show_ru_info(void
  static int ce_excluded(struct path_exclude_check *check, struct cache_entry *ce)
  {
        int dtype = ce_to_dtype(ce);
-       return path_excluded(check, ce->name, ce_namelen(ce), &dtype);
+       return is_path_excluded(check, ce->name, ce_namelen(ce), &dtype);
  }
  
  static void show_files(struct dir_struct *dir)
@@@ -337,7 -337,7 +337,7 @@@ void overlay_tree_on_cache(const char *
                matchbuf[0] = prefix;
                matchbuf[1] = NULL;
                init_pathspec(&pathspec, matchbuf);
 -              pathspec.items[0].use_wildcard = 0;
 +              pathspec.items[0].nowildcard_len = pathspec.items[0].len;
        } else
                init_pathspec(&pathspec, NULL);
        if (read_tree(tree, 1, &pathspec))
@@@ -405,7 -405,7 +405,7 @@@ int report_path_error(const char *ps_ma
  }
  
  static const char * const ls_files_usage[] = {
 -      "git ls-files [options] [<file>...]",
 +      N_("git ls-files [options] [<file>...]"),
        NULL
  };
  
@@@ -457,57 -457,57 +457,57 @@@ int cmd_ls_files(int argc, const char *
        struct dir_struct dir;
        struct option builtin_ls_files_options[] = {
                { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
 -                      "paths are separated with NUL character",
 +                      N_("paths are separated with NUL character"),
                        PARSE_OPT_NOARG, option_parse_z },
                OPT_BOOLEAN('t', NULL, &show_tag,
 -                      "identify the file status with tags"),
 +                      N_("identify the file status with tags")),
                OPT_BOOLEAN('v', NULL, &show_valid_bit,
 -                      "use lowercase letters for 'assume unchanged' files"),
 +                      N_("use lowercase letters for 'assume unchanged' files")),
                OPT_BOOLEAN('c', "cached", &show_cached,
 -                      "show cached files in the output (default)"),
 +                      N_("show cached files in the output (default)")),
                OPT_BOOLEAN('d', "deleted", &show_deleted,
 -                      "show deleted files in the output"),
 +                      N_("show deleted files in the output")),
                OPT_BOOLEAN('m', "modified", &show_modified,
 -                      "show modified files in the output"),
 +                      N_("show modified files in the output")),
                OPT_BOOLEAN('o', "others", &show_others,
 -                      "show other files in the output"),
 +                      N_("show other files in the output")),
                OPT_BIT('i', "ignored", &dir.flags,
 -                      "show ignored files in the output",
 +                      N_("show ignored files in the output"),
                        DIR_SHOW_IGNORED),
                OPT_BOOLEAN('s', "stage", &show_stage,
 -                      "show staged contents' object name in the output"),
 +                      N_("show staged contents' object name in the output")),
                OPT_BOOLEAN('k', "killed", &show_killed,
 -                      "show files on the filesystem that need to be removed"),
 +                      N_("show files on the filesystem that need to be removed")),
                OPT_BIT(0, "directory", &dir.flags,
 -                      "show 'other' directories' name only",
 +                      N_("show 'other' directories' name only"),
                        DIR_SHOW_OTHER_DIRECTORIES),
                OPT_NEGBIT(0, "empty-directory", &dir.flags,
 -                      "don't show empty directories",
 +                      N_("don't show empty directories"),
                        DIR_HIDE_EMPTY_DIRECTORIES),
                OPT_BOOLEAN('u', "unmerged", &show_unmerged,
 -                      "show unmerged files in the output"),
 +                      N_("show unmerged files in the output")),
                OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo,
 -                          "show resolve-undo information"),
 -              { OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], "pattern",
 -                      "skip files matching pattern",
 +                          N_("show resolve-undo information")),
 +              { OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], N_("pattern"),
 +                      N_("skip files matching pattern"),
                        0, option_parse_exclude },
 -              { OPTION_CALLBACK, 'X', "exclude-from", &dir, "file",
 -                      "exclude patterns are read from <file>",
 +              { OPTION_CALLBACK, 'X', "exclude-from", &dir, N_("file"),
 +                      N_("exclude patterns are read from <file>"),
                        0, option_parse_exclude_from },
 -              OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, "file",
 -                      "read additional per-directory exclude patterns in <file>"),
 +              OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, N_("file"),
 +                      N_("read additional per-directory exclude patterns in <file>")),
                { OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL,
 -                      "add the standard git exclusions",
 +                      N_("add the standard git exclusions"),
                        PARSE_OPT_NOARG, option_parse_exclude_standard },
                { OPTION_SET_INT, 0, "full-name", &prefix_len, NULL,
 -                      "make the output relative to the project top directory",
 +                      N_("make the output relative to the project top directory"),
                        PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL },
                OPT_BOOLEAN(0, "error-unmatch", &error_unmatch,
 -                      "if any <file> is not in the index, treat this as an error"),
 -              OPT_STRING(0, "with-tree", &with_tree, "tree-ish",
 -                      "pretend that paths removed since <tree-ish> are still present"),
 +                      N_("if any <file> is not in the index, treat this as an error")),
 +              OPT_STRING(0, "with-tree", &with_tree, N_("tree-ish"),
 +                      N_("pretend that paths removed since <tree-ish> are still present")),
                OPT__ABBREV(&abbrev),
 -              OPT_BOOLEAN(0, "debug", &debug_mode, "show debugging data"),
 +              OPT_BOOLEAN(0, "debug", &debug_mode, N_("show debugging data")),
                OPT_END()
        };
  
diff --combined dir.c
index 37807550475cf488b60187872f698cbc6171dcb1,41f141c8d6df61b4d4f2592f1dbde4f26cb43c93..e883a91483981ad9e2da4841063c1c8bad6a25fd
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -2,13 -2,14 +2,15 @@@
   * This handles recursive filename detection with exclude
   * files, index knowledge etc..
   *
+  * See Documentation/technical/api-directory-listing.txt
+  *
   * Copyright (C) Linus Torvalds, 2005-2006
   *             Junio Hamano, 2005-2006
   */
  #include "cache.h"
  #include "dir.h"
  #include "refs.h"
 +#include "wildmatch.h"
  
  struct path_simplify {
        int len;
@@@ -35,33 -36,10 +37,33 @@@ int fnmatch_icase(const char *pattern, 
        return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0));
  }
  
 +inline int git_fnmatch(const char *pattern, const char *string,
 +                     int flags, int prefix)
 +{
 +      int fnm_flags = 0;
 +      if (flags & GFNM_PATHNAME)
 +              fnm_flags |= FNM_PATHNAME;
 +      if (prefix > 0) {
 +              if (strncmp(pattern, string, prefix))
 +                      return FNM_NOMATCH;
 +              pattern += prefix;
 +              string += prefix;
 +      }
 +      if (flags & GFNM_ONESTAR) {
 +              int pattern_len = strlen(++pattern);
 +              int string_len = strlen(string);
 +              return string_len < pattern_len ||
 +                     strcmp(pattern,
 +                            string + string_len - pattern_len);
 +      }
 +      return fnmatch(pattern, string, fnm_flags);
 +}
 +
  static size_t common_prefix_len(const char **pathspec)
  {
        const char *n, *first;
        size_t max = 0;
 +      int literal = limit_pathspec_to_literal();
  
        if (!pathspec)
                return max;
@@@ -71,7 -49,7 +73,7 @@@
                size_t i, len = 0;
                for (i = 0; first == n || i < max; i++) {
                        char c = n[i];
 -                      if (!c || c != first[i] || is_glob_special(c))
 +                      if (!c || c != first[i] || (!literal && is_glob_special(c)))
                                break;
                        if (c == '/')
                                len = i + 1;
@@@ -141,7 -119,6 +143,7 @@@ int within_depth(const char *name, int 
  static int match_one(const char *match, const char *name, int namelen)
  {
        int matchlen;
 +      int literal = limit_pathspec_to_literal();
  
        /* If the match was just the prefix, we matched */
        if (!*match)
                for (;;) {
                        unsigned char c1 = tolower(*match);
                        unsigned char c2 = tolower(*name);
 -                      if (c1 == '\0' || is_glob_special(c1))
 +                      if (c1 == '\0' || (!literal && is_glob_special(c1)))
                                break;
                        if (c1 != c2)
                                return 0;
                for (;;) {
                        unsigned char c1 = *match;
                        unsigned char c2 = *name;
 -                      if (c1 == '\0' || is_glob_special(c1))
 +                      if (c1 == '\0' || (!literal && is_glob_special(c1)))
                                break;
                        if (c1 != c2)
                                return 0;
                }
        }
  
 -
        /*
         * If we don't match the matchstring exactly,
         * we need to match by fnmatch
         */
        matchlen = strlen(match);
 -      if (strncmp_icase(match, name, matchlen))
 +      if (strncmp_icase(match, name, matchlen)) {
 +              if (literal)
 +                      return 0;
                return !fnmatch_icase(match, name, 0) ? MATCHED_FNMATCH : 0;
 +      }
  
        if (namelen == matchlen)
                return MATCHED_EXACTLY;
@@@ -257,10 -232,7 +259,10 @@@ static int match_pathspec_item(const st
                        return MATCHED_RECURSIVELY;
        }
  
 -      if (item->use_wildcard && !fnmatch(match, name, 0))
 +      if (item->nowildcard_len < item->len &&
 +          !git_fnmatch(match, name,
 +                       item->flags & PATHSPEC_ONESTAR ? GFNM_ONESTAR : 0,
 +                       item->nowildcard_len - prefix))
                return MATCHED_FNMATCH;
  
        return 0;
@@@ -377,7 -349,7 +379,7 @@@ void parse_exclude_pattern(const char *
  }
  
  void add_exclude(const char *string, const char *base,
-                int baselen, struct exclude_list *which)
+                int baselen, struct exclude_list *el)
  {
        struct exclude *x;
        int patternlen;
        x->base = base;
        x->baselen = baselen;
        x->flags = flags;
-       ALLOC_GROW(which->excludes, which->nr + 1, which->alloc);
-       which->excludes[which->nr++] = x;
+       ALLOC_GROW(el->excludes, el->nr + 1, el->alloc);
+       el->excludes[el->nr++] = x;
  }
  
  static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
        return data;
  }
  
- void free_excludes(struct exclude_list *el)
+ /*
+  * Frees memory within el which was allocated for exclude patterns and
+  * the file buffer.  Does not free el itself.
+  */
+ void clear_exclude_list(struct exclude_list *el)
  {
        int i;
  
@@@ -444,7 -420,7 +450,7 @@@ int add_excludes_from_file_to_list(cons
                                   const char *base,
                                   int baselen,
                                   char **buf_p,
-                                  struct exclude_list *which,
+                                  struct exclude_list *el,
                                   int check_index)
  {
        struct stat st;
  
        fd = open(fname, O_RDONLY);
        if (fd < 0 || fstat(fd, &st) < 0) {
 +              if (errno != ENOENT)
 +                      warn_on_inaccessible(fname);
                if (0 <= fd)
                        close(fd);
                if (!check_index ||
                if (buf[i] == '\n') {
                        if (entry != buf + i && entry[0] != '#') {
                                buf[i - (i && buf[i-1] == '\r')] = 0;
-                               add_exclude(entry, base, baselen, which);
+                               add_exclude(entry, base, baselen, el);
                        }
                        entry = buf + i + 1;
                }
@@@ -508,6 -482,10 +514,10 @@@ void add_excludes_from_file(struct dir_
                die("cannot use %s as an exclude file", fname);
  }
  
+ /*
+  * Loads the per-directory exclude list for the substring of base
+  * which has a char length of baselen.
+  */
  static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
  {
        struct exclude_list *el;
            (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX))
                return; /* too long a path -- ignore */
  
-       /* Pop the ones that are not the prefix of the path being checked. */
+       /* Pop the directories that are not the prefix of the path being checked. */
        el = &dir->exclude_list[EXC_DIRS];
        while ((stk = dir->exclude_stack) != NULL) {
                if (stk->baselen <= baselen &&
@@@ -625,26 -603,29 +635,30 @@@ int match_pathname(const char *pathname
                namelen -= prefix;
        }
  
 -      return fnmatch_icase(pattern, name, FNM_PATHNAME) == 0;
 +      return wildmatch(pattern, name,
 +                       ignore_case ? FNM_CASEFOLD : 0) == 0;
  }
  
- /* Scan the list and let the last match determine the fate.
-  * Return 1 for exclude, 0 for include and -1 for undecided.
+ /*
+  * Scan the given exclude list in reverse to see whether pathname
+  * should be ignored.  The first match (i.e. the last on the list), if
+  * any, determines the fate.  Returns the exclude_list element which
+  * matched, or NULL for undecided.
   */
- int excluded_from_list(const char *pathname,
-                      int pathlen, const char *basename, int *dtype,
-                      struct exclude_list *el)
+ static struct exclude *last_exclude_matching_from_list(const char *pathname,
+                                                      int pathlen,
+                                                      const char *basename,
+                                                      int *dtype,
+                                                      struct exclude_list *el)
  {
        int i;
  
        if (!el->nr)
-               return -1;      /* undefined */
+               return NULL;    /* undefined */
  
        for (i = el->nr - 1; 0 <= i; i--) {
                struct exclude *x = el->excludes[i];
                const char *exclude = x->pattern;
-               int to_exclude = x->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
                int prefix = x->nowildcardlen;
  
                if (x->flags & EXC_FLAG_MUSTBEDIR) {
                                           pathlen - (basename - pathname),
                                           exclude, prefix, x->patternlen,
                                           x->flags))
-                               return to_exclude;
+                               return x;
                        continue;
                }
  
                if (match_pathname(pathname, pathlen,
                                   x->base, x->baselen ? x->baselen - 1 : 0,
                                   exclude, prefix, x->patternlen, x->flags))
-                       return to_exclude;
+                       return x;
        }
+       return NULL; /* undecided */
+ }
+ /*
+  * Scan the list and let the last match determine the fate.
+  * Return 1 for exclude, 0 for include and -1 for undecided.
+  */
+ int is_excluded_from_list(const char *pathname,
+                         int pathlen, const char *basename, int *dtype,
+                         struct exclude_list *el)
+ {
+       struct exclude *exclude;
+       exclude = last_exclude_matching_from_list(pathname, pathlen, basename, dtype, el);
+       if (exclude)
+               return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
        return -1; /* undecided */
  }
  
- static int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
+ /*
+  * Loads the exclude lists for the directory containing pathname, then
+  * scans all exclude lists to determine whether pathname is excluded.
+  * Returns the exclude_list element which matched, or NULL for
+  * undecided.
+  */
+ static struct exclude *last_exclude_matching(struct dir_struct *dir,
+                                            const char *pathname,
+                                            int *dtype_p)
  {
        int pathlen = strlen(pathname);
        int st;
+       struct exclude *exclude;
        const char *basename = strrchr(pathname, '/');
        basename = (basename) ? basename+1 : pathname;
  
        prep_exclude(dir, pathname, basename-pathname);
        for (st = EXC_CMDL; st <= EXC_FILE; st++) {
-               switch (excluded_from_list(pathname, pathlen, basename,
-                                          dtype_p, &dir->exclude_list[st])) {
-               case 0:
-                       return 0;
-               case 1:
-                       return 1;
-               }
+               exclude = last_exclude_matching_from_list(
+                       pathname, pathlen, basename, dtype_p,
+                       &dir->exclude_list[st]);
+               if (exclude)
+                       return exclude;
        }
+       return NULL;
+ }
+ /*
+  * Loads the exclude lists for the directory containing pathname, then
+  * scans all exclude lists to determine whether pathname is excluded.
+  * Returns 1 if true, otherwise 0.
+  */
+ static int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
+ {
+       struct exclude *exclude =
+               last_exclude_matching(dir, pathname, dtype_p);
+       if (exclude)
+               return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
        return 0;
  }
  
@@@ -696,6 -713,7 +746,7 @@@ void path_exclude_check_init(struct pat
                             struct dir_struct *dir)
  {
        check->dir = dir;
+       check->exclude = NULL;
        strbuf_init(&check->path, 256);
  }
  
@@@ -705,32 -723,41 +756,41 @@@ void path_exclude_check_clear(struct pa
  }
  
  /*
-  * Is this name excluded?  This is for a caller like show_files() that
-  * do not honor directory hierarchy and iterate through paths that are
-  * possibly in an ignored directory.
+  * For each subdirectory in name, starting with the top-most, checks
+  * to see if that subdirectory is excluded, and if so, returns the
+  * corresponding exclude structure.  Otherwise, checks whether name
+  * itself (which is presumably a file) is excluded.
   *
   * A path to a directory known to be excluded is left in check->path to
   * optimize for repeated checks for files in the same excluded directory.
   */
- int path_excluded(struct path_exclude_check *check,
-                 const char *name, int namelen, int *dtype)
+ struct exclude *last_exclude_matching_path(struct path_exclude_check *check,
+                                          const char *name, int namelen,
+                                          int *dtype)
  {
        int i;
        struct strbuf *path = &check->path;
+       struct exclude *exclude;
  
        /*
         * we allow the caller to pass namelen as an optimization; it
         * must match the length of the name, as we eventually call
-        * excluded() on the whole name string.
+        * is_excluded() on the whole name string.
         */
        if (namelen < 0)
                namelen = strlen(name);
  
+       /*
+        * If path is non-empty, and name is equal to path or a
+        * subdirectory of path, name should be excluded, because
+        * it's inside a directory which is already known to be
+        * excluded and was previously left in check->path.
+        */
        if (path->len &&
            path->len <= namelen &&
            !memcmp(name, path->buf, path->len) &&
            (!name[path->len] || name[path->len] == '/'))
-               return 1;
+               return check->exclude;
  
        strbuf_setlen(path, 0);
        for (i = 0; name[i]; i++) {
  
                if (ch == '/') {
                        int dt = DT_DIR;
-                       if (excluded(check->dir, path->buf, &dt))
-                               return 1;
+                       exclude = last_exclude_matching(check->dir,
+                                                       path->buf, &dt);
+                       if (exclude) {
+                               check->exclude = exclude;
+                               return exclude;
+                       }
                }
                strbuf_addch(path, ch);
        }
        /* An entry in the index; cannot be a directory with subentries */
        strbuf_setlen(path, 0);
  
-       return excluded(check->dir, name, dtype);
+       return last_exclude_matching(check->dir, name, dtype);
+ }
+ /*
+  * Is this name excluded?  This is for a caller like show_files() that
+  * do not honor directory hierarchy and iterate through paths that are
+  * possibly in an ignored directory.
+  */
+ int is_path_excluded(struct path_exclude_check *check,
+                 const char *name, int namelen, int *dtype)
+ {
+       struct exclude *exclude =
+               last_exclude_matching_path(check, name, namelen, dtype);
+       if (exclude)
+               return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
+       return 0;
  }
  
  static struct dir_entry *dir_entry_new(const char *pathname, int len)
@@@ -1047,7 -1093,7 +1126,7 @@@ static enum path_treatment treat_one_pa
                                          const struct path_simplify *simplify,
                                          int dtype, struct dirent *de)
  {
-       int exclude = excluded(dir, path->buf, &dtype);
+       int exclude = is_excluded(dir, path->buf, &dtype);
        if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
            && exclude_matches_pathspec(path->buf, path->len, simplify))
                dir_add_ignored(dir, path->buf, path->len);
@@@ -1396,17 -1442,12 +1475,17 @@@ int remove_dir_recursively(struct strbu
  void setup_standard_excludes(struct dir_struct *dir)
  {
        const char *path;
 +      char *xdg_path;
  
        dir->exclude_per_dir = ".gitignore";
        path = git_path("info/exclude");
 -      if (!access(path, R_OK))
 +      if (!excludes_file) {
 +              home_config_paths(NULL, &xdg_path, "ignore");
 +              excludes_file = xdg_path;
 +      }
 +      if (!access_or_warn(path, R_OK))
                add_excludes_from_file(dir, path);
 -      if (excludes_file && !access(excludes_file, R_OK))
 +      if (excludes_file && !access_or_warn(excludes_file, R_OK))
                add_excludes_from_file(dir, excludes_file);
  }
  
@@@ -1460,18 -1501,9 +1539,18 @@@ int init_pathspec(struct pathspec *path
  
                item->match = path;
                item->len = strlen(path);
 -              item->use_wildcard = !no_wildcard(path);
 -              if (item->use_wildcard)
 -                      pathspec->has_wildcard = 1;
 +              item->flags = 0;
 +              if (limit_pathspec_to_literal()) {
 +                      item->nowildcard_len = item->len;
 +              } else {
 +                      item->nowildcard_len = simple_length(path);
 +                      if (item->nowildcard_len < item->len) {
 +                              pathspec->has_wildcard = 1;
 +                              if (path[item->nowildcard_len] == '*' &&
 +                                  no_wildcard(path + item->nowildcard_len + 1))
 +                                      item->flags |= PATHSPEC_ONESTAR;
 +                      }
 +              }
        }
  
        qsort(pathspec->items, pathspec->nr,
@@@ -1485,11 -1517,3 +1564,11 @@@ void free_pathspec(struct pathspec *pat
        free(pathspec->items);
        pathspec->items = NULL;
  }
 +
 +int limit_pathspec_to_literal(void)
 +{
 +      static int flag = -1;
 +      if (flag < 0)
 +              flag = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0);
 +      return flag;
 +}
diff --combined dir.h
index ab5af42b2eedcf7045abd0b6029e84ba804f6057,5664ba8c6401e40f002385bdaf9aed9b4856a443..ae1bc467ae909e6db151b055e212ded45fac34f6
--- 1/dir.h
--- 2/dir.h
+++ b/dir.h
@@@ -1,6 -1,8 +1,8 @@@
  #ifndef DIR_H
  #define DIR_H
  
+ /* See Documentation/technical/api-directory-listing.txt */
  #include "strbuf.h"
  
  struct dir_entry {
  #define EXC_FLAG_MUSTBEDIR 8
  #define EXC_FLAG_NEGATIVE 16
  
+ /*
+  * Each .gitignore file will be parsed into patterns which are then
+  * appended to the relevant exclude_list (either EXC_DIRS or
+  * EXC_FILE).  exclude_lists are also used to represent the list of
+  * --exclude values passed via CLI args (EXC_CMDL).
+  */
  struct exclude_list {
        int nr;
        int alloc;
        } **excludes;
  };
  
+ /*
+  * The contents of the per-directory exclude files are lazily read on
+  * demand and then cached in memory, one per exclude_stack struct, in
+  * order to avoid opening and parsing each one every time that
+  * directory is traversed.
+  */
  struct exclude_stack {
-       struct exclude_stack *prev;
-       char *filebuf;
+       struct exclude_stack *prev; /* the struct exclude_stack for the parent directory */
+       char *filebuf; /* remember pointer to per-directory exclude file contents so we can free() */
        int baselen;
        int exclude_ix;
  };
@@@ -59,6 -73,14 +73,14 @@@ struct dir_struct 
  #define EXC_DIRS 1
  #define EXC_FILE 2
  
+       /*
+        * Temporary variables which are used during loading of the
+        * per-directory exclude lists.
+        *
+        * exclude_stack points to the top of the exclude_stack, and
+        * basebuf contains the full path to the current
+        * (sub)directory in the traversal.
+        */
        struct exclude_stack *exclude_stack;
        char basebuf[PATH_MAX];
  };
@@@ -76,8 -98,8 +98,8 @@@ extern int within_depth(const char *nam
  extern int fill_directory(struct dir_struct *dir, const char **pathspec);
  extern int read_directory(struct dir_struct *, const char *path, int len, const char **pathspec);
  
- extern int excluded_from_list(const char *pathname, int pathlen, const char *basename,
-                             int *dtype, struct exclude_list *el);
+ extern int is_excluded_from_list(const char *pathname, int pathlen, const char *basename,
+                                int *dtype, struct exclude_list *el);
  struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len);
  
  /*
@@@ -91,26 -113,29 +113,29 @@@ extern int match_pathname(const char *
                          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
+  * The is_excluded() API is meant for callers that check each level of leading
+  * directory hierarchies with is_excluded() to avoid recursing into excluded
   * directories.  Callers that do not do so should use this API instead.
   */
  struct path_exclude_check {
        struct dir_struct *dir;
+       struct exclude *exclude;
        struct strbuf path;
  };
  extern void path_exclude_check_init(struct path_exclude_check *, struct dir_struct *);
  extern void path_exclude_check_clear(struct path_exclude_check *);
- extern int path_excluded(struct path_exclude_check *, const char *, int namelen, int *dtype);
+ extern struct exclude *last_exclude_matching_path(struct path_exclude_check *, const char *,
+                                                 int namelen, int *dtype);
+ extern int is_path_excluded(struct path_exclude_check *, const char *, int namelen, int *dtype);
  
  
  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);
+                                         char **buf_p, struct exclude_list *el, 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);
+                       int baselen, struct exclude_list *el);
+ extern void clear_exclude_list(struct exclude_list *el);
  extern int file_exists(const char *);
  
  extern int is_inside_dir(const char *dir);
@@@ -139,13 -164,4 +164,13 @@@ extern int strcmp_icase(const char *a, 
  extern int strncmp_icase(const char *a, const char *b, size_t count);
  extern int fnmatch_icase(const char *pattern, const char *string, int flags);
  
 +/*
 + * The prefix part of pattern must not contains wildcards.
 + */
 +#define GFNM_PATHNAME 1               /* similar to FNM_PATHNAME */
 +#define GFNM_ONESTAR  2               /* there is only _one_ wildcard, a star */
 +
 +extern int git_fnmatch(const char *pattern, const char *string,
 +                     int flags, int prefix);
 +
  #endif
diff --combined unpack-trees.c
index 61acc5e5646f0082c8e24a9587280c5fd2f5704e,ad621d95e9f14bac3aca2c2d247bc92a98054684..0e1a196ace33110e2bb74cde90d6f62358413ead
@@@ -539,8 -539,7 +539,8 @@@ static struct cache_entry *create_ce_en
        struct cache_entry *ce = xcalloc(1, cache_entry_size(len));
  
        ce->ce_mode = create_ce_mode(n->mode);
 -      ce->ce_flags = create_ce_flags(len, stage);
 +      ce->ce_flags = create_ce_flags(stage);
 +      ce->ce_namelen = len;
        hashcpy(ce->sha1, n->sha1);
        make_traverse_path(ce->name, info, n);
  
@@@ -837,7 -836,8 +837,8 @@@ static int clear_ce_flags_dir(struct ca
  {
        struct cache_entry **cache_end;
        int dtype = DT_DIR;
-       int ret = excluded_from_list(prefix, prefix_len, basename, &dtype, el);
+       int ret = is_excluded_from_list(prefix, prefix_len,
+                                       basename, &dtype, el);
  
        prefix[prefix_len++] = '/';
  
         * with ret (iow, we know in advance the incl/excl
         * decision for the entire directory), clear flag here without
         * calling clear_ce_flags_1(). That function will call
-        * the expensive excluded_from_list() on every entry.
+        * the expensive is_excluded_from_list() on every entry.
         */
        return clear_ce_flags_1(cache, cache_end - cache,
                                prefix, prefix_len,
@@@ -939,7 -939,8 +940,8 @@@ static int clear_ce_flags_1(struct cach
  
                /* Non-directory */
                dtype = ce_to_dtype(ce);
-               ret = excluded_from_list(ce->name, ce_namelen(ce), name, &dtype, el);
+               ret = is_excluded_from_list(ce->name, ce_namelen(ce),
+                                           name, &dtype, el);
                if (ret < 0)
                        ret = defval;
                if (ret > 0)
@@@ -1152,7 -1153,7 +1154,7 @@@ int unpack_trees(unsigned len, struct t
                *o->dst_index = o->result;
  
  done:
-       free_excludes(&el);
+       clear_exclude_list(&el);
        if (o->path_exclude_check) {
                path_exclude_check_clear(o->path_exclude_check);
                free(o->path_exclude_check);
@@@ -1297,7 -1298,7 +1299,7 @@@ static int verify_clean_subdirectory(st
         * First let's make sure we do not have a local modification
         * in that directory.
         */
 -      namelen = strlen(ce->name);
 +      namelen = ce_namelen(ce);
        for (i = locate_in_src_index(ce, o);
             i < o->src_index->cache_nr;
             i++) {
@@@ -1373,7 -1374,7 +1375,7 @@@ static int check_ok_to_remove(const cha
                return 0;
  
        if (o->dir &&
-           path_excluded(o->path_exclude_check, name, -1, &dtype))
+           is_path_excluded(o->path_exclude_check, name, -1, &dtype))
                /*
                 * ce->name is explicitly excluded, so it is Ok to
                 * overwrite it.
@@@ -1834,7 -1835,7 +1836,7 @@@ int oneway_merge(struct cache_entry **s
  
        if (old && same(old, a)) {
                int update = 0;
 -              if (o->reset && !ce_uptodate(old) && !ce_skip_worktree(old)) {
 +              if (o->reset && o->update && !ce_uptodate(old) && !ce_skip_worktree(old)) {
                        struct stat st;
                        if (lstat(old->name, &st) ||
                            ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE))