Merge branch 'ss/exc-flag-is-a-collection-of-bits' into maint
authorJunio C Hamano <gitster@pobox.com>
Fri, 15 Apr 2016 01:37:15 +0000 (18:37 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 15 Apr 2016 01:37:15 +0000 (18:37 -0700)
Code clean-up.

* ss/exc-flag-is-a-collection-of-bits:
dir: store EXC_FLAG_* values in unsigned integers

1  2 
attr.c
dir.c
dir.h
diff --combined attr.c
index 6537a433da201e866208ab23bf32bcafee037683,e273f7a3e3fa95627d9aef296a87218ed2709ab5..eec5d7d15a48fb9ca2fdd43d94ae515191d573f1
--- 1/attr.c
--- 2/attr.c
+++ b/attr.c
@@@ -93,7 -93,9 +93,7 @@@ static struct git_attr *git_attr_intern
        if (invalid_attr_name(name, len))
                return NULL;
  
 -      a = xmalloc(sizeof(*a) + len + 1);
 -      memcpy(a->name, name, len);
 -      a->name[len] = 0;
 +      FLEX_ALLOC_MEM(a, name, name, len);
        a->h = hval;
        a->next = git_attr_hash[pos];
        a->attr_nr = attr_nr++;
@@@ -122,7 -124,7 +122,7 @@@ struct pattern 
        const char *pattern;
        int patternlen;
        int nowildcardlen;
-       int flags;              /* EXC_FLAG_* */
+       unsigned flags;         /* EXC_FLAG_* */
  };
  
  /*
@@@ -488,8 -490,6 +488,8 @@@ static int git_attr_system(void
        return !git_env_bool("GIT_ATTR_NOSYSTEM", 0);
  }
  
 +static GIT_PATH_FUNC(git_path_info_attributes, INFOATTRIBUTES_FILE)
 +
  static void bootstrap_attr_stack(void)
  {
        struct attr_stack *elem;
                debug_push(elem);
        }
  
 -      elem = read_attr_from_file(git_path(INFOATTRIBUTES_FILE), 1);
 +      elem = read_attr_from_file(git_path_info_attributes(), 1);
        if (!elem)
                elem = xcalloc(1, sizeof(*elem));
        elem->origin = NULL;
@@@ -797,7 -797,7 +797,7 @@@ int git_all_attrs(const char *path, in
                        ++count;
        }
        *num = count;
 -      *check = xmalloc(sizeof(**check) * count);
 +      ALLOC_ARRAY(*check, count);
        j = 0;
        for (i = 0; i < attr_nr; i++) {
                const char *value = check_all_attr[i].value;
diff --combined dir.c
index a4a9d9fae154849fe38b153af9b16e00ef401f60,372581b0a352d412838100b05d2c4615e6ec212d..996653b0d3b6d81b15a3185d8d63314de647c53d
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -13,8 -13,6 +13,8 @@@
  #include "wildmatch.h"
  #include "pathspec.h"
  #include "utf8.h"
 +#include "varint.h"
 +#include "ewah/ewok.h"
  
  struct path_simplify {
        int len;
@@@ -34,22 -32,8 +34,22 @@@ enum path_treatment 
        path_untracked
  };
  
 +/*
 + * Support data structure for our opendir/readdir/closedir wrappers
 + */
 +struct cached_dir {
 +      DIR *fdir;
 +      struct untracked_cache_dir *untracked;
 +      int nr_files;
 +      int nr_dirs;
 +
 +      struct dirent *de;
 +      const char *file;
 +      struct untracked_cache_dir *ucd;
 +};
 +
  static enum path_treatment read_directory_recursive(struct dir_struct *dir,
 -      const char *path, int len,
 +      const char *path, int len, struct untracked_cache_dir *untracked,
        int check_only, const struct path_simplify *simplify);
  static int get_dtype(struct dirent *de, const char *path, int len);
  
@@@ -457,7 -441,7 +457,7 @@@ int no_wildcard(const char *string
  
  void parse_exclude_pattern(const char **pattern,
                           int *patternlen,
-                          int *flags,
+                          unsigned *flags,
                           int *nowildcardlen)
  {
        const char *p = *pattern;
@@@ -498,12 -482,17 +498,12 @@@ void add_exclude(const char *string, co
  {
        struct exclude *x;
        int patternlen;
-       int flags;
+       unsigned flags;
        int nowildcardlen;
  
        parse_exclude_pattern(&string, &patternlen, &flags, &nowildcardlen);
        if (flags & EXC_FLAG_MUSTBEDIR) {
 -              char *s;
 -              x = xmalloc(sizeof(*x) + patternlen + 1);
 -              s = (char *)(x+1);
 -              memcpy(s, string, patternlen);
 -              s[patternlen] = '\0';
 -              x->pattern = s;
 +              FLEXPTR_ALLOC_MEM(x, pattern, string, patternlen);
        } else {
                x = xmalloc(sizeof(*x));
                x->pattern = string;
        x->el = el;
  }
  
 -static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
 +static void *read_skip_worktree_file_from_index(const char *path, size_t *size,
 +                                              struct sha1_stat *sha1_stat)
  {
        int pos, len;
        unsigned long sz;
                return NULL;
        }
        *size = xsize_t(sz);
 +      if (sha1_stat) {
 +              memset(&sha1_stat->stat, 0, sizeof(sha1_stat->stat));
 +              hashcpy(sha1_stat->sha1, active_cache[pos]->sha1);
 +      }
        return data;
  }
  
@@@ -559,7 -543,9 +559,7 @@@ void clear_exclude_list(struct exclude_
        free(el->excludes);
        free(el->filebuf);
  
 -      el->nr = 0;
 -      el->excludes = NULL;
 -      el->filebuf = NULL;
 +      memset(el, 0, sizeof(*el));
  }
  
  static void trim_trailing_spaces(char *buf)
                *last_space = '\0';
  }
  
 -int add_excludes_from_file_to_list(const char *fname,
 -                                 const char *base,
 -                                 int baselen,
 -                                 struct exclude_list *el,
 -                                 int check_index)
 +/*
 + * Given a subdirectory name and "dir" of the current directory,
 + * search the subdir in "dir" and return it, or create a new one if it
 + * does not exist in "dir".
 + *
 + * If "name" has the trailing slash, it'll be excluded in the search.
 + */
 +static struct untracked_cache_dir *lookup_untracked(struct untracked_cache *uc,
 +                                                  struct untracked_cache_dir *dir,
 +                                                  const char *name, int len)
 +{
 +      int first, last;
 +      struct untracked_cache_dir *d;
 +      if (!dir)
 +              return NULL;
 +      if (len && name[len - 1] == '/')
 +              len--;
 +      first = 0;
 +      last = dir->dirs_nr;
 +      while (last > first) {
 +              int cmp, next = (last + first) >> 1;
 +              d = dir->dirs[next];
 +              cmp = strncmp(name, d->name, len);
 +              if (!cmp && strlen(d->name) > len)
 +                      cmp = -1;
 +              if (!cmp)
 +                      return d;
 +              if (cmp < 0) {
 +                      last = next;
 +                      continue;
 +              }
 +              first = next+1;
 +      }
 +
 +      uc->dir_created++;
 +      FLEX_ALLOC_MEM(d, name, name, len);
 +
 +      ALLOC_GROW(dir->dirs, dir->dirs_nr + 1, dir->dirs_alloc);
 +      memmove(dir->dirs + first + 1, dir->dirs + first,
 +              (dir->dirs_nr - first) * sizeof(*dir->dirs));
 +      dir->dirs_nr++;
 +      dir->dirs[first] = d;
 +      return d;
 +}
 +
 +static void do_invalidate_gitignore(struct untracked_cache_dir *dir)
 +{
 +      int i;
 +      dir->valid = 0;
 +      dir->untracked_nr = 0;
 +      for (i = 0; i < dir->dirs_nr; i++)
 +              do_invalidate_gitignore(dir->dirs[i]);
 +}
 +
 +static void invalidate_gitignore(struct untracked_cache *uc,
 +                               struct untracked_cache_dir *dir)
 +{
 +      uc->gitignore_invalidated++;
 +      do_invalidate_gitignore(dir);
 +}
 +
 +static void invalidate_directory(struct untracked_cache *uc,
 +                               struct untracked_cache_dir *dir)
 +{
 +      int i;
 +      uc->dir_invalidated++;
 +      dir->valid = 0;
 +      dir->untracked_nr = 0;
 +      for (i = 0; i < dir->dirs_nr; i++)
 +              dir->dirs[i]->recurse = 0;
 +}
 +
 +/*
 + * Given a file with name "fname", read it (either from disk, or from
 + * the index if "check_index" is non-zero), parse it and store the
 + * exclude rules in "el".
 + *
 + * If "ss" is not NULL, compute SHA-1 of the exclude file and fill
 + * stat data from disk (only valid if add_excludes returns zero). If
 + * ss_valid is non-zero, "ss" must contain good value as input.
 + */
 +static int add_excludes(const char *fname, const char *base, int baselen,
 +                      struct exclude_list *el, int check_index,
 +                      struct sha1_stat *sha1_stat)
  {
        struct stat st;
        int fd, i, lineno = 1;
                if (0 <= fd)
                        close(fd);
                if (!check_index ||
 -                  (buf = read_skip_worktree_file_from_index(fname, &size)) == NULL)
 +                  (buf = read_skip_worktree_file_from_index(fname, &size, sha1_stat)) == NULL)
                        return -1;
                if (size == 0) {
                        free(buf);
                        return 0;
                }
                if (buf[size-1] != '\n') {
 -                      buf = xrealloc(buf, size+1);
 +                      buf = xrealloc(buf, st_add(size, 1));
                        buf[size++] = '\n';
                }
        } else {
                size = xsize_t(st.st_size);
                if (size == 0) {
 +                      if (sha1_stat) {
 +                              fill_stat_data(&sha1_stat->stat, &st);
 +                              hashcpy(sha1_stat->sha1, EMPTY_BLOB_SHA1_BIN);
 +                              sha1_stat->valid = 1;
 +                      }
                        close(fd);
                        return 0;
                }
 -              buf = xmalloc(size+1);
 +              buf = xmallocz(size);
                if (read_in_full(fd, buf, size) != size) {
                        free(buf);
                        close(fd);
                }
                buf[size++] = '\n';
                close(fd);
 +              if (sha1_stat) {
 +                      int pos;
 +                      if (sha1_stat->valid &&
 +                          !match_stat_data_racy(&the_index, &sha1_stat->stat, &st))
 +                              ; /* no content change, ss->sha1 still good */
 +                      else if (check_index &&
 +                               (pos = cache_name_pos(fname, strlen(fname))) >= 0 &&
 +                               !ce_stage(active_cache[pos]) &&
 +                               ce_uptodate(active_cache[pos]) &&
 +                               !would_convert_to_git(fname))
 +                              hashcpy(sha1_stat->sha1, active_cache[pos]->sha1);
 +                      else
 +                              hash_sha1_file(buf, size, "blob", sha1_stat->sha1);
 +                      fill_stat_data(&sha1_stat->stat, &st);
 +                      sha1_stat->valid = 1;
 +              }
        }
  
        el->filebuf = buf;
        return 0;
  }
  
 +int add_excludes_from_file_to_list(const char *fname, const char *base,
 +                                 int baselen, struct exclude_list *el,
 +                                 int check_index)
 +{
 +      return add_excludes(fname, base, baselen, el, check_index, NULL);
 +}
 +
  struct exclude_list *add_exclude_list(struct dir_struct *dir,
                                      int group_type, const char *src)
  {
  /*
   * Used to set up core.excludesfile and .git/info/exclude lists.
   */
 -void add_excludes_from_file(struct dir_struct *dir, const char *fname)
 +static void add_excludes_from_file_1(struct dir_struct *dir, const char *fname,
 +                                   struct sha1_stat *sha1_stat)
  {
        struct exclude_list *el;
 +      /*
 +       * catch setup_standard_excludes() that's called before
 +       * dir->untracked is assigned. That function behaves
 +       * differently when dir->untracked is non-NULL.
 +       */
 +      if (!dir->untracked)
 +              dir->unmanaged_exclude_files++;
        el = add_exclude_list(dir, EXC_FILE, fname);
 -      if (add_excludes_from_file_to_list(fname, "", 0, el, 0) < 0)
 +      if (add_excludes(fname, "", 0, el, 0, sha1_stat) < 0)
                die("cannot use %s as an exclude file", fname);
  }
  
 +void add_excludes_from_file(struct dir_struct *dir, const char *fname)
 +{
 +      dir->unmanaged_exclude_files++; /* see validate_untracked_cache() */
 +      add_excludes_from_file_1(dir, fname, NULL);
 +}
 +
  int match_basename(const char *basename, int basenamelen,
                   const char *pattern, int prefix, int patternlen,
-                  int flags)
+                  unsigned flags)
  {
        if (prefix == patternlen) {
                if (patternlen == basenamelen &&
  int match_pathname(const char *pathname, int pathlen,
                   const char *base, int baselen,
                   const char *pattern, int prefix, int patternlen,
-                  int flags)
+                  unsigned flags)
  {
        const char *name;
        int namelen;
@@@ -891,7 -756,6 +891,7 @@@ static struct exclude *last_exclude_mat
                                                       int *dtype,
                                                       struct exclude_list *el)
  {
 +      struct exclude *exc = NULL; /* undecided */
        int i;
  
        if (!el->nr)
                        if (match_basename(basename,
                                           pathlen - (basename - pathname),
                                           exclude, prefix, x->patternlen,
 -                                         x->flags))
 -                              return x;
 +                                         x->flags)) {
 +                              exc = x;
 +                              break;
 +                      }
                        continue;
                }
  
                assert(x->baselen == 0 || x->base[x->baselen - 1] == '/');
                if (match_pathname(pathname, pathlen,
                                   x->base, x->baselen ? x->baselen - 1 : 0,
 -                                 exclude, prefix, x->patternlen, x->flags))
 -                      return x;
 +                                 exclude, prefix, x->patternlen, x->flags)) {
 +                      exc = x;
 +                      break;
 +              }
        }
 -      return NULL; /* undecided */
 +      return exc;
  }
  
  /*
@@@ -975,7 -835,6 +975,7 @@@ static void prep_exclude(struct dir_str
        struct exclude_list_group *group;
        struct exclude_list *el;
        struct exclude_stack *stk = NULL;
 +      struct untracked_cache_dir *untracked;
        int current;
  
        group = &dir->exclude_list_group[EXC_DIRS];
        /* Read from the parent directories and push them down. */
        current = stk ? stk->baselen : -1;
        strbuf_setlen(&dir->basebuf, current < 0 ? 0 : current);
 +      if (dir->untracked)
 +              untracked = stk ? stk->ucd : dir->untracked->root;
 +      else
 +              untracked = NULL;
 +
        while (current < baselen) {
                const char *cp;
 +              struct sha1_stat sha1_stat;
  
                stk = xcalloc(1, sizeof(*stk));
                if (current < 0) {
                        if (!cp)
                                die("oops in prep_exclude");
                        cp++;
 +                      untracked =
 +                              lookup_untracked(dir->untracked, untracked,
 +                                               base + current,
 +                                               cp - base - current);
                }
                stk->prev = dir->exclude_stack;
                stk->baselen = cp - base;
                stk->exclude_ix = group->nr;
 +              stk->ucd = untracked;
                el = add_exclude_list(dir, EXC_DIRS, NULL);
                strbuf_add(&dir->basebuf, base + current, stk->baselen - current);
                assert(stk->baselen == dir->basebuf.len);
                }
  
                /* Try to read per-directory file */
 -              if (dir->exclude_per_dir) {
 +              hashclr(sha1_stat.sha1);
 +              sha1_stat.valid = 0;
 +              if (dir->exclude_per_dir &&
 +                  /*
 +                   * If we know that no files have been added in
 +                   * this directory (i.e. valid_cached_dir() has
 +                   * been executed and set untracked->valid) ..
 +                   */
 +                  (!untracked || !untracked->valid ||
 +                   /*
 +                    * .. and .gitignore does not exist before
 +                    * (i.e. null exclude_sha1). Then we can skip
 +                    * loading .gitignore, which would result in
 +                    * ENOENT anyway.
 +                    */
 +                   !is_null_sha1(untracked->exclude_sha1))) {
                        /*
                         * dir->basebuf gets reused by the traversal, but we
                         * need fname to remain unchanged to ensure the src
                        strbuf_addbuf(&sb, &dir->basebuf);
                        strbuf_addstr(&sb, dir->exclude_per_dir);
                        el->src = strbuf_detach(&sb, NULL);
 -                      add_excludes_from_file_to_list(el->src, el->src,
 -                                                     stk->baselen, el, 1);
 +                      add_excludes(el->src, el->src, stk->baselen, el, 1,
 +                                   untracked ? &sha1_stat : NULL);
 +              }
 +              /*
 +               * NEEDSWORK: when untracked cache is enabled, prep_exclude()
 +               * will first be called in valid_cached_dir() then maybe many
 +               * times more in last_exclude_matching(). When the cache is
 +               * used, last_exclude_matching() will not be called and
 +               * reading .gitignore content will be a waste.
 +               *
 +               * So when it's called by valid_cached_dir() and we can get
 +               * .gitignore SHA-1 from the index (i.e. .gitignore is not
 +               * modified on work tree), we could delay reading the
 +               * .gitignore content until we absolutely need it in
 +               * last_exclude_matching(). Be careful about ignore rule
 +               * order, though, if you do that.
 +               */
 +              if (untracked &&
 +                  hashcmp(sha1_stat.sha1, untracked->exclude_sha1)) {
 +                      invalidate_gitignore(dir->untracked, untracked);
 +                      hashcpy(untracked->exclude_sha1, sha1_stat.sha1);
                }
                dir->exclude_stack = stk;
                current = stk->baselen;
@@@ -1159,8 -973,10 +1159,8 @@@ static struct dir_entry *dir_entry_new(
  {
        struct dir_entry *ent;
  
 -      ent = xmalloc(sizeof(*ent) + len + 1);
 +      FLEX_ALLOC_MEM(ent, name, pathname, len);
        ent->len = len;
 -      memcpy(ent->name, pathname, len);
 -      ent->name[len] = 0;
        return ent;
  }
  
@@@ -1195,15 -1011,29 +1195,15 @@@ enum exist_status 
   */
  static enum exist_status directory_exists_in_index_icase(const char *dirname, int len)
  {
 -      const struct cache_entry *ce = cache_dir_exists(dirname, len);
 -      unsigned char endchar;
 -
 -      if (!ce)
 -              return index_nonexistent;
 -      endchar = ce->name[len];
 +      struct cache_entry *ce;
  
 -      /*
 -       * The cache_entry structure returned will contain this dirname
 -       * and possibly additional path components.
 -       */
 -      if (endchar == '/')
 +      if (cache_dir_exists(dirname, len))
                return index_directory;
  
 -      /*
 -       * If there are no additional path components, then this cache_entry
 -       * represents a submodule.  Submodules, despite being directories,
 -       * are stored in the cache without a closing slash.
 -       */
 -      if (!endchar && S_ISGITLINK(ce->ce_mode))
 +      ce = cache_file_exists(dirname, len, ignore_case);
 +      if (ce && S_ISGITLINK(ce->ce_mode))
                return index_gitdir;
  
 -      /* This should never be hit, but it exists just in case. */
        return index_nonexistent;
  }
  
@@@ -1275,8 -1105,7 +1275,8 @@@ static enum exist_status directory_exis
   *  (c) otherwise, we recurse into it.
   */
  static enum path_treatment treat_directory(struct dir_struct *dir,
 -      const char *dirname, int len, int exclude,
 +      struct untracked_cache_dir *untracked,
 +      const char *dirname, int len, int baselen, int exclude,
        const struct path_simplify *simplify)
  {
        /* The "len-1" is to strip the final '/' */
        if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
                return exclude ? path_excluded : path_untracked;
  
 -      return read_directory_recursive(dir, dirname, len, 1, simplify);
 +      untracked = lookup_untracked(dir->untracked, untracked,
 +                                   dirname + baselen, len - baselen);
 +      return read_directory_recursive(dir, dirname, len,
 +                                      untracked, 1, simplify);
  }
  
  /*
@@@ -1422,9 -1248,7 +1422,9 @@@ static int get_dtype(struct dirent *de
  }
  
  static enum path_treatment treat_one_path(struct dir_struct *dir,
 +                                        struct untracked_cache_dir *untracked,
                                          struct strbuf *path,
 +                                        int baselen,
                                          const struct path_simplify *simplify,
                                          int dtype, struct dirent *de)
  {
                return path_none;
        case DT_DIR:
                strbuf_addch(path, '/');
 -              return treat_directory(dir, path->buf, path->len, exclude,
 -                      simplify);
 +              return treat_directory(dir, untracked, path->buf, path->len,
 +                                     baselen, exclude, simplify);
        case DT_REG:
        case DT_LNK:
                return exclude ? path_excluded : path_untracked;
        }
  }
  
 +static enum path_treatment treat_path_fast(struct dir_struct *dir,
 +                                         struct untracked_cache_dir *untracked,
 +                                         struct cached_dir *cdir,
 +                                         struct strbuf *path,
 +                                         int baselen,
 +                                         const struct path_simplify *simplify)
 +{
 +      strbuf_setlen(path, baselen);
 +      if (!cdir->ucd) {
 +              strbuf_addstr(path, cdir->file);
 +              return path_untracked;
 +      }
 +      strbuf_addstr(path, cdir->ucd->name);
 +      /* treat_one_path() does this before it calls treat_directory() */
 +      strbuf_complete(path, '/');
 +      if (cdir->ucd->check_only)
 +              /*
 +               * check_only is set as a result of treat_directory() getting
 +               * to its bottom. Verify again the same set of directories
 +               * with check_only set.
 +               */
 +              return read_directory_recursive(dir, path->buf, path->len,
 +                                              cdir->ucd, 1, simplify);
 +      /*
 +       * We get path_recurse in the first run when
 +       * directory_exists_in_index() returns index_nonexistent. We
 +       * are sure that new changes in the index does not impact the
 +       * outcome. Return now.
 +       */
 +      return path_recurse;
 +}
 +
  static enum path_treatment treat_path(struct dir_struct *dir,
 -                                    struct dirent *de,
 +                                    struct untracked_cache_dir *untracked,
 +                                    struct cached_dir *cdir,
                                      struct strbuf *path,
                                      int baselen,
                                      const struct path_simplify *simplify)
  {
        int dtype;
 +      struct dirent *de = cdir->de;
  
 +      if (!de)
 +              return treat_path_fast(dir, untracked, cdir, path,
 +                                     baselen, simplify);
        if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
                return path_none;
        strbuf_setlen(path, baselen);
                return path_none;
  
        dtype = DTYPE(de);
 -      return treat_one_path(dir, path, simplify, dtype, de);
 +      return treat_one_path(dir, untracked, path, baselen, simplify, dtype, de);
 +}
 +
 +static void add_untracked(struct untracked_cache_dir *dir, const char *name)
 +{
 +      if (!dir)
 +              return;
 +      ALLOC_GROW(dir->untracked, dir->untracked_nr + 1,
 +                 dir->untracked_alloc);
 +      dir->untracked[dir->untracked_nr++] = xstrdup(name);
 +}
 +
 +static int valid_cached_dir(struct dir_struct *dir,
 +                          struct untracked_cache_dir *untracked,
 +                          struct strbuf *path,
 +                          int check_only)
 +{
 +      struct stat st;
 +
 +      if (!untracked)
 +              return 0;
 +
 +      if (stat(path->len ? path->buf : ".", &st)) {
 +              invalidate_directory(dir->untracked, untracked);
 +              memset(&untracked->stat_data, 0, sizeof(untracked->stat_data));
 +              return 0;
 +      }
 +      if (!untracked->valid ||
 +          match_stat_data_racy(&the_index, &untracked->stat_data, &st)) {
 +              if (untracked->valid)
 +                      invalidate_directory(dir->untracked, untracked);
 +              fill_stat_data(&untracked->stat_data, &st);
 +              return 0;
 +      }
 +
 +      if (untracked->check_only != !!check_only) {
 +              invalidate_directory(dir->untracked, untracked);
 +              return 0;
 +      }
 +
 +      /*
 +       * prep_exclude will be called eventually on this directory,
 +       * but it's called much later in last_exclude_matching(). We
 +       * need it now to determine the validity of the cache for this
 +       * path. The next calls will be nearly no-op, the way
 +       * prep_exclude() is designed.
 +       */
 +      if (path->len && path->buf[path->len - 1] != '/') {
 +              strbuf_addch(path, '/');
 +              prep_exclude(dir, path->buf, path->len);
 +              strbuf_setlen(path, path->len - 1);
 +      } else
 +              prep_exclude(dir, path->buf, path->len);
 +
 +      /* hopefully prep_exclude() haven't invalidated this entry... */
 +      return untracked->valid;
 +}
 +
 +static int open_cached_dir(struct cached_dir *cdir,
 +                         struct dir_struct *dir,
 +                         struct untracked_cache_dir *untracked,
 +                         struct strbuf *path,
 +                         int check_only)
 +{
 +      memset(cdir, 0, sizeof(*cdir));
 +      cdir->untracked = untracked;
 +      if (valid_cached_dir(dir, untracked, path, check_only))
 +              return 0;
 +      cdir->fdir = opendir(path->len ? path->buf : ".");
 +      if (dir->untracked)
 +              dir->untracked->dir_opened++;
 +      if (!cdir->fdir)
 +              return -1;
 +      return 0;
 +}
 +
 +static int read_cached_dir(struct cached_dir *cdir)
 +{
 +      if (cdir->fdir) {
 +              cdir->de = readdir(cdir->fdir);
 +              if (!cdir->de)
 +                      return -1;
 +              return 0;
 +      }
 +      while (cdir->nr_dirs < cdir->untracked->dirs_nr) {
 +              struct untracked_cache_dir *d = cdir->untracked->dirs[cdir->nr_dirs];
 +              if (!d->recurse) {
 +                      cdir->nr_dirs++;
 +                      continue;
 +              }
 +              cdir->ucd = d;
 +              cdir->nr_dirs++;
 +              return 0;
 +      }
 +      cdir->ucd = NULL;
 +      if (cdir->nr_files < cdir->untracked->untracked_nr) {
 +              struct untracked_cache_dir *d = cdir->untracked;
 +              cdir->file = d->untracked[cdir->nr_files++];
 +              return 0;
 +      }
 +      return -1;
 +}
 +
 +static void close_cached_dir(struct cached_dir *cdir)
 +{
 +      if (cdir->fdir)
 +              closedir(cdir->fdir);
 +      /*
 +       * We have gone through this directory and found no untracked
 +       * entries. Mark it valid.
 +       */
 +      if (cdir->untracked) {
 +              cdir->untracked->valid = 1;
 +              cdir->untracked->recurse = 1;
 +      }
  }
  
  /*
   */
  static enum path_treatment read_directory_recursive(struct dir_struct *dir,
                                    const char *base, int baselen,
 -                                  int check_only,
 +                                  struct untracked_cache_dir *untracked, int check_only,
                                    const struct path_simplify *simplify)
  {
 -      DIR *fdir;
 +      struct cached_dir cdir;
        enum path_treatment state, subdir_state, dir_state = path_none;
 -      struct dirent *de;
        struct strbuf path = STRBUF_INIT;
  
        strbuf_add(&path, base, baselen);
  
 -      fdir = opendir(path.len ? path.buf : ".");
 -      if (!fdir)
 +      if (open_cached_dir(&cdir, dir, untracked, &path, check_only))
                goto out;
  
 -      while ((de = readdir(fdir)) != NULL) {
 +      if (untracked)
 +              untracked->check_only = !!check_only;
 +
 +      while (!read_cached_dir(&cdir)) {
                /* check how the file or directory should be treated */
 -              state = treat_path(dir, de, &path, baselen, simplify);
 +              state = treat_path(dir, untracked, &cdir, &path, baselen, simplify);
 +
                if (state > dir_state)
                        dir_state = state;
  
                /* recurse into subdir if instructed by treat_path */
                if (state == path_recurse) {
 -                      subdir_state = read_directory_recursive(dir, path.buf,
 -                              path.len, check_only, simplify);
 +                      struct untracked_cache_dir *ud;
 +                      ud = lookup_untracked(dir->untracked, untracked,
 +                                            path.buf + baselen,
 +                                            path.len - baselen);
 +                      subdir_state =
 +                              read_directory_recursive(dir, path.buf, path.len,
 +                                                       ud, check_only, simplify);
                        if (subdir_state > dir_state)
                                dir_state = subdir_state;
                }
  
                if (check_only) {
                        /* abort early if maximum state has been reached */
 -                      if (dir_state == path_untracked)
 +                      if (dir_state == path_untracked) {
 +                              if (cdir.fdir)
 +                                      add_untracked(untracked, path.buf + baselen);
                                break;
 +                      }
                        /* skip the dir_add_* part */
                        continue;
                }
                        break;
  
                case path_untracked:
 -                      if (!(dir->flags & DIR_SHOW_IGNORED))
 -                              dir_add_name(dir, path.buf, path.len);
 +                      if (dir->flags & DIR_SHOW_IGNORED)
 +                              break;
 +                      dir_add_name(dir, path.buf, path.len);
 +                      if (cdir.fdir)
 +                              add_untracked(untracked, path.buf + baselen);
                        break;
  
                default:
                        break;
                }
        }
 -      closedir(fdir);
 +      close_cached_dir(&cdir);
   out:
        strbuf_release(&path);
  
@@@ -1807,7 -1467,7 +1807,7 @@@ static int treat_leading_path(struct di
                        break;
                if (simplify_away(sb.buf, sb.len, simplify))
                        break;
 -              if (treat_one_path(dir, &sb, simplify,
 +              if (treat_one_path(dir, NULL, &sb, baselen, simplify,
                                   DT_DIR, NULL) == path_none)
                        break; /* do not recurse into it */
                if (len <= baselen) {
        return rc;
  }
  
 +static const char *get_ident_string(void)
 +{
 +      static struct strbuf sb = STRBUF_INIT;
 +      struct utsname uts;
 +
 +      if (sb.len)
 +              return sb.buf;
 +      if (uname(&uts) < 0)
 +              die_errno(_("failed to get kernel name and information"));
 +      strbuf_addf(&sb, "Location %s, system %s", get_git_work_tree(),
 +                  uts.sysname);
 +      return sb.buf;
 +}
 +
 +static int ident_in_untracked(const struct untracked_cache *uc)
 +{
 +      /*
 +       * Previous git versions may have saved many NUL separated
 +       * strings in the "ident" field, but it is insane to manage
 +       * many locations, so just take care of the first one.
 +       */
 +
 +      return !strcmp(uc->ident.buf, get_ident_string());
 +}
 +
 +static void set_untracked_ident(struct untracked_cache *uc)
 +{
 +      strbuf_reset(&uc->ident);
 +      strbuf_addstr(&uc->ident, get_ident_string());
 +
 +      /*
 +       * This strbuf used to contain a list of NUL separated
 +       * strings, so save NUL too for backward compatibility.
 +       */
 +      strbuf_addch(&uc->ident, 0);
 +}
 +
 +static void new_untracked_cache(struct index_state *istate)
 +{
 +      struct untracked_cache *uc = xcalloc(1, sizeof(*uc));
 +      strbuf_init(&uc->ident, 100);
 +      uc->exclude_per_dir = ".gitignore";
 +      /* should be the same flags used by git-status */
 +      uc->dir_flags = DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
 +      set_untracked_ident(uc);
 +      istate->untracked = uc;
 +      istate->cache_changed |= UNTRACKED_CHANGED;
 +}
 +
 +void add_untracked_cache(struct index_state *istate)
 +{
 +      if (!istate->untracked) {
 +              new_untracked_cache(istate);
 +      } else {
 +              if (!ident_in_untracked(istate->untracked)) {
 +                      free_untracked_cache(istate->untracked);
 +                      new_untracked_cache(istate);
 +              }
 +      }
 +}
 +
 +void remove_untracked_cache(struct index_state *istate)
 +{
 +      if (istate->untracked) {
 +              free_untracked_cache(istate->untracked);
 +              istate->untracked = NULL;
 +              istate->cache_changed |= UNTRACKED_CHANGED;
 +      }
 +}
 +
 +static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *dir,
 +                                                    int base_len,
 +                                                    const struct pathspec *pathspec)
 +{
 +      struct untracked_cache_dir *root;
 +
 +      if (!dir->untracked || getenv("GIT_DISABLE_UNTRACKED_CACHE"))
 +              return NULL;
 +
 +      /*
 +       * We only support $GIT_DIR/info/exclude and core.excludesfile
 +       * as the global ignore rule files. Any other additions
 +       * (e.g. from command line) invalidate the cache. This
 +       * condition also catches running setup_standard_excludes()
 +       * before setting dir->untracked!
 +       */
 +      if (dir->unmanaged_exclude_files)
 +              return NULL;
 +
 +      /*
 +       * Optimize for the main use case only: whole-tree git
 +       * status. More work involved in treat_leading_path() if we
 +       * use cache on just a subset of the worktree. pathspec
 +       * support could make the matter even worse.
 +       */
 +      if (base_len || (pathspec && pathspec->nr))
 +              return NULL;
 +
 +      /* Different set of flags may produce different results */
 +      if (dir->flags != dir->untracked->dir_flags ||
 +          /*
 +           * See treat_directory(), case index_nonexistent. Without
 +           * this flag, we may need to also cache .git file content
 +           * for the resolve_gitlink_ref() call, which we don't.
 +           */
 +          !(dir->flags & DIR_SHOW_OTHER_DIRECTORIES) ||
 +          /* We don't support collecting ignore files */
 +          (dir->flags & (DIR_SHOW_IGNORED | DIR_SHOW_IGNORED_TOO |
 +                         DIR_COLLECT_IGNORED)))
 +              return NULL;
 +
 +      /*
 +       * If we use .gitignore in the cache and now you change it to
 +       * .gitexclude, everything will go wrong.
 +       */
 +      if (dir->exclude_per_dir != dir->untracked->exclude_per_dir &&
 +          strcmp(dir->exclude_per_dir, dir->untracked->exclude_per_dir))
 +              return NULL;
 +
 +      /*
 +       * EXC_CMDL is not considered in the cache. If people set it,
 +       * skip the cache.
 +       */
 +      if (dir->exclude_list_group[EXC_CMDL].nr)
 +              return NULL;
 +
 +      if (!ident_in_untracked(dir->untracked)) {
 +              warning(_("Untracked cache is disabled on this system or location."));
 +              return NULL;
 +      }
 +
 +      if (!dir->untracked->root) {
 +              const int len = sizeof(*dir->untracked->root);
 +              dir->untracked->root = xmalloc(len);
 +              memset(dir->untracked->root, 0, len);
 +      }
 +
 +      /* Validate $GIT_DIR/info/exclude and core.excludesfile */
 +      root = dir->untracked->root;
 +      if (hashcmp(dir->ss_info_exclude.sha1,
 +                  dir->untracked->ss_info_exclude.sha1)) {
 +              invalidate_gitignore(dir->untracked, root);
 +              dir->untracked->ss_info_exclude = dir->ss_info_exclude;
 +      }
 +      if (hashcmp(dir->ss_excludes_file.sha1,
 +                  dir->untracked->ss_excludes_file.sha1)) {
 +              invalidate_gitignore(dir->untracked, root);
 +              dir->untracked->ss_excludes_file = dir->ss_excludes_file;
 +      }
 +
 +      /* Make sure this directory is not dropped out at saving phase */
 +      root->recurse = 1;
 +      return root;
 +}
 +
  int read_directory(struct dir_struct *dir, const char *path, int len, const struct pathspec *pathspec)
  {
        struct path_simplify *simplify;
 +      struct untracked_cache_dir *untracked;
  
        /*
         * Check out create_simplify()
         * create_simplify().
         */
        simplify = create_simplify(pathspec ? pathspec->_raw : NULL);
 +      untracked = validate_untracked_cache(dir, len, pathspec);
 +      if (!untracked)
 +              /*
 +               * make sure untracked cache code path is disabled,
 +               * e.g. prep_exclude()
 +               */
 +              dir->untracked = NULL;
        if (!len || treat_leading_path(dir, path, len, simplify))
 -              read_directory_recursive(dir, path, len, 0, simplify);
 +              read_directory_recursive(dir, path, len, untracked, 0, simplify);
        free_simplify(simplify);
        qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
        qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);
 +      if (dir->untracked) {
 +              static struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS);
 +              trace_printf_key(&trace_untracked_stats,
 +                               "node creation: %u\n"
 +                               "gitignore invalidation: %u\n"
 +                               "directory invalidation: %u\n"
 +                               "opendir: %u\n",
 +                               dir->untracked->dir_created,
 +                               dir->untracked->gitignore_invalidated,
 +                               dir->untracked->dir_invalidated,
 +                               dir->untracked->dir_opened);
 +              if (dir->untracked == the_index.untracked &&
 +                  (dir->untracked->dir_opened ||
 +                   dir->untracked->gitignore_invalidated ||
 +                   dir->untracked->dir_invalidated))
 +                      the_index.cache_changed |= UNTRACKED_CHANGED;
 +              if (dir->untracked != the_index.untracked) {
 +                      free(dir->untracked);
 +                      dir->untracked = NULL;
 +              }
 +      }
        return dir->nr;
  }
  
@@@ -2044,15 -1520,6 +2044,15 @@@ int file_exists(const char *f
        return lstat(f, &sb) == 0;
  }
  
 +static int cmp_icase(char a, char b)
 +{
 +      if (a == b)
 +              return 0;
 +      if (ignore_case)
 +              return toupper(a) - toupper(b);
 +      return a - b;
 +}
 +
  /*
   * Given two normalized paths (a trailing slash is ok), if subdir is
   * outside dir, return -1.  Otherwise return the offset in subdir that
@@@ -2064,7 -1531,7 +2064,7 @@@ int dir_inside_of(const char *subdir, c
  
        assert(dir && subdir && *dir && *subdir);
  
 -      while (*dir && *subdir && *dir == *subdir) {
 +      while (*dir && *subdir && !cmp_icase(*dir, *subdir)) {
                dir++;
                subdir++;
                offset++;
@@@ -2149,7 -1616,8 +2149,7 @@@ static int remove_dir_recurse(struct st
                else
                        return -1;
        }
 -      if (path->buf[original_len - 1] != '/')
 -              strbuf_addch(path, '/');
 +      strbuf_complete(path, '/');
  
        len = path->len;
        while ((e = readdir(dir)) != NULL) {
@@@ -2198,8 -1666,6 +2198,8 @@@ int remove_dir_recursively(struct strbu
        return remove_dir_recurse(path, flag, NULL);
  }
  
 +static GIT_PATH_FUNC(git_path_info_exclude, "info/exclude")
 +
  void setup_standard_excludes(struct dir_struct *dir)
  {
        const char *path;
        if (!excludes_file)
                excludes_file = xdg_config_home("ignore");
        if (excludes_file && !access_or_warn(excludes_file, R_OK, 0))
 -              add_excludes_from_file(dir, excludes_file);
 +              add_excludes_from_file_1(dir, excludes_file,
 +                                       dir->untracked ? &dir->ss_excludes_file : NULL);
  
        /* per repository user preference */
 -      path = git_path("info/exclude");
 +      path = git_path_info_exclude();
        if (!access_or_warn(path, R_OK, 0))
 -              add_excludes_from_file(dir, path);
 +              add_excludes_from_file_1(dir, path,
 +                                       dir->untracked ? &dir->ss_info_exclude : NULL);
  }
  
  int remove_path(const char *name)
@@@ -2269,447 -1733,3 +2269,447 @@@ void clear_directory(struct dir_struct 
        }
        strbuf_release(&dir->basebuf);
  }
 +
 +struct ondisk_untracked_cache {
 +      struct stat_data info_exclude_stat;
 +      struct stat_data excludes_file_stat;
 +      uint32_t dir_flags;
 +      unsigned char info_exclude_sha1[20];
 +      unsigned char excludes_file_sha1[20];
 +      char exclude_per_dir[FLEX_ARRAY];
 +};
 +
 +#define ouc_size(len) (offsetof(struct ondisk_untracked_cache, exclude_per_dir) + len + 1)
 +
 +struct write_data {
 +      int index;         /* number of written untracked_cache_dir */
 +      struct ewah_bitmap *check_only; /* from untracked_cache_dir */
 +      struct ewah_bitmap *valid;      /* from untracked_cache_dir */
 +      struct ewah_bitmap *sha1_valid; /* set if exclude_sha1 is not null */
 +      struct strbuf out;
 +      struct strbuf sb_stat;
 +      struct strbuf sb_sha1;
 +};
 +
 +static void stat_data_to_disk(struct stat_data *to, const struct stat_data *from)
 +{
 +      to->sd_ctime.sec  = htonl(from->sd_ctime.sec);
 +      to->sd_ctime.nsec = htonl(from->sd_ctime.nsec);
 +      to->sd_mtime.sec  = htonl(from->sd_mtime.sec);
 +      to->sd_mtime.nsec = htonl(from->sd_mtime.nsec);
 +      to->sd_dev        = htonl(from->sd_dev);
 +      to->sd_ino        = htonl(from->sd_ino);
 +      to->sd_uid        = htonl(from->sd_uid);
 +      to->sd_gid        = htonl(from->sd_gid);
 +      to->sd_size       = htonl(from->sd_size);
 +}
 +
 +static void write_one_dir(struct untracked_cache_dir *untracked,
 +                        struct write_data *wd)
 +{
 +      struct stat_data stat_data;
 +      struct strbuf *out = &wd->out;
 +      unsigned char intbuf[16];
 +      unsigned int intlen, value;
 +      int i = wd->index++;
 +
 +      /*
 +       * untracked_nr should be reset whenever valid is clear, but
 +       * for safety..
 +       */
 +      if (!untracked->valid) {
 +              untracked->untracked_nr = 0;
 +              untracked->check_only = 0;
 +      }
 +
 +      if (untracked->check_only)
 +              ewah_set(wd->check_only, i);
 +      if (untracked->valid) {
 +              ewah_set(wd->valid, i);
 +              stat_data_to_disk(&stat_data, &untracked->stat_data);
 +              strbuf_add(&wd->sb_stat, &stat_data, sizeof(stat_data));
 +      }
 +      if (!is_null_sha1(untracked->exclude_sha1)) {
 +              ewah_set(wd->sha1_valid, i);
 +              strbuf_add(&wd->sb_sha1, untracked->exclude_sha1, 20);
 +      }
 +
 +      intlen = encode_varint(untracked->untracked_nr, intbuf);
 +      strbuf_add(out, intbuf, intlen);
 +
 +      /* skip non-recurse directories */
 +      for (i = 0, value = 0; i < untracked->dirs_nr; i++)
 +              if (untracked->dirs[i]->recurse)
 +                      value++;
 +      intlen = encode_varint(value, intbuf);
 +      strbuf_add(out, intbuf, intlen);
 +
 +      strbuf_add(out, untracked->name, strlen(untracked->name) + 1);
 +
 +      for (i = 0; i < untracked->untracked_nr; i++)
 +              strbuf_add(out, untracked->untracked[i],
 +                         strlen(untracked->untracked[i]) + 1);
 +
 +      for (i = 0; i < untracked->dirs_nr; i++)
 +              if (untracked->dirs[i]->recurse)
 +                      write_one_dir(untracked->dirs[i], wd);
 +}
 +
 +void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked)
 +{
 +      struct ondisk_untracked_cache *ouc;
 +      struct write_data wd;
 +      unsigned char varbuf[16];
 +      int varint_len;
 +      size_t len = strlen(untracked->exclude_per_dir);
 +
 +      FLEX_ALLOC_MEM(ouc, exclude_per_dir, untracked->exclude_per_dir, len);
 +      stat_data_to_disk(&ouc->info_exclude_stat, &untracked->ss_info_exclude.stat);
 +      stat_data_to_disk(&ouc->excludes_file_stat, &untracked->ss_excludes_file.stat);
 +      hashcpy(ouc->info_exclude_sha1, untracked->ss_info_exclude.sha1);
 +      hashcpy(ouc->excludes_file_sha1, untracked->ss_excludes_file.sha1);
 +      ouc->dir_flags = htonl(untracked->dir_flags);
 +
 +      varint_len = encode_varint(untracked->ident.len, varbuf);
 +      strbuf_add(out, varbuf, varint_len);
 +      strbuf_add(out, untracked->ident.buf, untracked->ident.len);
 +
 +      strbuf_add(out, ouc, ouc_size(len));
 +      free(ouc);
 +      ouc = NULL;
 +
 +      if (!untracked->root) {
 +              varint_len = encode_varint(0, varbuf);
 +              strbuf_add(out, varbuf, varint_len);
 +              return;
 +      }
 +
 +      wd.index      = 0;
 +      wd.check_only = ewah_new();
 +      wd.valid      = ewah_new();
 +      wd.sha1_valid = ewah_new();
 +      strbuf_init(&wd.out, 1024);
 +      strbuf_init(&wd.sb_stat, 1024);
 +      strbuf_init(&wd.sb_sha1, 1024);
 +      write_one_dir(untracked->root, &wd);
 +
 +      varint_len = encode_varint(wd.index, varbuf);
 +      strbuf_add(out, varbuf, varint_len);
 +      strbuf_addbuf(out, &wd.out);
 +      ewah_serialize_strbuf(wd.valid, out);
 +      ewah_serialize_strbuf(wd.check_only, out);
 +      ewah_serialize_strbuf(wd.sha1_valid, out);
 +      strbuf_addbuf(out, &wd.sb_stat);
 +      strbuf_addbuf(out, &wd.sb_sha1);
 +      strbuf_addch(out, '\0'); /* safe guard for string lists */
 +
 +      ewah_free(wd.valid);
 +      ewah_free(wd.check_only);
 +      ewah_free(wd.sha1_valid);
 +      strbuf_release(&wd.out);
 +      strbuf_release(&wd.sb_stat);
 +      strbuf_release(&wd.sb_sha1);
 +}
 +
 +static void free_untracked(struct untracked_cache_dir *ucd)
 +{
 +      int i;
 +      if (!ucd)
 +              return;
 +      for (i = 0; i < ucd->dirs_nr; i++)
 +              free_untracked(ucd->dirs[i]);
 +      for (i = 0; i < ucd->untracked_nr; i++)
 +              free(ucd->untracked[i]);
 +      free(ucd->untracked);
 +      free(ucd->dirs);
 +      free(ucd);
 +}
 +
 +void free_untracked_cache(struct untracked_cache *uc)
 +{
 +      if (uc)
 +              free_untracked(uc->root);
 +      free(uc);
 +}
 +
 +struct read_data {
 +      int index;
 +      struct untracked_cache_dir **ucd;
 +      struct ewah_bitmap *check_only;
 +      struct ewah_bitmap *valid;
 +      struct ewah_bitmap *sha1_valid;
 +      const unsigned char *data;
 +      const unsigned char *end;
 +};
 +
 +static void stat_data_from_disk(struct stat_data *to, const struct stat_data *from)
 +{
 +      to->sd_ctime.sec  = get_be32(&from->sd_ctime.sec);
 +      to->sd_ctime.nsec = get_be32(&from->sd_ctime.nsec);
 +      to->sd_mtime.sec  = get_be32(&from->sd_mtime.sec);
 +      to->sd_mtime.nsec = get_be32(&from->sd_mtime.nsec);
 +      to->sd_dev        = get_be32(&from->sd_dev);
 +      to->sd_ino        = get_be32(&from->sd_ino);
 +      to->sd_uid        = get_be32(&from->sd_uid);
 +      to->sd_gid        = get_be32(&from->sd_gid);
 +      to->sd_size       = get_be32(&from->sd_size);
 +}
 +
 +static int read_one_dir(struct untracked_cache_dir **untracked_,
 +                      struct read_data *rd)
 +{
 +      struct untracked_cache_dir ud, *untracked;
 +      const unsigned char *next, *data = rd->data, *end = rd->end;
 +      unsigned int value;
 +      int i, len;
 +
 +      memset(&ud, 0, sizeof(ud));
 +
 +      next = data;
 +      value = decode_varint(&next);
 +      if (next > end)
 +              return -1;
 +      ud.recurse         = 1;
 +      ud.untracked_alloc = value;
 +      ud.untracked_nr    = value;
 +      if (ud.untracked_nr)
 +              ALLOC_ARRAY(ud.untracked, ud.untracked_nr);
 +      data = next;
 +
 +      next = data;
 +      ud.dirs_alloc = ud.dirs_nr = decode_varint(&next);
 +      if (next > end)
 +              return -1;
 +      ALLOC_ARRAY(ud.dirs, ud.dirs_nr);
 +      data = next;
 +
 +      len = strlen((const char *)data);
 +      next = data + len + 1;
 +      if (next > rd->end)
 +              return -1;
 +      *untracked_ = untracked = xmalloc(st_add(sizeof(*untracked), len));
 +      memcpy(untracked, &ud, sizeof(ud));
 +      memcpy(untracked->name, data, len + 1);
 +      data = next;
 +
 +      for (i = 0; i < untracked->untracked_nr; i++) {
 +              len = strlen((const char *)data);
 +              next = data + len + 1;
 +              if (next > rd->end)
 +                      return -1;
 +              untracked->untracked[i] = xstrdup((const char*)data);
 +              data = next;
 +      }
 +
 +      rd->ucd[rd->index++] = untracked;
 +      rd->data = data;
 +
 +      for (i = 0; i < untracked->dirs_nr; i++) {
 +              len = read_one_dir(untracked->dirs + i, rd);
 +              if (len < 0)
 +                      return -1;
 +      }
 +      return 0;
 +}
 +
 +static void set_check_only(size_t pos, void *cb)
 +{
 +      struct read_data *rd = cb;
 +      struct untracked_cache_dir *ud = rd->ucd[pos];
 +      ud->check_only = 1;
 +}
 +
 +static void read_stat(size_t pos, void *cb)
 +{
 +      struct read_data *rd = cb;
 +      struct untracked_cache_dir *ud = rd->ucd[pos];
 +      if (rd->data + sizeof(struct stat_data) > rd->end) {
 +              rd->data = rd->end + 1;
 +              return;
 +      }
 +      stat_data_from_disk(&ud->stat_data, (struct stat_data *)rd->data);
 +      rd->data += sizeof(struct stat_data);
 +      ud->valid = 1;
 +}
 +
 +static void read_sha1(size_t pos, void *cb)
 +{
 +      struct read_data *rd = cb;
 +      struct untracked_cache_dir *ud = rd->ucd[pos];
 +      if (rd->data + 20 > rd->end) {
 +              rd->data = rd->end + 1;
 +              return;
 +      }
 +      hashcpy(ud->exclude_sha1, rd->data);
 +      rd->data += 20;
 +}
 +
 +static void load_sha1_stat(struct sha1_stat *sha1_stat,
 +                         const struct stat_data *stat,
 +                         const unsigned char *sha1)
 +{
 +      stat_data_from_disk(&sha1_stat->stat, stat);
 +      hashcpy(sha1_stat->sha1, sha1);
 +      sha1_stat->valid = 1;
 +}
 +
 +struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz)
 +{
 +      const struct ondisk_untracked_cache *ouc;
 +      struct untracked_cache *uc;
 +      struct read_data rd;
 +      const unsigned char *next = data, *end = (const unsigned char *)data + sz;
 +      const char *ident;
 +      int ident_len, len;
 +
 +      if (sz <= 1 || end[-1] != '\0')
 +              return NULL;
 +      end--;
 +
 +      ident_len = decode_varint(&next);
 +      if (next + ident_len > end)
 +              return NULL;
 +      ident = (const char *)next;
 +      next += ident_len;
 +
 +      ouc = (const struct ondisk_untracked_cache *)next;
 +      if (next + ouc_size(0) > end)
 +              return NULL;
 +
 +      uc = xcalloc(1, sizeof(*uc));
 +      strbuf_init(&uc->ident, ident_len);
 +      strbuf_add(&uc->ident, ident, ident_len);
 +      load_sha1_stat(&uc->ss_info_exclude, &ouc->info_exclude_stat,
 +                     ouc->info_exclude_sha1);
 +      load_sha1_stat(&uc->ss_excludes_file, &ouc->excludes_file_stat,
 +                     ouc->excludes_file_sha1);
 +      uc->dir_flags = get_be32(&ouc->dir_flags);
 +      uc->exclude_per_dir = xstrdup(ouc->exclude_per_dir);
 +      /* NUL after exclude_per_dir is covered by sizeof(*ouc) */
 +      next += ouc_size(strlen(ouc->exclude_per_dir));
 +      if (next >= end)
 +              goto done2;
 +
 +      len = decode_varint(&next);
 +      if (next > end || len == 0)
 +              goto done2;
 +
 +      rd.valid      = ewah_new();
 +      rd.check_only = ewah_new();
 +      rd.sha1_valid = ewah_new();
 +      rd.data       = next;
 +      rd.end        = end;
 +      rd.index      = 0;
 +      ALLOC_ARRAY(rd.ucd, len);
 +
 +      if (read_one_dir(&uc->root, &rd) || rd.index != len)
 +              goto done;
 +
 +      next = rd.data;
 +      len = ewah_read_mmap(rd.valid, next, end - next);
 +      if (len < 0)
 +              goto done;
 +
 +      next += len;
 +      len = ewah_read_mmap(rd.check_only, next, end - next);
 +      if (len < 0)
 +              goto done;
 +
 +      next += len;
 +      len = ewah_read_mmap(rd.sha1_valid, next, end - next);
 +      if (len < 0)
 +              goto done;
 +
 +      ewah_each_bit(rd.check_only, set_check_only, &rd);
 +      rd.data = next + len;
 +      ewah_each_bit(rd.valid, read_stat, &rd);
 +      ewah_each_bit(rd.sha1_valid, read_sha1, &rd);
 +      next = rd.data;
 +
 +done:
 +      free(rd.ucd);
 +      ewah_free(rd.valid);
 +      ewah_free(rd.check_only);
 +      ewah_free(rd.sha1_valid);
 +done2:
 +      if (next != end) {
 +              free_untracked_cache(uc);
 +              uc = NULL;
 +      }
 +      return uc;
 +}
 +
 +static void invalidate_one_directory(struct untracked_cache *uc,
 +                                   struct untracked_cache_dir *ucd)
 +{
 +      uc->dir_invalidated++;
 +      ucd->valid = 0;
 +      ucd->untracked_nr = 0;
 +}
 +
 +/*
 + * Normally when an entry is added or removed from a directory,
 + * invalidating that directory is enough. No need to touch its
 + * ancestors. When a directory is shown as "foo/bar/" in git-status
 + * however, deleting or adding an entry may have cascading effect.
 + *
 + * Say the "foo/bar/file" has become untracked, we need to tell the
 + * untracked_cache_dir of "foo" that "bar/" is not an untracked
 + * directory any more (because "bar" is managed by foo as an untracked
 + * "file").
 + *
 + * Similarly, if "foo/bar/file" moves from untracked to tracked and it
 + * was the last untracked entry in the entire "foo", we should show
 + * "foo/" instead. Which means we have to invalidate past "bar" up to
 + * "foo".
 + *
 + * This function traverses all directories from root to leaf. If there
 + * is a chance of one of the above cases happening, we invalidate back
 + * to root. Otherwise we just invalidate the leaf. There may be a more
 + * sophisticated way than checking for SHOW_OTHER_DIRECTORIES to
 + * detect these cases and avoid unnecessary invalidation, for example,
 + * checking for the untracked entry named "bar/" in "foo", but for now
 + * stick to something safe and simple.
 + */
 +static int invalidate_one_component(struct untracked_cache *uc,
 +                                  struct untracked_cache_dir *dir,
 +                                  const char *path, int len)
 +{
 +      const char *rest = strchr(path, '/');
 +
 +      if (rest) {
 +              int component_len = rest - path;
 +              struct untracked_cache_dir *d =
 +                      lookup_untracked(uc, dir, path, component_len);
 +              int ret =
 +                      invalidate_one_component(uc, d, rest + 1,
 +                                               len - (component_len + 1));
 +              if (ret)
 +                      invalidate_one_directory(uc, dir);
 +              return ret;
 +      }
 +
 +      invalidate_one_directory(uc, dir);
 +      return uc->dir_flags & DIR_SHOW_OTHER_DIRECTORIES;
 +}
 +
 +void untracked_cache_invalidate_path(struct index_state *istate,
 +                                   const char *path)
 +{
 +      if (!istate->untracked || !istate->untracked->root)
 +              return;
 +      invalidate_one_component(istate->untracked, istate->untracked->root,
 +                               path, strlen(path));
 +}
 +
 +void untracked_cache_remove_from_index(struct index_state *istate,
 +                                     const char *path)
 +{
 +      untracked_cache_invalidate_path(istate, path);
 +}
 +
 +void untracked_cache_add_to_index(struct index_state *istate,
 +                                const char *path)
 +{
 +      untracked_cache_invalidate_path(istate, path);
 +}
diff --combined dir.h
index cd46f30017ce239720926afdad4301b2ac402ccf,124c073b461dd265dd9a4f423a8790f3ca498a1c..301b737a37236c1d61d109b29fa556143c28df46
--- 1/dir.h
--- 2/dir.h
+++ b/dir.h
@@@ -27,7 -27,7 +27,7 @@@ struct exclude 
        int nowildcardlen;
        const char *base;
        int baselen;
-       int flags;
+       unsigned flags;         /* EXC_FLAG_* */
  
        /*
         * Counting starts from 1 for line numbers in ignore files,
@@@ -66,7 -66,6 +66,7 @@@ struct exclude_stack 
        struct exclude_stack *prev; /* the struct exclude_stack for the parent directory */
        int baselen;
        int exclude_ix; /* index of exclude_list within EXC_DIRS exclude_list_group */
 +      struct untracked_cache_dir *ucd;
  };
  
  struct exclude_list_group {
        struct exclude_list *el;
  };
  
 +struct sha1_stat {
 +      struct stat_data stat;
 +      unsigned char sha1[20];
 +      int valid;
 +};
 +
 +/*
 + *  Untracked cache
 + *
 + *  The following inputs are sufficient to determine what files in a
 + *  directory are excluded:
 + *
 + *   - The list of files and directories of the directory in question
 + *   - The $GIT_DIR/index
 + *   - dir_struct flags
 + *   - The content of $GIT_DIR/info/exclude
 + *   - The content of core.excludesfile
 + *   - The content (or the lack) of .gitignore of all parent directories
 + *     from $GIT_WORK_TREE
 + *   - The check_only flag in read_directory_recursive (for
 + *     DIR_HIDE_EMPTY_DIRECTORIES)
 + *
 + *  The first input can be checked using directory mtime. In many
 + *  filesystems, directory mtime (stat_data field) is updated when its
 + *  files or direct subdirs are added or removed.
 + *
 + *  The second one can be hooked from cache_tree_invalidate_path().
 + *  Whenever a file (or a submodule) is added or removed from a
 + *  directory, we invalidate that directory.
 + *
 + *  The remaining inputs are easy, their SHA-1 could be used to verify
 + *  their contents (exclude_sha1[], info_exclude_sha1[] and
 + *  excludes_file_sha1[])
 + */
 +struct untracked_cache_dir {
 +      struct untracked_cache_dir **dirs;
 +      char **untracked;
 +      struct stat_data stat_data;
 +      unsigned int untracked_alloc, dirs_nr, dirs_alloc;
 +      unsigned int untracked_nr;
 +      unsigned int check_only : 1;
 +      /* all data except 'dirs' in this struct are good */
 +      unsigned int valid : 1;
 +      unsigned int recurse : 1;
 +      /* null SHA-1 means this directory does not have .gitignore */
 +      unsigned char exclude_sha1[20];
 +      char name[FLEX_ARRAY];
 +};
 +
 +struct untracked_cache {
 +      struct sha1_stat ss_info_exclude;
 +      struct sha1_stat ss_excludes_file;
 +      const char *exclude_per_dir;
 +      struct strbuf ident;
 +      /*
 +       * dir_struct#flags must match dir_flags or the untracked
 +       * cache is ignored.
 +       */
 +      unsigned dir_flags;
 +      struct untracked_cache_dir *root;
 +      /* Statistics */
 +      int dir_created;
 +      int gitignore_invalidated;
 +      int dir_invalidated;
 +      int dir_opened;
 +};
 +
  struct dir_struct {
        int nr, alloc;
        int ignored_nr, ignored_alloc;
        struct exclude_stack *exclude_stack;
        struct exclude *exclude;
        struct strbuf basebuf;
 +
 +      /* Enable untracked file cache if set */
 +      struct untracked_cache *untracked;
 +      struct sha1_stat ss_info_exclude;
 +      struct sha1_stat ss_excludes_file;
 +      unsigned unmanaged_exclude_files;
  };
  
  /*
@@@ -226,10 -152,10 +226,10 @@@ struct dir_entry *dir_add_ignored(struc
   * attr.c:path_matches()
   */
  extern int match_basename(const char *, int,
-                         const char *, int, int, int);
+                         const char *, int, int, unsigned);
  extern int match_pathname(const char *, int,
                          const char *, int,
-                         const char *, int, int, int);
+                         const char *, int, int, unsigned);
  
  extern struct exclude *last_exclude_matching(struct dir_struct *dir,
                                             const char *name, int *dtype);
@@@ -241,7 -167,7 +241,7 @@@ extern struct exclude_list *add_exclude
  extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
                                          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 parse_exclude_pattern(const char **string, int *patternlen, unsigned *flags, int *nowildcardlen);
  extern void add_exclude(const char *string, const char *base,
                        int baselen, struct exclude_list *el, int srcpos);
  extern void clear_exclude_list(struct exclude_list *el);
@@@ -300,13 -226,4 +300,13 @@@ static inline int dir_path_match(const 
                              has_trailing_dir);
  }
  
 +void untracked_cache_invalidate_path(struct index_state *, const char *);
 +void untracked_cache_remove_from_index(struct index_state *, const char *);
 +void untracked_cache_add_to_index(struct index_state *, const char *);
 +
 +void free_untracked_cache(struct untracked_cache *);
 +struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz);
 +void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked);
 +void add_untracked_cache(struct index_state *istate);
 +void remove_untracked_cache(struct index_state *istate);
  #endif