Merge branch 'sl/clean-d-ignored-fix'
authorJunio C Hamano <gitster@pobox.com>
Fri, 2 Jun 2017 06:06:04 +0000 (15:06 +0900)
committerJunio C Hamano <gitster@pobox.com>
Fri, 2 Jun 2017 06:06:05 +0000 (15:06 +0900)
"git clean -d" used to clean directories that has ignored files,
even though the command should not lose ignored ones without "-x".
"git status --ignored" did not list ignored and untracked files
without "-uall". These have been corrected.

* sl/clean-d-ignored-fix:
clean: teach clean -d to preserve ignored paths
dir: expose cmp_name() and check_contains()
dir: hide untracked contents of untracked dirs
dir: recurse into untracked dirs for ignored files
t7061: status --ignored should search untracked dirs
t7300: clean -d should skip dirs with ignored files

1  2 
builtin/clean.c
dir.c
dir.h
diff --combined builtin/clean.c
index 329b68c40b2f1698e08776cb05bb9388d5f77634,727203318726bf3e6dbd7a6881391d233b4f106e..142bf668cffe814006fae791f1bdc6b20fe6f366
@@@ -174,10 -174,8 +174,10 @@@ static int remove_dirs(struct strbuf *p
                /* an empty dir could be removed even if it is unreadble */
                res = dry_run ? 0 : rmdir(path->buf);
                if (res) {
 +                      int saved_errno = errno;
                        quote_path_relative(path->buf, prefix, &quoted);
 -                      warning(_(msg_warn_remove_failed), quoted.buf);
 +                      errno = saved_errno;
 +                      warning_errno(_(msg_warn_remove_failed), quoted.buf);
                        *dir_gone = 0;
                }
                return res;
                                quote_path_relative(path->buf, prefix, &quoted);
                                string_list_append(&dels, quoted.buf);
                        } else {
 +                              int saved_errno = errno;
                                quote_path_relative(path->buf, prefix, &quoted);
 -                              warning(_(msg_warn_remove_failed), quoted.buf);
 +                              errno = saved_errno;
 +                              warning_errno(_(msg_warn_remove_failed), quoted.buf);
                                *dir_gone = 0;
                                ret = 1;
                        }
                if (!res)
                        *dir_gone = 1;
                else {
 +                      int saved_errno = errno;
                        quote_path_relative(path->buf, prefix, &quoted);
 -                      warning(_(msg_warn_remove_failed), quoted.buf);
 +                      errno = saved_errno;
 +                      warning_errno(_(msg_warn_remove_failed), quoted.buf);
                        *dir_gone = 0;
                        ret = 1;
                }
@@@ -683,7 -677,7 +683,7 @@@ static int filter_by_patterns_cmd(void
                for_each_string_list_item(item, &del_list) {
                        int dtype = DT_UNKNOWN;
  
 -                      if (is_excluded(&dir, item->string, &dtype)) {
 +                      if (is_excluded(&dir, &the_index, item->string, &dtype)) {
                                *item->string = '\0';
                                changed++;
                        }
@@@ -857,6 -851,38 +857,38 @@@ static void interactive_main_loop(void
        }
  }
  
+ static void correct_untracked_entries(struct dir_struct *dir)
+ {
+       int src, dst, ign;
+       for (src = dst = ign = 0; src < dir->nr; src++) {
+               /* skip paths in ignored[] that cannot be inside entries[src] */
+               while (ign < dir->ignored_nr &&
+                      0 <= cmp_dir_entry(&dir->entries[src], &dir->ignored[ign]))
+                       ign++;
+               if (ign < dir->ignored_nr &&
+                   check_dir_entry_contains(dir->entries[src], dir->ignored[ign])) {
+                       /* entries[src] contains an ignored path, so we drop it */
+                       free(dir->entries[src]);
+               } else {
+                       struct dir_entry *ent = dir->entries[src++];
+                       /* entries[src] does not contain an ignored path, so we keep it */
+                       dir->entries[dst++] = ent;
+                       /* then discard paths in entries[] contained inside entries[src] */
+                       while (src < dir->nr &&
+                              check_dir_entry_contains(ent, dir->entries[src]))
+                               free(dir->entries[src++]);
+                       /* compensate for the outer loop's loop control */
+                       src--;
+               }
+       }
+       dir->nr = dst;
+ }
  int cmd_clean(int argc, const char **argv, const char *prefix)
  {
        int i, res;
  
        dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
  
+       if (remove_directories)
+               dir.flags |= DIR_SHOW_IGNORED_TOO | DIR_KEEP_UNTRACKED_CONTENTS;
        if (read_cache() < 0)
                die(_("index file corrupt"));
  
                       PATHSPEC_PREFER_CWD,
                       prefix, argv);
  
 -      fill_directory(&dir, &pathspec);
 +      fill_directory(&dir, &the_index, &pathspec);
+       correct_untracked_entries(&dir);
  
        for (i = 0; i < dir.nr; i++) {
                struct dir_entry *ent = dir.entries[i];
                string_list_append(&del_list, rel);
        }
  
+       for (i = 0; i < dir.nr; i++)
+               free(dir.entries[i]);
+       for (i = 0; i < dir.ignored_nr; i++)
+               free(dir.ignored[i]);
        if (interactive && del_list.nr > 0)
                interactive_main_loop();
  
                } else {
                        res = dry_run ? 0 : unlink(abs_path.buf);
                        if (res) {
 +                              int saved_errno = errno;
                                qname = quote_path_relative(item->string, NULL, &buf);
 -                              warning(_(msg_warn_remove_failed), qname);
 +                              errno = saved_errno;
 +                              warning_errno(_(msg_warn_remove_failed), qname);
                                errors++;
                        } else if (!quiet) {
                                qname = quote_path_relative(item->string, NULL, &buf);
diff --combined dir.c
index 3f3167e55a92c9d23c7686c190544de9fbbb9164,31c6e1dac008aa3c47e9e4c688c3043b1f91a9b6..9efcf1eab689bd0e637ea1b961b261eb97862b04
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -7,10 -7,8 +7,10 @@@
   * Copyright (C) Linus Torvalds, 2005-2006
   *             Junio Hamano, 2005-2006
   */
 +#define NO_THE_INDEX_COMPATIBILITY_MACROS
  #include "cache.h"
  #include "dir.h"
 +#include "attr.h"
  #include "refs.h"
  #include "wildmatch.h"
  #include "pathspec.h"
@@@ -46,11 -44,9 +46,11 @@@ struct cached_dir 
  };
  
  static enum path_treatment read_directory_recursive(struct dir_struct *dir,
 -      const char *path, int len, struct untracked_cache_dir *untracked,
 +      struct index_state *istate, const char *path, int len,
 +      struct untracked_cache_dir *untracked,
        int check_only, const struct pathspec *pathspec);
 -static int get_dtype(struct dirent *de, const char *path, int len);
 +static int get_dtype(struct dirent *de, struct index_state *istate,
 +                   const char *path, int len);
  
  int fspathcmp(const char *a, const char *b)
  {
@@@ -138,8 -134,7 +138,8 @@@ static size_t common_prefix_len(const s
                       PATHSPEC_LITERAL |
                       PATHSPEC_GLOB |
                       PATHSPEC_ICASE |
 -                     PATHSPEC_EXCLUDE);
 +                     PATHSPEC_EXCLUDE |
 +                     PATHSPEC_ATTR);
  
        for (n = 0; n < pathspec->nr; n++) {
                size_t i = 0, len = 0, item_len;
@@@ -177,9 -172,7 +177,9 @@@ char *common_prefix(const struct pathsp
        return len ? xmemdupz(pathspec->items[0].match, len) : NULL;
  }
  
 -int fill_directory(struct dir_struct *dir, const struct pathspec *pathspec)
 +int fill_directory(struct dir_struct *dir,
 +                 struct index_state *istate,
 +                 const struct pathspec *pathspec)
  {
        const char *prefix;
        size_t prefix_len;
        prefix = prefix_len ? pathspec->items[0].match : "";
  
        /* Read the directory and prune it */
 -      read_directory(dir, prefix, prefix_len, pathspec);
 +      read_directory(dir, istate, prefix, prefix_len, pathspec);
  
        return prefix_len;
  }
@@@ -216,36 -209,6 +216,36 @@@ int within_depth(const char *name, int 
  #define DO_MATCH_DIRECTORY (1<<1)
  #define DO_MATCH_SUBMODULE (1<<2)
  
 +static int match_attrs(const char *name, int namelen,
 +                     const struct pathspec_item *item)
 +{
 +      int i;
 +
 +      git_check_attr(name, item->attr_check);
 +      for (i = 0; i < item->attr_match_nr; i++) {
 +              const char *value;
 +              int matched;
 +              enum attr_match_mode match_mode;
 +
 +              value = item->attr_check->items[i].value;
 +              match_mode = item->attr_match[i].match_mode;
 +
 +              if (ATTR_TRUE(value))
 +                      matched = (match_mode == MATCH_SET);
 +              else if (ATTR_FALSE(value))
 +                      matched = (match_mode == MATCH_UNSET);
 +              else if (ATTR_UNSET(value))
 +                      matched = (match_mode == MATCH_UNSPECIFIED);
 +              else
 +                      matched = (match_mode == MATCH_VALUE &&
 +                                 !strcmp(item->attr_match[i].value, value));
 +              if (!matched)
 +                      return 0;
 +      }
 +
 +      return 1;
 +}
 +
  /*
   * Does 'match' match the given name?
   * A match is found if
@@@ -298,9 -261,6 +298,9 @@@ static int match_pathspec_item(const st
            strncmp(item->match, name - prefix, item->prefix))
                return 0;
  
 +      if (item->attr_match_nr && !match_attrs(name, namelen, item))
 +              return 0;
 +
        /* If the match was just the prefix, we matched */
        if (!*match)
                return MATCHED_RECURSIVELY;
@@@ -379,8 -339,7 +379,8 @@@ static int do_match_pathspec(const stru
                       PATHSPEC_LITERAL |
                       PATHSPEC_GLOB |
                       PATHSPEC_ICASE |
 -                     PATHSPEC_EXCLUDE);
 +                     PATHSPEC_EXCLUDE |
 +                     PATHSPEC_ATTR);
  
        if (!ps->nr) {
                if (!ps->recursive ||
@@@ -592,8 -551,7 +592,8 @@@ void add_exclude(const char *string, co
        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 struct index_state *istate,
 +                                              const char *path, size_t *size,
                                                struct sha1_stat *sha1_stat)
  {
        int pos, len;
        void *data;
  
        len = strlen(path);
 -      pos = cache_name_pos(path, len);
 +      pos = index_name_pos(istate, path, len);
        if (pos < 0)
                return NULL;
 -      if (!ce_skip_worktree(active_cache[pos]))
 +      if (!ce_skip_worktree(istate->cache[pos]))
                return NULL;
 -      data = read_sha1_file(active_cache[pos]->oid.hash, &type, &sz);
 +      data = read_sha1_file(istate->cache[pos]->oid.hash, &type, &sz);
        if (!data || type != OBJ_BLOB) {
                free(data);
                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]->oid.hash);
 +              hashcpy(sha1_stat->sha1, istate->cache[pos]->oid.hash);
        }
        return data;
  }
@@@ -733,7 -691,7 +733,7 @@@ static void invalidate_directory(struc
  
  /*
   * 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
 + * an index if 'istate' is non-null), parse it and store the
   * exclude rules in "el".
   *
   * If "ss" is not NULL, compute SHA-1 of the exclude file and fill
   * 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 exclude_list *el,
 +                      struct index_state *istate,
                        struct sha1_stat *sha1_stat)
  {
        struct stat st;
                        warn_on_inaccessible(fname);
                if (0 <= fd)
                        close(fd);
 -              if (!check_index ||
 -                  (buf = read_skip_worktree_file_from_index(fname, &size, sha1_stat)) == NULL)
 +              if (!istate ||
 +                  (buf = read_skip_worktree_file_from_index(istate, fname, &size, sha1_stat)) == NULL)
                        return -1;
                if (size == 0) {
                        free(buf);
                if (sha1_stat) {
                        int pos;
                        if (sha1_stat->valid &&
 -                          !match_stat_data_racy(&the_index, &sha1_stat->stat, &st))
 +                          !match_stat_data_racy(istate, &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]) &&
 +                      else if (istate &&
 +                               (pos = index_name_pos(istate, fname, strlen(fname))) >= 0 &&
 +                               !ce_stage(istate->cache[pos]) &&
 +                               ce_uptodate(istate->cache[pos]) &&
                                 !would_convert_to_git(fname))
                                hashcpy(sha1_stat->sha1,
 -                                      active_cache[pos]->oid.hash);
 +                                      istate->cache[pos]->oid.hash);
                        else
                                hash_sha1_file(buf, size, "blob", sha1_stat->sha1);
                        fill_stat_data(&sha1_stat->stat, &st);
  
  int add_excludes_from_file_to_list(const char *fname, const char *base,
                                   int baselen, struct exclude_list *el,
 -                                 int check_index)
 +                                 struct index_state *istate)
  {
 -      return add_excludes(fname, base, baselen, el, check_index, NULL);
 +      return add_excludes(fname, base, baselen, el, istate, NULL);
  }
  
  struct exclude_list *add_exclude_list(struct dir_struct *dir,
@@@ -862,7 -819,7 +862,7 @@@ static void add_excludes_from_file_1(st
        if (!dir->untracked)
                dir->unmanaged_exclude_files++;
        el = add_exclude_list(dir, EXC_FILE, fname);
 -      if (add_excludes(fname, "", 0, el, 0, sha1_stat) < 0)
 +      if (add_excludes(fname, "", 0, el, NULL, sha1_stat) < 0)
                die("cannot use %s as an exclude file", fname);
  }
  
@@@ -965,8 -922,7 +965,8 @@@ static struct exclude *last_exclude_mat
                                                       int pathlen,
                                                       const char *basename,
                                                       int *dtype,
 -                                                     struct exclude_list *el)
 +                                                     struct exclude_list *el,
 +                                                     struct index_state *istate)
  {
        struct exclude *exc = NULL; /* undecided */
        int i;
  
                if (x->flags & EXC_FLAG_MUSTBEDIR) {
                        if (*dtype == DT_UNKNOWN)
 -                              *dtype = get_dtype(NULL, pathname, pathlen);
 +                              *dtype = get_dtype(NULL, istate, pathname, pathlen);
                        if (*dtype != DT_DIR)
                                continue;
                }
   */
  int is_excluded_from_list(const char *pathname,
                          int pathlen, const char *basename, int *dtype,
 -                        struct exclude_list *el)
 +                        struct exclude_list *el, struct index_state *istate)
  {
        struct exclude *exclude;
 -      exclude = last_exclude_matching_from_list(pathname, pathlen, basename, dtype, el);
 +      exclude = last_exclude_matching_from_list(pathname, pathlen, basename,
 +                                                dtype, el, istate);
        if (exclude)
                return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
        return -1; /* undecided */
  }
  
  static struct exclude *last_exclude_matching_from_lists(struct dir_struct *dir,
 +                                                      struct index_state *istate,
                const char *pathname, int pathlen, const char *basename,
                int *dtype_p)
  {
                for (j = group->nr - 1; j >= 0; j--) {
                        exclude = last_exclude_matching_from_list(
                                pathname, pathlen, basename, dtype_p,
 -                              &group->el[j]);
 +                              &group->el[j], istate);
                        if (exclude)
                                return exclude;
                }
   * 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)
 +static void prep_exclude(struct dir_struct *dir,
 +                       struct index_state *istate,
 +                       const char *base, int baselen)
  {
        struct exclude_list_group *group;
        struct exclude_list *el;
                        int dt = DT_DIR;
                        dir->basebuf.buf[stk->baselen - 1] = 0;
                        dir->exclude = last_exclude_matching_from_lists(dir,
 +                                                                      istate,
                                dir->basebuf.buf, stk->baselen - 1,
                                dir->basebuf.buf + current, &dt);
                        dir->basebuf.buf[stk->baselen - 1] = '/';
                        strbuf_addbuf(&sb, &dir->basebuf);
                        strbuf_addstr(&sb, dir->exclude_per_dir);
                        el->src = strbuf_detach(&sb, NULL);
 -                      add_excludes(el->src, el->src, stk->baselen, el, 1,
 +                      add_excludes(el->src, el->src, stk->baselen, el, istate,
                                     untracked ? &sha1_stat : NULL);
                }
                /*
   * undecided.
   */
  struct exclude *last_exclude_matching(struct dir_struct *dir,
 -                                           const char *pathname,
 -                                           int *dtype_p)
 +                                    struct index_state *istate,
 +                                    const char *pathname,
 +                                    int *dtype_p)
  {
        int pathlen = strlen(pathname);
        const char *basename = strrchr(pathname, '/');
        basename = (basename) ? basename+1 : pathname;
  
 -      prep_exclude(dir, pathname, basename-pathname);
 +      prep_exclude(dir, istate, pathname, basename-pathname);
  
        if (dir->exclude)
                return dir->exclude;
  
 -      return last_exclude_matching_from_lists(dir, pathname, pathlen,
 +      return last_exclude_matching_from_lists(dir, istate, pathname, pathlen,
                        basename, dtype_p);
  }
  
   * scans all exclude lists to determine whether pathname is excluded.
   * Returns 1 if true, otherwise 0.
   */
 -int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
 +int is_excluded(struct dir_struct *dir, struct index_state *istate,
 +              const char *pathname, int *dtype_p)
  {
        struct exclude *exclude =
 -              last_exclude_matching(dir, pathname, dtype_p);
 +              last_exclude_matching(dir, istate, pathname, dtype_p);
        if (exclude)
                return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
        return 0;
@@@ -1248,22 -1197,18 +1248,22 @@@ static struct dir_entry *dir_entry_new(
        return ent;
  }
  
 -static struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)
 +static struct dir_entry *dir_add_name(struct dir_struct *dir,
 +                                    struct index_state *istate,
 +                                    const char *pathname, int len)
  {
 -      if (cache_file_exists(pathname, len, ignore_case))
 +      if (index_file_exists(istate, pathname, len, ignore_case))
                return NULL;
  
        ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc);
        return dir->entries[dir->nr++] = dir_entry_new(pathname, len);
  }
  
 -struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len)
 +struct dir_entry *dir_add_ignored(struct dir_struct *dir,
 +                                struct index_state *istate,
 +                                const char *pathname, int len)
  {
 -      if (!cache_name_is_other(pathname, len))
 +      if (!index_name_is_other(istate, pathname, len))
                return NULL;
  
        ALLOC_GROW(dir->ignored, dir->ignored_nr+1, dir->ignored_alloc);
@@@ -1281,15 -1226,14 +1281,15 @@@ enum exist_status 
   * the directory name; instead, use the case insensitive
   * directory hash.
   */
 -static enum exist_status directory_exists_in_index_icase(const char *dirname, int len)
 +static enum exist_status directory_exists_in_index_icase(struct index_state *istate,
 +                                                       const char *dirname, int len)
  {
        struct cache_entry *ce;
  
 -      if (cache_dir_exists(dirname, len))
 +      if (index_dir_exists(istate, dirname, len))
                return index_directory;
  
 -      ce = cache_file_exists(dirname, len, ignore_case);
 +      ce = index_file_exists(istate, dirname, len, ignore_case);
        if (ce && S_ISGITLINK(ce->ce_mode))
                return index_gitdir;
  
   * the files it contains) will sort with the '/' at the
   * end.
   */
 -static enum exist_status directory_exists_in_index(const char *dirname, int len)
 +static enum exist_status directory_exists_in_index(struct index_state *istate,
 +                                                 const char *dirname, int len)
  {
        int pos;
  
        if (ignore_case)
 -              return directory_exists_in_index_icase(dirname, len);
 +              return directory_exists_in_index_icase(istate, dirname, len);
  
 -      pos = cache_name_pos(dirname, len);
 +      pos = index_name_pos(istate, dirname, len);
        if (pos < 0)
                pos = -pos-1;
 -      while (pos < active_nr) {
 -              const struct cache_entry *ce = active_cache[pos++];
 +      while (pos < istate->cache_nr) {
 +              const struct cache_entry *ce = istate->cache[pos++];
                unsigned char endchar;
  
                if (strncmp(ce->name, dirname, len))
   *  (c) otherwise, we recurse into it.
   */
  static enum path_treatment treat_directory(struct dir_struct *dir,
 +      struct index_state *istate,
        struct untracked_cache_dir *untracked,
        const char *dirname, int len, int baselen, int exclude,
        const struct pathspec *pathspec)
  {
        /* The "len-1" is to strip the final '/' */
 -      switch (directory_exists_in_index(dirname, len-1)) {
 +      switch (directory_exists_in_index(istate, dirname, len-1)) {
        case index_directory:
                return path_recurse;
  
  
        untracked = lookup_untracked(dir->untracked, untracked,
                                     dirname + baselen, len - baselen);
 -      return read_directory_recursive(dir, dirname, len,
 +      return read_directory_recursive(dir, istate, dirname, len,
                                        untracked, 1, pathspec);
  }
  
@@@ -1419,8 -1361,7 +1419,8 @@@ static int simplify_away(const char *pa
                       PATHSPEC_LITERAL |
                       PATHSPEC_GLOB |
                       PATHSPEC_ICASE |
 -                     PATHSPEC_EXCLUDE);
 +                     PATHSPEC_EXCLUDE |
 +                     PATHSPEC_ATTR);
  
        for (i = 0; i < pathspec->nr; i++) {
                const struct pathspec_item *item = &pathspec->items[i];
@@@ -1477,13 -1418,12 +1477,13 @@@ static int exclude_matches_pathspec(con
        return 0;
  }
  
 -static int get_index_dtype(const char *path, int len)
 +static int get_index_dtype(struct index_state *istate,
 +                         const char *path, int len)
  {
        int pos;
        const struct cache_entry *ce;
  
 -      ce = cache_file_exists(path, len, 0);
 +      ce = index_file_exists(istate, path, len, 0);
        if (ce) {
                if (!ce_uptodate(ce))
                        return DT_UNKNOWN;
        }
  
        /* Try to look it up as a directory */
 -      pos = cache_name_pos(path, len);
 +      pos = index_name_pos(istate, path, len);
        if (pos >= 0)
                return DT_UNKNOWN;
        pos = -pos-1;
 -      while (pos < active_nr) {
 -              ce = active_cache[pos++];
 +      while (pos < istate->cache_nr) {
 +              ce = istate->cache[pos++];
                if (strncmp(ce->name, path, len))
                        break;
                if (ce->name[len] > '/')
        return DT_UNKNOWN;
  }
  
 -static int get_dtype(struct dirent *de, const char *path, int len)
 +static int get_dtype(struct dirent *de, struct index_state *istate,
 +                   const char *path, int len)
  {
        int dtype = de ? DTYPE(de) : DT_UNKNOWN;
        struct stat st;
  
        if (dtype != DT_UNKNOWN)
                return dtype;
 -      dtype = get_index_dtype(path, len);
 +      dtype = get_index_dtype(istate, path, len);
        if (dtype != DT_UNKNOWN)
                return dtype;
        if (lstat(path, &st))
  
  static enum path_treatment treat_one_path(struct dir_struct *dir,
                                          struct untracked_cache_dir *untracked,
 +                                        struct index_state *istate,
                                          struct strbuf *path,
                                          int baselen,
                                          const struct pathspec *pathspec,
                                          int dtype, struct dirent *de)
  {
        int exclude;
 -      int has_path_in_index = !!cache_file_exists(path->buf, path->len, ignore_case);
 +      int has_path_in_index = !!index_file_exists(istate, path->buf, path->len, ignore_case);
  
        if (dtype == DT_UNKNOWN)
 -              dtype = get_dtype(de, path->buf, path->len);
 +              dtype = get_dtype(de, istate, path->buf, path->len);
  
        /* Always exclude indexed files */
        if (dtype != DT_DIR && has_path_in_index)
        if ((dir->flags & DIR_COLLECT_KILLED_ONLY) &&
            (dtype == DT_DIR) &&
            !has_path_in_index &&
 -          (directory_exists_in_index(path->buf, path->len) == index_nonexistent))
 +          (directory_exists_in_index(istate, path->buf, path->len) == index_nonexistent))
                return path_none;
  
 -      exclude = is_excluded(dir, path->buf, &dtype);
 +      exclude = is_excluded(dir, istate, path->buf, &dtype);
  
        /*
         * Excluded? If we don't explicitly want to show
                return path_none;
        case DT_DIR:
                strbuf_addch(path, '/');
 -              return treat_directory(dir, untracked, path->buf, path->len,
 +              return treat_directory(dir, istate, untracked, path->buf, path->len,
                                       baselen, exclude, pathspec);
        case DT_REG:
        case DT_LNK:
  static enum path_treatment treat_path_fast(struct dir_struct *dir,
                                           struct untracked_cache_dir *untracked,
                                           struct cached_dir *cdir,
 +                                         struct index_state *istate,
                                           struct strbuf *path,
                                           int baselen,
                                           const struct pathspec *pathspec)
                 * to its bottom. Verify again the same set of directories
                 * with check_only set.
                 */
 -              return read_directory_recursive(dir, path->buf, path->len,
 +              return read_directory_recursive(dir, istate, path->buf, path->len,
                                                cdir->ucd, 1, pathspec);
        /*
         * We get path_recurse in the first run when
  static enum path_treatment treat_path(struct dir_struct *dir,
                                      struct untracked_cache_dir *untracked,
                                      struct cached_dir *cdir,
 +                                    struct index_state *istate,
                                      struct strbuf *path,
                                      int baselen,
                                      const struct pathspec *pathspec)
        struct dirent *de = cdir->de;
  
        if (!de)
 -              return treat_path_fast(dir, untracked, cdir, path,
 +              return treat_path_fast(dir, untracked, cdir, istate, path,
                                       baselen, pathspec);
        if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
                return path_none;
                return path_none;
  
        dtype = DTYPE(de);
 -      return treat_one_path(dir, untracked, path, baselen, pathspec, dtype, de);
 +      return treat_one_path(dir, untracked, istate, path, baselen, pathspec, dtype, de);
  }
  
  static void add_untracked(struct untracked_cache_dir *dir, const char *name)
  
  static int valid_cached_dir(struct dir_struct *dir,
                            struct untracked_cache_dir *untracked,
 +                          struct index_state *istate,
                            struct strbuf *path,
                            int check_only)
  {
                return 0;
        }
        if (!untracked->valid ||
 -          match_stat_data_racy(&the_index, &untracked->stat_data, &st)) {
 +          match_stat_data_racy(istate, &untracked->stat_data, &st)) {
                if (untracked->valid)
                        invalidate_directory(dir->untracked, untracked);
                fill_stat_data(&untracked->stat_data, &st);
         */
        if (path->len && path->buf[path->len - 1] != '/') {
                strbuf_addch(path, '/');
 -              prep_exclude(dir, path->buf, path->len);
 +              prep_exclude(dir, istate, path->buf, path->len);
                strbuf_setlen(path, path->len - 1);
        } else
 -              prep_exclude(dir, path->buf, path->len);
 +              prep_exclude(dir, istate, 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 index_state *istate,
                           struct strbuf *path,
                           int check_only)
  {
        memset(cdir, 0, sizeof(*cdir));
        cdir->untracked = untracked;
 -      if (valid_cached_dir(dir, untracked, path, check_only))
 +      if (valid_cached_dir(dir, untracked, istate, path, check_only))
                return 0;
        cdir->fdir = opendir(path->len ? path->buf : ".");
        if (dir->untracked)
@@@ -1788,9 -1722,9 +1788,9 @@@ static void close_cached_dir(struct cac
   * Returns the most significant path_treatment value encountered in the scan.
   */
  static enum path_treatment read_directory_recursive(struct dir_struct *dir,
 -                                  const char *base, int baselen,
 -                                  struct untracked_cache_dir *untracked, int check_only,
 -                                  const struct pathspec *pathspec)
 +      struct index_state *istate, const char *base, int baselen,
 +      struct untracked_cache_dir *untracked, int check_only,
 +      const struct pathspec *pathspec)
  {
        struct cached_dir cdir;
        enum path_treatment state, subdir_state, dir_state = path_none;
  
        strbuf_add(&path, base, baselen);
  
 -      if (open_cached_dir(&cdir, dir, untracked, &path, check_only))
 +      if (open_cached_dir(&cdir, dir, untracked, istate, &path, check_only))
                goto out;
  
        if (untracked)
  
        while (!read_cached_dir(&cdir)) {
                /* check how the file or directory should be treated */
 -              state = treat_path(dir, untracked, &cdir, &path,
 +              state = treat_path(dir, untracked, &cdir, istate, &path,
                                   baselen, pathspec);
  
                if (state > dir_state)
                        dir_state = state;
  
                /* recurse into subdir if instructed by treat_path */
-               if (state == path_recurse) {
+               if ((state == path_recurse) ||
+                       ((state == path_untracked) &&
+                        (dir->flags & DIR_SHOW_IGNORED_TOO) &&
 -                       (get_dtype(cdir.de, path.buf, path.len) == DT_DIR))) {
++                       (get_dtype(cdir.de, istate, path.buf, path.len) == DT_DIR))) {
                        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,
 +                              read_directory_recursive(dir, istate, path.buf,
                                                         path.len, ud,
                                                         check_only, pathspec);
                        if (subdir_state > dir_state)
                switch (state) {
                case path_excluded:
                        if (dir->flags & DIR_SHOW_IGNORED)
 -                              dir_add_name(dir, path.buf, path.len);
 +                              dir_add_name(dir, istate, path.buf, path.len);
                        else if ((dir->flags & DIR_SHOW_IGNORED_TOO) ||
                                ((dir->flags & DIR_COLLECT_IGNORED) &&
                                exclude_matches_pathspec(path.buf, path.len,
                                                         pathspec)))
 -                              dir_add_ignored(dir, path.buf, path.len);
 +                              dir_add_ignored(dir, istate, path.buf, path.len);
                        break;
  
                case path_untracked:
                        if (dir->flags & DIR_SHOW_IGNORED)
                                break;
 -                      dir_add_name(dir, path.buf, path.len);
 +                      dir_add_name(dir, istate, path.buf, path.len);
                        if (cdir.fdir)
                                add_untracked(untracked, path.buf + baselen);
                        break;
        return dir_state;
  }
  
static int cmp_name(const void *p1, const void *p2)
int cmp_dir_entry(const void *p1, const void *p2)
  {
        const struct dir_entry *e1 = *(const struct dir_entry **)p1;
        const struct dir_entry *e2 = *(const struct dir_entry **)p2;
        return name_compare(e1->name, e1->len, e2->name, e2->len);
  }
  
+ /* check if *out lexically strictly contains *in */
+ int check_dir_entry_contains(const struct dir_entry *out, const struct dir_entry *in)
+ {
+       return (out->len < in->len) &&
+               (out->name[out->len - 1] == '/') &&
+               !memcmp(out->name, in->name, out->len);
+ }
  static int treat_leading_path(struct dir_struct *dir,
 +                            struct index_state *istate,
                              const char *path, int len,
                              const struct pathspec *pathspec)
  {
                        break;
                if (simplify_away(sb.buf, sb.len, pathspec))
                        break;
 -              if (treat_one_path(dir, NULL, &sb, baselen, pathspec,
 +              if (treat_one_path(dir, NULL, istate, &sb, baselen, pathspec,
                                   DT_DIR, NULL) == path_none)
                        break; /* do not recurse into it */
                if (len <= baselen) {
@@@ -2073,8 -2017,8 +2084,8 @@@ static struct untracked_cache_dir *vali
        return root;
  }
  
 -int read_directory(struct dir_struct *dir, const char *path,
 -                 int len, const struct pathspec *pathspec)
 +int read_directory(struct dir_struct *dir, struct index_state *istate,
 +                 const char *path, int len, const struct pathspec *pathspec)
  {
        struct untracked_cache_dir *untracked;
  
                 * e.g. prep_exclude()
                 */
                dir->untracked = NULL;
 -      if (!len || treat_leading_path(dir, path, len, pathspec))
 -              read_directory_recursive(dir, path, len, untracked, 0, pathspec);
 +      if (!len || treat_leading_path(dir, istate, path, len, pathspec))
 +              read_directory_recursive(dir, istate, path, len, untracked, 0, pathspec);
-       QSORT(dir->entries, dir->nr, cmp_name);
-       QSORT(dir->ignored, dir->ignored_nr, cmp_name);
+       QSORT(dir->entries, dir->nr, cmp_dir_entry);
+       QSORT(dir->ignored, dir->ignored_nr, cmp_dir_entry);
+       /*
+        * If DIR_SHOW_IGNORED_TOO is set, read_directory_recursive() will
+        * also pick up untracked contents of untracked dirs; by default
+        * we discard these, but given DIR_KEEP_UNTRACKED_CONTENTS we do not.
+        */
+       if ((dir->flags & DIR_SHOW_IGNORED_TOO) &&
+                    !(dir->flags & DIR_KEEP_UNTRACKED_CONTENTS)) {
+               int i, j;
+               /* remove from dir->entries untracked contents of untracked dirs */
+               for (i = j = 0; j < dir->nr; j++) {
+                       if (i &&
+                           check_dir_entry_contains(dir->entries[i - 1], dir->entries[j])) {
+                               free(dir->entries[j]);
+                               dir->entries[j] = NULL;
+                       } else {
+                               dir->entries[i++] = dir->entries[j];
+                       }
+               }
+               dir->nr = i;
+       }
        if (dir->untracked) {
                static struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS);
                trace_printf_key(&trace_untracked_stats,
                                 dir->untracked->gitignore_invalidated,
                                 dir->untracked->dir_invalidated,
                                 dir->untracked->dir_opened);
 -              if (dir->untracked == the_index.untracked &&
 +              if (dir->untracked == istate->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) {
 +                      istate->cache_changed |= UNTRACKED_CHANGED;
 +              if (dir->untracked != istate->untracked) {
                        free(dir->untracked);
                        dir->untracked = NULL;
                }
@@@ -2795,33 -2763,23 +2830,33 @@@ void untracked_cache_add_to_index(struc
  /* Update gitfile and core.worktree setting to connect work tree and git dir */
  void connect_work_tree_and_git_dir(const char *work_tree_, const char *git_dir_)
  {
 -      struct strbuf file_name = STRBUF_INIT;
 +      struct strbuf gitfile_sb = STRBUF_INIT;
 +      struct strbuf cfg_sb = STRBUF_INIT;
        struct strbuf rel_path = STRBUF_INIT;
 -      char *git_dir = real_pathdup(git_dir_, 1);
 -      char *work_tree = real_pathdup(work_tree_, 1);
 +      char *git_dir, *work_tree;
  
 -      /* Update gitfile */
 -      strbuf_addf(&file_name, "%s/.git", work_tree);
 -      write_file(file_name.buf, "gitdir: %s",
 -                 relative_path(git_dir, work_tree, &rel_path));
 +      /* Prepare .git file */
 +      strbuf_addf(&gitfile_sb, "%s/.git", work_tree_);
 +      if (safe_create_leading_directories_const(gitfile_sb.buf))
 +              die(_("could not create directories for %s"), gitfile_sb.buf);
 +
 +      /* Prepare config file */
 +      strbuf_addf(&cfg_sb, "%s/config", git_dir_);
 +      if (safe_create_leading_directories_const(cfg_sb.buf))
 +              die(_("could not create directories for %s"), cfg_sb.buf);
  
 +      git_dir = real_pathdup(git_dir_, 1);
 +      work_tree = real_pathdup(work_tree_, 1);
 +
 +      /* Write .git file */
 +      write_file(gitfile_sb.buf, "gitdir: %s",
 +                 relative_path(git_dir, work_tree, &rel_path));
        /* Update core.worktree setting */
 -      strbuf_reset(&file_name);
 -      strbuf_addf(&file_name, "%s/config", git_dir);
 -      git_config_set_in_file(file_name.buf, "core.worktree",
 +      git_config_set_in_file(cfg_sb.buf, "core.worktree",
                               relative_path(work_tree, git_dir, &rel_path));
  
 -      strbuf_release(&file_name);
 +      strbuf_release(&gitfile_sb);
 +      strbuf_release(&cfg_sb);
        strbuf_release(&rel_path);
        free(work_tree);
        free(git_dir);
diff --combined dir.h
index 17d110693d94e453bd7786dd7f6eaa273313f952,edb5fda586444762e50397409815962e65f22415..a89c13e27a4fb55883f811a2c82fb465bb14988d
--- 1/dir.h
--- 2/dir.h
+++ b/dir.h
@@@ -151,7 -151,8 +151,8 @@@ struct dir_struct 
                DIR_NO_GITLINKS = 1<<3,
                DIR_COLLECT_IGNORED = 1<<4,
                DIR_SHOW_IGNORED_TOO = 1<<5,
-               DIR_COLLECT_KILLED_ONLY = 1<<6
+               DIR_COLLECT_KILLED_ONLY = 1<<6,
+               DIR_KEEP_UNTRACKED_CONTENTS = 1<<7
        } flags;
        struct dir_entry **entries;
        struct dir_entry **ignored;
@@@ -214,20 -215,12 +215,20 @@@ extern int match_pathspec(const struct 
  extern int report_path_error(const char *ps_matched, const struct pathspec *pathspec, const char *prefix);
  extern int within_depth(const char *name, int namelen, int depth, int max_depth);
  
 -extern int fill_directory(struct dir_struct *dir, const struct pathspec *pathspec);
 -extern int read_directory(struct dir_struct *, const char *path, int len, const struct pathspec *pathspec);
 +extern int fill_directory(struct dir_struct *dir,
 +                        struct index_state *istate,
 +                        const struct pathspec *pathspec);
 +extern int read_directory(struct dir_struct *, struct index_state *istate,
 +                        const char *path, int len,
 +                        const struct pathspec *pathspec);
  
 -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);
 +extern int is_excluded_from_list(const char *pathname, int pathlen,
 +                               const char *basename, int *dtype,
 +                               struct exclude_list *el,
 +                               struct index_state *istate);
 +struct dir_entry *dir_add_ignored(struct dir_struct *dir,
 +                                struct index_state *istate,
 +                                const char *pathname, int len);
  
  /*
   * these implement the matching logic for dir.c:excluded_from_list and
@@@ -240,17 -233,14 +241,17 @@@ extern int match_pathname(const char *
                          const char *, int, int, unsigned);
  
  extern struct exclude *last_exclude_matching(struct dir_struct *dir,
 +                                           struct index_state *istate,
                                             const char *name, int *dtype);
  
 -extern int is_excluded(struct dir_struct *dir, const char *name, int *dtype);
 +extern int is_excluded(struct dir_struct *dir,
 +                     struct index_state *istate,
 +                     const char *name, int *dtype);
  
  extern struct exclude_list *add_exclude_list(struct dir_struct *dir,
                                             int group_type, const char *src);
  extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
 -                                        struct exclude_list *el, int check_index);
 +                                        struct exclude_list *el, struct  index_state *istate);
  extern void add_excludes_from_file(struct dir_struct *, const char *fname);
  extern void parse_exclude_pattern(const char **string, int *patternlen, unsigned *flags, int *nowildcardlen);
  extern void add_exclude(const char *string, const char *base,
@@@ -337,6 -327,9 +338,9 @@@ static inline int dir_path_match(const 
                              has_trailing_dir);
  }
  
+ int cmp_dir_entry(const void *p1, const void *p2);
+ int check_dir_entry_contains(const struct dir_entry *out, const struct dir_entry *in);
  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 *);