Merge branch 'jc/noent-notdir'
authorJunio C Hamano <gitster@pobox.com>
Tue, 13 Jun 2017 20:47:06 +0000 (13:47 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 13 Jun 2017 20:47:07 +0000 (13:47 -0700)
Our code often opens a path to an optional file, to work on its
contents when we can successfully open it. We can ignore a failure
to open if such an optional file does not exist, but we do want to
report a failure in opening for other reasons (e.g. we got an I/O
error, or the file is there, but we lack the permission to open).

The exact errors we need to ignore are ENOENT (obviously) and
ENOTDIR (less obvious). Instead of repeating comparison of errno
with these two constants, introduce a helper function to do so.

* jc/noent-notdir:
treewide: use is_missing_file_error() where ENOENT and ENOTDIR are checked
compat-util: is_missing_file_error()

1  2 
apply.c
builtin/rm.c
builtin/update-index.c
diff-lib.c
dir.c
git-compat-util.h
setup.c
sha1_name.c
wrapper.c
diff --combined apply.c
index c49cef0637355d33a7a1636fb283210784f14e02,59bb3497dea1ec74e90846b120277cdf5407e6fa..854faa67795bcd356b33420f46260f208c2ec047
+++ b/apply.c
@@@ -2046,7 -2046,7 +2046,7 @@@ static void prefix_one(struct apply_sta
        char *old_name = *name;
        if (!old_name)
                return;
 -      *name = xstrdup(prefix_filename(state->prefix, state->prefix_length, *name));
 +      *name = prefix_filename(state->prefix, *name);
        free(old_name);
  }
  
@@@ -3741,7 -3741,7 +3741,7 @@@ static int check_to_create(struct apply
                        return 0;
  
                return EXISTS_IN_WORKTREE;
-       } else if ((errno != ENOENT) && (errno != ENOTDIR)) {
+       } else if (!is_missing_file_error(errno)) {
                return error_errno("%s", new_name);
        }
        return 0;
@@@ -4091,181 -4091,181 +4091,181 @@@ static int build_fake_ancestor(struct a
        res = write_locked_index(&result, &lock, COMMIT_LOCK);
        discard_index(&result);
  
 -       if (res)
 -               return error(_("could not write temporary index to %s"),
 -                            state->fake_ancestor);
 +      if (res)
 +              return error(_("could not write temporary index to %s"),
 +                           state->fake_ancestor);
  
 -       return 0;
 - }
 +      return 0;
 +}
  
 - static void stat_patch_list(struct apply_state *state, struct patch *patch)
 - {
 -       int files, adds, dels;
 +static void stat_patch_list(struct apply_state *state, struct patch *patch)
 +{
 +      int files, adds, dels;
  
 -       for (files = adds = dels = 0 ; patch ; patch = patch->next) {
 -               files++;
 -               adds += patch->lines_added;
 -               dels += patch->lines_deleted;
 -               show_stats(state, patch);
 -       }
 +      for (files = adds = dels = 0 ; patch ; patch = patch->next) {
 +              files++;
 +              adds += patch->lines_added;
 +              dels += patch->lines_deleted;
 +              show_stats(state, patch);
 +      }
  
 -       print_stat_summary(stdout, files, adds, dels);
 - }
 +      print_stat_summary(stdout, files, adds, dels);
 +}
  
 - static void numstat_patch_list(struct apply_state *state,
 -                              struct patch *patch)
 - {
 -       for ( ; patch; patch = patch->next) {
 -               const char *name;
 -               name = patch->new_name ? patch->new_name : patch->old_name;
 -               if (patch->is_binary)
 -                       printf("-\t-\t");
 -               else
 -                       printf("%d\t%d\t", patch->lines_added, patch->lines_deleted);
 -               write_name_quoted(name, stdout, state->line_termination);
 -       }
 - }
 -
 - static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name)
 - {
 -       if (mode)
 -               printf(" %s mode %06o %s\n", newdelete, mode, name);
 -       else
 -               printf(" %s %s\n", newdelete, name);
 - }
 -
 - static void show_mode_change(struct patch *p, int show_name)
 - {
 -       if (p->old_mode && p->new_mode && p->old_mode != p->new_mode) {
 -               if (show_name)
 -                       printf(" mode change %06o => %06o %s\n",
 -                              p->old_mode, p->new_mode, p->new_name);
 -               else
 -                       printf(" mode change %06o => %06o\n",
 -                              p->old_mode, p->new_mode);
 -       }
 - }
 -
 - static void show_rename_copy(struct patch *p)
 - {
 -       const char *renamecopy = p->is_rename ? "rename" : "copy";
 -       const char *old, *new;
 -
 -       /* Find common prefix */
 -       old = p->old_name;
 -       new = p->new_name;
 -       while (1) {
 -               const char *slash_old, *slash_new;
 -               slash_old = strchr(old, '/');
 -               slash_new = strchr(new, '/');
 -               if (!slash_old ||
 -                   !slash_new ||
 -                   slash_old - old != slash_new - new ||
 -                   memcmp(old, new, slash_new - new))
 -                       break;
 -               old = slash_old + 1;
 -               new = slash_new + 1;
 -       }
 -       /* p->old_name thru old is the common prefix, and old and new
 -        * through the end of names are renames
 -        */
 -       if (old != p->old_name)
 -               printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy,
 -                      (int)(old - p->old_name), p->old_name,
 -                      old, new, p->score);
 -       else
 -               printf(" %s %s => %s (%d%%)\n", renamecopy,
 -                      p->old_name, p->new_name, p->score);
 -       show_mode_change(p, 0);
 - }
 -
 - static void summary_patch_list(struct patch *patch)
 - {
 -       struct patch *p;
 -
 -       for (p = patch; p; p = p->next) {
 -               if (p->is_new)
 -                       show_file_mode_name("create", p->new_mode, p->new_name);
 -               else if (p->is_delete)
 -                       show_file_mode_name("delete", p->old_mode, p->old_name);
 -               else {
 -                       if (p->is_rename || p->is_copy)
 -                               show_rename_copy(p);
 -                       else {
 -                               if (p->score) {
 -                                       printf(" rewrite %s (%d%%)\n",
 -                                              p->new_name, p->score);
 -                                       show_mode_change(p, 0);
 -                               }
 -                               else
 -                                       show_mode_change(p, 1);
 -                       }
 -               }
 -       }
 - }
 -
 - static void patch_stats(struct apply_state *state, struct patch *patch)
 - {
 -       int lines = patch->lines_added + patch->lines_deleted;
 -
 -       if (lines > state->max_change)
 -               state->max_change = lines;
 -       if (patch->old_name) {
 -               int len = quote_c_style(patch->old_name, NULL, NULL, 0);
 -               if (!len)
 -                       len = strlen(patch->old_name);
 -               if (len > state->max_len)
 -                       state->max_len = len;
 -       }
 -       if (patch->new_name) {
 -               int len = quote_c_style(patch->new_name, NULL, NULL, 0);
 -               if (!len)
 -                       len = strlen(patch->new_name);
 -               if (len > state->max_len)
 -                       state->max_len = len;
 -       }
 - }
 -
 - static int remove_file(struct apply_state *state, struct patch *patch, int rmdir_empty)
 - {
 -       if (state->update_index) {
 -               if (remove_file_from_cache(patch->old_name) < 0)
 -                       return error(_("unable to remove %s from index"), patch->old_name);
 -       }
 -       if (!state->cached) {
 -               if (!remove_or_warn(patch->old_mode, patch->old_name) && rmdir_empty) {
 -                       remove_path(patch->old_name);
 -               }
 -       }
 -       return 0;
 - }
 -
 - static int add_index_file(struct apply_state *state,
 -                         const char *path,
 -                         unsigned mode,
 -                         void *buf,
 -                         unsigned long size)
 - {
 -       struct stat st;
 -       struct cache_entry *ce;
 -       int namelen = strlen(path);
 -       unsigned ce_size = cache_entry_size(namelen);
 -
 -       if (!state->update_index)
 -               return 0;
 -
 -       ce = xcalloc(1, ce_size);
 -       memcpy(ce->name, path, namelen);
 -       ce->ce_mode = create_ce_mode(mode);
 -       ce->ce_flags = create_ce_flags(0);
 -       ce->ce_namelen = namelen;
 -       if (S_ISGITLINK(mode)) {
 -               const char *s;
 -
 -               if (!skip_prefix(buf, "Subproject commit ", &s) ||
 -                   get_oid_hex(s, &ce->oid)) {
 +static void numstat_patch_list(struct apply_state *state,
 +                             struct patch *patch)
 +{
 +      for ( ; patch; patch = patch->next) {
 +              const char *name;
 +              name = patch->new_name ? patch->new_name : patch->old_name;
 +              if (patch->is_binary)
 +                      printf("-\t-\t");
 +              else
 +                      printf("%d\t%d\t", patch->lines_added, patch->lines_deleted);
 +              write_name_quoted(name, stdout, state->line_termination);
 +      }
 +}
 +
 +static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name)
 +{
 +      if (mode)
 +              printf(" %s mode %06o %s\n", newdelete, mode, name);
 +      else
 +              printf(" %s %s\n", newdelete, name);
 +}
 +
 +static void show_mode_change(struct patch *p, int show_name)
 +{
 +      if (p->old_mode && p->new_mode && p->old_mode != p->new_mode) {
 +              if (show_name)
 +                      printf(" mode change %06o => %06o %s\n",
 +                             p->old_mode, p->new_mode, p->new_name);
 +              else
 +                      printf(" mode change %06o => %06o\n",
 +                             p->old_mode, p->new_mode);
 +      }
 +}
 +
 +static void show_rename_copy(struct patch *p)
 +{
 +      const char *renamecopy = p->is_rename ? "rename" : "copy";
 +      const char *old, *new;
 +
 +      /* Find common prefix */
 +      old = p->old_name;
 +      new = p->new_name;
 +      while (1) {
 +              const char *slash_old, *slash_new;
 +              slash_old = strchr(old, '/');
 +              slash_new = strchr(new, '/');
 +              if (!slash_old ||
 +                  !slash_new ||
 +                  slash_old - old != slash_new - new ||
 +                  memcmp(old, new, slash_new - new))
 +                      break;
 +              old = slash_old + 1;
 +              new = slash_new + 1;
 +      }
 +      /* p->old_name thru old is the common prefix, and old and new
 +       * through the end of names are renames
 +       */
 +      if (old != p->old_name)
 +              printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy,
 +                     (int)(old - p->old_name), p->old_name,
 +                     old, new, p->score);
 +      else
 +              printf(" %s %s => %s (%d%%)\n", renamecopy,
 +                     p->old_name, p->new_name, p->score);
 +      show_mode_change(p, 0);
 +}
 +
 +static void summary_patch_list(struct patch *patch)
 +{
 +      struct patch *p;
 +
 +      for (p = patch; p; p = p->next) {
 +              if (p->is_new)
 +                      show_file_mode_name("create", p->new_mode, p->new_name);
 +              else if (p->is_delete)
 +                      show_file_mode_name("delete", p->old_mode, p->old_name);
 +              else {
 +                      if (p->is_rename || p->is_copy)
 +                              show_rename_copy(p);
 +                      else {
 +                              if (p->score) {
 +                                      printf(" rewrite %s (%d%%)\n",
 +                                             p->new_name, p->score);
 +                                      show_mode_change(p, 0);
 +                              }
 +                              else
 +                                      show_mode_change(p, 1);
 +                      }
 +              }
 +      }
 +}
 +
 +static void patch_stats(struct apply_state *state, struct patch *patch)
 +{
 +      int lines = patch->lines_added + patch->lines_deleted;
 +
 +      if (lines > state->max_change)
 +              state->max_change = lines;
 +      if (patch->old_name) {
 +              int len = quote_c_style(patch->old_name, NULL, NULL, 0);
 +              if (!len)
 +                      len = strlen(patch->old_name);
 +              if (len > state->max_len)
 +                      state->max_len = len;
 +      }
 +      if (patch->new_name) {
 +              int len = quote_c_style(patch->new_name, NULL, NULL, 0);
 +              if (!len)
 +                      len = strlen(patch->new_name);
 +              if (len > state->max_len)
 +                      state->max_len = len;
 +      }
 +}
 +
 +static int remove_file(struct apply_state *state, struct patch *patch, int rmdir_empty)
 +{
 +      if (state->update_index) {
 +              if (remove_file_from_cache(patch->old_name) < 0)
 +                      return error(_("unable to remove %s from index"), patch->old_name);
 +      }
 +      if (!state->cached) {
 +              if (!remove_or_warn(patch->old_mode, patch->old_name) && rmdir_empty) {
 +                      remove_path(patch->old_name);
 +              }
 +      }
 +      return 0;
 +}
 +
 +static int add_index_file(struct apply_state *state,
 +                        const char *path,
 +                        unsigned mode,
 +                        void *buf,
 +                        unsigned long size)
 +{
 +      struct stat st;
 +      struct cache_entry *ce;
 +      int namelen = strlen(path);
 +      unsigned ce_size = cache_entry_size(namelen);
 +
 +      if (!state->update_index)
 +              return 0;
 +
 +      ce = xcalloc(1, ce_size);
 +      memcpy(ce->name, path, namelen);
 +      ce->ce_mode = create_ce_mode(mode);
 +      ce->ce_flags = create_ce_flags(0);
 +      ce->ce_namelen = namelen;
 +      if (S_ISGITLINK(mode)) {
 +              const char *s;
 +
 +              if (!skip_prefix(buf, "Subproject commit ", &s) ||
 +                  get_oid_hex(s, &ce->oid)) {
                        free(ce);
 -                      return error(_("corrupt patch for submodule %s"), path);
 +                     return error(_("corrupt patch for submodule %s"), path);
                }
        } else {
                if (!state->cached) {
@@@ -4805,7 -4805,6 +4805,7 @@@ int apply_all_patches(struct apply_stat
  
        for (i = 0; i < argc; i++) {
                const char *arg = argv[i];
 +              char *to_free = NULL;
                int fd;
  
                if (!strcmp(arg, "-")) {
                        errs |= res;
                        read_stdin = 0;
                        continue;
 -              } else if (0 < state->prefix_length)
 -                      arg = prefix_filename(state->prefix,
 -                                            state->prefix_length,
 -                                            arg);
 +              } else
 +                      arg = to_free = prefix_filename(state->prefix, arg);
  
                fd = open(arg, O_RDONLY);
                if (fd < 0) {
                        error(_("can't open patch '%s': %s"), arg, strerror(errno));
                        res = -128;
 +                      free(to_free);
                        goto end;
                }
                read_stdin = 0;
                set_default_whitespace_mode(state);
                res = apply_patch(state, fd, arg, options);
                close(fd);
 +              free(to_free);
                if (res < 0)
                        goto end;
                errs |= res;
diff --combined builtin/rm.c
index 7c323d01235bf8bbb341004c843761dad99590d2,30c4332c68e24032afa9df49a894d01310a08d68..b39f10fcb64f047090967dbf8b31120d2beb1b67
@@@ -129,7 -129,7 +129,7 @@@ static int check_local_mod(struct objec
                ce = active_cache[pos];
  
                if (lstat(ce->name, &st) < 0) {
-                       if (errno != ENOENT && errno != ENOTDIR)
+                       if (!is_missing_file_error(errno))
                                warning_errno(_("failed to stat '%s'"), ce->name);
                        /* It already vanished from the working tree */
                        continue;
@@@ -271,7 -271,8 +271,7 @@@ int cmd_rm(int argc, const char **argv
                die(_("index file corrupt"));
  
        parse_pathspec(&pathspec, 0,
 -                     PATHSPEC_PREFER_CWD |
 -                     PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
 +                     PATHSPEC_PREFER_CWD,
                       prefix, argv);
        refresh_index(&the_index, REFRESH_QUIET, &pathspec, NULL, NULL);
  
diff --combined builtin/update-index.c
index ebfc09faa0d604218af8f5815af5e5fee5915158,4e9402984aff394adf9b0853343e5f906fca238c..f99b1e5790b9b6aeafaa88f438a8a50f1538c2fe
@@@ -125,16 -125,12 +125,16 @@@ static int test_if_untracked_cache_is_s
        struct stat st;
        struct stat_data base;
        int fd, ret = 0;
 +      char *cwd;
  
        strbuf_addstr(&mtime_dir, "mtime-test-XXXXXX");
        if (!mkdtemp(mtime_dir.buf))
                die_errno("Could not make temporary directory");
  
 -      fprintf(stderr, _("Testing mtime in '%s' "), xgetcwd());
 +      cwd = xgetcwd();
 +      fprintf(stderr, _("Testing mtime in '%s' "), cwd);
 +      free(cwd);
 +
        atexit(remove_test_directory);
        xstat_mtime_dir(&st);
        fill_stat_data(&base, &st);
@@@ -257,7 -253,7 +257,7 @@@ static int remove_one_path(const char *
   */
  static int process_lstat_error(const char *path, int err)
  {
-       if (err == ENOENT || err == ENOTDIR)
+       if (is_missing_file_error(err))
                return remove_one_path(path);
        return error("lstat(\"%s\"): %s", path, strerror(err));
  }
@@@ -1103,20 -1099,17 +1103,20 @@@ int cmd_update_index(int argc, const ch
        }
  
        if (split_index > 0) {
 -              init_split_index(&the_index);
 -              the_index.cache_changed |= SPLIT_INDEX_ORDERED;
 -      } else if (!split_index && the_index.split_index) {
 -              /*
 -               * can't discard_split_index(&the_index); because that
 -               * will destroy split_index->base->cache[], which may
 -               * be shared with the_index.cache[]. So yeah we're
 -               * leaking a bit here.
 -               */
 -              the_index.split_index = NULL;
 -              the_index.cache_changed |= SOMETHING_CHANGED;
 +              if (git_config_get_split_index() == 0)
 +                      warning(_("core.splitIndex is set to false; "
 +                                "remove or change it, if you really want to "
 +                                "enable split index"));
 +              if (the_index.split_index)
 +                      the_index.cache_changed |= SPLIT_INDEX_ORDERED;
 +              else
 +                      add_split_index(&the_index);
 +      } else if (!split_index) {
 +              if (git_config_get_split_index() == 1)
 +                      warning(_("core.splitIndex is set to true; "
 +                                "remove or change it, if you really want to "
 +                                "disable split index"));
 +              remove_split_index(&the_index);
        }
  
        switch (untracked_cache) {
diff --combined diff-lib.c
index 2982bf055acb6049a8cd6007244d60d985b7e619,88fc71e89e191ae2defcb2ca74e87765126a45d7..76c8f185cd032b92e010b95e4d92e3b08a6ab18a
@@@ -29,7 -29,7 +29,7 @@@
  static int check_removed(const struct cache_entry *ce, struct stat *st)
  {
        if (lstat(ce->name, st) < 0) {
-               if (errno != ENOENT && errno != ENOTDIR)
+               if (!is_missing_file_error(errno))
                        return -1;
                return 1;
        }
@@@ -478,7 -478,7 +478,7 @@@ static int oneway_diff(const struct cac
  }
  
  static int diff_cache(struct rev_info *revs,
 -                    const unsigned char *tree_sha1,
 +                    const struct object_id *tree_oid,
                      const char *tree_name,
                      int cached)
  {
        struct tree_desc t;
        struct unpack_trees_options opts;
  
 -      tree = parse_tree_indirect(tree_sha1);
 +      tree = parse_tree_indirect(tree_oid);
        if (!tree)
                return error("bad tree object %s",
 -                           tree_name ? tree_name : sha1_to_hex(tree_sha1));
 +                           tree_name ? tree_name : oid_to_hex(tree_oid));
        memset(&opts, 0, sizeof(opts));
        opts.head_idx = 1;
        opts.index_only = cached;
@@@ -512,7 -512,7 +512,7 @@@ int run_diff_index(struct rev_info *rev
        struct object_array_entry *ent;
  
        ent = revs->pending.objects;
 -      if (diff_cache(revs, ent->item->oid.hash, ent->name, cached))
 +      if (diff_cache(revs, &ent->item->oid, ent->name, cached))
                exit(128);
  
        diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/");
        return 0;
  }
  
 -int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
 +int do_diff_cache(const struct object_id *tree_oid, struct diff_options *opt)
  {
        struct rev_info revs;
  
        copy_pathspec(&revs.prune_data, &opt->pathspec);
        revs.diffopt = *opt;
  
 -      if (diff_cache(&revs, tree_sha1, NULL, 1))
 +      if (diff_cache(&revs, tree_oid, NULL, 1))
                exit(128);
        return 0;
  }
diff --combined dir.c
index 9efcf1eab689bd0e637ea1b961b261eb97862b04,98efe9d1c5d706bdaf12da508783006a4c882864..70f2de38f23ce5f9e4c107cfdb0f7901dfd22b84
--- 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, 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) {
@@@ -2084,8 -2006,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);
 -      QSORT(dir->entries, dir->nr, cmp_name);
 -      QSORT(dir->ignored, dir->ignored_nr, cmp_name);
 +      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_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;
                }
@@@ -2337,7 -2235,7 +2337,7 @@@ int remove_path(const char *name
  {
        char *slash;
  
-       if (unlink(name) && errno != ENOENT && errno != ENOTDIR)
+       if (unlink(name) && !is_missing_file_error(errno))
                return -1;
  
        slash = strrchr(name, '/');
@@@ -2830,33 -2728,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 git-compat-util.h
index 4b7dcf21adbe7064963cf446bde7018629ea2c10,8a3c680626bf0c2d1a91eda6dbc45789370a6dc8..e83fd2eb07729561d03566cdc312636525a83ee6
@@@ -319,11 -319,6 +319,11 @@@ extern char *gitdirname(char *)
  #define PRIo32 "o"
  #endif
  
 +typedef uintmax_t timestamp_t;
 +#define PRItime PRIuMAX
 +#define parse_timestamp strtoumax
 +#define TIME_MAX UINTMAX_MAX
 +
  #ifndef PATH_SEP
  #define PATH_SEP ':'
  #endif
@@@ -450,6 -445,7 +450,6 @@@ extern void (*get_error_routine(void))(
  extern void set_warn_routine(void (*routine)(const char *warn, va_list params));
  extern void (*get_warn_routine(void))(const char *warn, va_list params);
  extern void set_die_is_recursing_routine(int (*routine)(void));
 -extern void set_error_handle(FILE *);
  
  extern int starts_with(const char *str, const char *prefix);
  
@@@ -620,7 -616,7 +620,7 @@@ extern int git_lstat(const char *, stru
  #endif
  
  #define DEFAULT_PACKED_GIT_LIMIT \
 -      ((1024L * 1024L) * (size_t)(sizeof(void*) >= 8 ? 8192 : 256))
 +      ((1024L * 1024L) * (size_t)(sizeof(void*) >= 8 ? (32 * 1024L * 1024L) : 256))
  
  #ifdef NO_PREAD
  #define pread git_pread
@@@ -888,12 -884,6 +888,12 @@@ static inline size_t xsize_t(off_t len
  __attribute__((format (printf, 3, 4)))
  extern int xsnprintf(char *dst, size_t max, const char *fmt, ...);
  
 +#ifndef HOST_NAME_MAX
 +#define HOST_NAME_MAX 256
 +#endif
 +
 +extern int xgethostname(char *buf, size_t len);
 +
  /* in ctype.c, for kwset users */
  extern const unsigned char tolower_trans_tbl[256];
  
@@@ -1068,15 -1058,6 +1068,15 @@@ static inline int regexec_buf(const reg
  #define HAVE_VARIADIC_MACROS 1
  #endif
  
 +#ifdef HAVE_VARIADIC_MACROS
 +__attribute__((format (printf, 3, 4))) NORETURN
 +void BUG_fl(const char *file, int line, const char *fmt, ...);
 +#define BUG(...) BUG_fl(__FILE__, __LINE__, __VA_ARGS__)
 +#else
 +__attribute__((format (printf, 1, 2))) NORETURN
 +void BUG(const char *fmt, ...);
 +#endif
 +
  /*
   * Preserves errno, prints a message, but gives no warning for ENOENT.
   * Returns 0 on success, which includes trying to unlink an object that does
@@@ -1134,6 -1115,21 +1134,21 @@@ struct tm *git_gmtime_r(const time_t *
  #define getc_unlocked(fh) getc(fh)
  #endif
  
+ /*
+  * Our code often opens a path to an optional file, to work on its
+  * contents when we can successfully open it.  We can ignore a failure
+  * to open if such an optional file does not exist, but we do want to
+  * report a failure in opening for other reasons (e.g. we got an I/O
+  * error, or the file is there, but we lack the permission to open).
+  *
+  * Call this function after seeing an error from open() or fopen() to
+  * see if the errno indicates a missing file that we can safely ignore.
+  */
+ static inline int is_missing_file_error(int errno_)
+ {
+       return (errno_ == ENOENT || errno_ == ENOTDIR);
+ }
  extern int cmd_main(int, const char **);
  
  #endif
diff --combined setup.c
index e3f7699a902aed20a83820067cf913df2f3750a9,bb6a2c1bebf8081f62f65fcbc221e20e5fa4aa93..ba6e855178847a37044eacceae0ebcb17d8e990b
+++ b/setup.c
@@@ -135,7 -135,6 +135,7 @@@ int path_inside_repo(const char *prefix
  int check_filename(const char *prefix, const char *arg)
  {
        const char *name;
 +      char *to_free = NULL;
        struct stat st;
  
        if (starts_with(arg, ":/")) {
                        return 1;
                name = arg + 2;
        } else if (prefix)
 -              name = prefix_filename(prefix, strlen(prefix), arg);
 +              name = to_free = prefix_filename(prefix, arg);
        else
                name = arg;
 -      if (!lstat(name, &st))
 +      if (!lstat(name, &st)) {
 +              free(to_free);
                return 1; /* file exists */
 -      if (is_missing_file_error(errno))
 +      }
-       if (errno == ENOENT || errno == ENOTDIR) {
++      if (is_missing_file_error(errno)) {
 +              free(to_free);
                return 0; /* file does not exist */
 +      }
        die_errno("failed to stat '%s'", arg);
  }
  
@@@ -536,7 -531,6 +536,7 @@@ const char *read_gitfile_gently(const c
        ssize_t len;
  
        if (stat(path, &st)) {
 +              /* NEEDSWORK: discern between ENOENT vs other errors */
                error_code = READ_GITFILE_ERR_STAT_FAILED;
                goto cleanup_return;
        }
@@@ -703,16 -697,11 +703,16 @@@ static const char *setup_discovered_git
  
        /* --work-tree is set without --git-dir; use discovered one */
        if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) {
 +              char *to_free = NULL;
 +              const char *ret;
 +
                if (offset != cwd->len && !is_absolute_path(gitdir))
 -                      gitdir = real_pathdup(gitdir, 1);
 +                      gitdir = to_free = real_pathdup(gitdir, 1);
                if (chdir(cwd->buf))
                        die_errno("Could not come back to cwd");
 -              return setup_explicit_git_dir(gitdir, cwd, nongit_ok);
 +              ret = setup_explicit_git_dir(gitdir, cwd, nongit_ok);
 +              free(to_free);
 +              return ret;
        }
  
        /* #16.2, #17.2, #20.2, #21.2, #24, #25, #28, #29 (see t1510) */
        if (offset == cwd->len)
                return NULL;
  
 -      /* Make "offset" point to past the '/', and add a '/' at the end */
 -      offset++;
 +      /* Make "offset" point past the '/' (already the case for root dirs) */
 +      if (offset != offset_1st_component(cwd->buf))
 +              offset++;
 +      /* Add a '/' at the end */
        strbuf_addch(cwd, '/');
        return cwd->buf + offset;
  }
@@@ -753,7 -740,7 +753,7 @@@ static const char *setup_bare_git_dir(s
  
        /* --work-tree is set without --git-dir; use discovered one */
        if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) {
 -              const char *gitdir;
 +              static const char *gitdir;
  
                gitdir = offset == cwd->len ? "." : xmemdupz(cwd->buf, offset);
                if (chdir(cwd->buf))
@@@ -829,51 -816,50 +829,51 @@@ static int canonicalize_ceiling_entry(s
        }
  }
  
 +enum discovery_result {
 +      GIT_DIR_NONE = 0,
 +      GIT_DIR_EXPLICIT,
 +      GIT_DIR_DISCOVERED,
 +      GIT_DIR_BARE,
 +      /* these are errors */
 +      GIT_DIR_HIT_CEILING = -1,
 +      GIT_DIR_HIT_MOUNT_POINT = -2,
 +      GIT_DIR_INVALID_GITFILE = -3
 +};
 +
  /*
   * We cannot decide in this function whether we are in the work tree or
   * not, since the config can only be read _after_ this function was called.
 + *
 + * Also, we avoid changing any global state (such as the current working
 + * directory) to allow early callers.
 + *
 + * The directory where the search should start needs to be passed in via the
 + * `dir` parameter; upon return, the `dir` buffer will contain the path of
 + * the directory where the search ended, and `gitdir` will contain the path of
 + * the discovered .git/ directory, if any. If `gitdir` is not absolute, it
 + * is relative to `dir` (i.e. *not* necessarily the cwd).
   */
 -static const char *setup_git_directory_gently_1(int *nongit_ok)
 +static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
 +                                                        struct strbuf *gitdir,
 +                                                        int die_on_error)
  {
        const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);
        struct string_list ceiling_dirs = STRING_LIST_INIT_DUP;
 -      static struct strbuf cwd = STRBUF_INIT;
 -      const char *gitdirenv, *ret;
 -      char *gitfile;
 -      int offset, offset_parent, ceil_offset = -1;
 +      const char *gitdirenv;
 +      int ceil_offset = -1, min_offset = has_dos_drive_prefix(dir->buf) ? 3 : 1;
        dev_t current_device = 0;
        int one_filesystem = 1;
  
 -      /*
 -       * We may have read an incomplete configuration before
 -       * setting-up the git directory. If so, clear the cache so
 -       * that the next queries to the configuration reload complete
 -       * configuration (including the per-repo config file that we
 -       * ignored previously).
 -       */
 -      git_config_clear();
 -
 -      /*
 -       * Let's assume that we are in a git repository.
 -       * If it turns out later that we are somewhere else, the value will be
 -       * updated accordingly.
 -       */
 -      if (nongit_ok)
 -              *nongit_ok = 0;
 -
 -      if (strbuf_getcwd(&cwd))
 -              die_errno(_("Unable to read current working directory"));
 -      offset = cwd.len;
 -
        /*
         * If GIT_DIR is set explicitly, we're not going
         * to do any discovery, but we still do repository
         * validation.
         */
        gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
 -      if (gitdirenv)
 -              return setup_explicit_git_dir(gitdirenv, &cwd, nongit_ok);
 +      if (gitdirenv) {
 +              strbuf_addstr(gitdir, gitdirenv);
 +              return GIT_DIR_EXPLICIT;
 +      }
  
        if (env_ceiling_dirs) {
                int empty_entry_found = 0;
                string_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1);
                filter_string_list(&ceiling_dirs, 0,
                                   canonicalize_ceiling_entry, &empty_entry_found);
 -              ceil_offset = longest_ancestor_length(cwd.buf, &ceiling_dirs);
 +              ceil_offset = longest_ancestor_length(dir->buf, &ceiling_dirs);
                string_list_clear(&ceiling_dirs, 0);
        }
  
 -      if (ceil_offset < 0 && has_dos_drive_prefix(cwd.buf))
 -              ceil_offset = 1;
 +      if (ceil_offset < 0)
 +              ceil_offset = min_offset - 2;
  
        /*
 -       * Test in the following order (relative to the cwd):
 +       * Test in the following order (relative to the dir):
         * - .git (file containing "gitdir: <path>")
         * - .git/
         * - ./ (bare)
         */
        one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0);
        if (one_filesystem)
 -              current_device = get_device_or_die(".", NULL, 0);
 +              current_device = get_device_or_die(dir->buf, NULL, 0);
        for (;;) {
 -              gitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);
 -              if (gitfile)
 -                      gitdirenv = gitfile = xstrdup(gitfile);
 -              else {
 -                      if (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))
 -                              gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;
 +              int offset = dir->len, error_code = 0;
 +
 +              if (offset > min_offset)
 +                      strbuf_addch(dir, '/');
 +              strbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT);
 +              gitdirenv = read_gitfile_gently(dir->buf, die_on_error ?
 +                                              NULL : &error_code);
 +              if (!gitdirenv) {
 +                      if (die_on_error ||
 +                          error_code == READ_GITFILE_ERR_NOT_A_FILE) {
 +                              /* NEEDSWORK: fail if .git is not file nor dir */
 +                              if (is_git_directory(dir->buf))
 +                                      gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;
 +                      } else if (error_code != READ_GITFILE_ERR_STAT_FAILED)
 +                              return GIT_DIR_INVALID_GITFILE;
                }
 -
 +              strbuf_setlen(dir, offset);
                if (gitdirenv) {
 -                      ret = setup_discovered_git_dir(gitdirenv,
 -                                                     &cwd, offset,
 -                                                     nongit_ok);
 -                      free(gitfile);
 -                      return ret;
 +                      strbuf_addstr(gitdir, gitdirenv);
 +                      return GIT_DIR_DISCOVERED;
                }
 -              free(gitfile);
  
 -              if (is_git_directory("."))
 -                      return setup_bare_git_dir(&cwd, offset, nongit_ok);
 -
 -              offset_parent = offset;
 -              while (--offset_parent > ceil_offset && cwd.buf[offset_parent] != '/');
 -              if (offset_parent <= ceil_offset)
 -                      return setup_nongit(cwd.buf, nongit_ok);
 -              if (one_filesystem) {
 -                      dev_t parent_device = get_device_or_die("..", cwd.buf,
 -                                                              offset);
 -                      if (parent_device != current_device) {
 -                              if (nongit_ok) {
 -                                      if (chdir(cwd.buf))
 -                                              die_errno(_("Cannot come back to cwd"));
 -                                      *nongit_ok = 1;
 -                                      return NULL;
 -                              }
 -                              strbuf_setlen(&cwd, offset);
 -                              die(_("Not a git repository (or any parent up to mount point %s)\n"
 -                              "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set)."),
 -                                  cwd.buf);
 -                      }
 -              }
 -              if (chdir("..")) {
 -                      strbuf_setlen(&cwd, offset);
 -                      die_errno(_("Cannot change to '%s/..'"), cwd.buf);
 +              if (is_git_directory(dir->buf)) {
 +                      strbuf_addstr(gitdir, ".");
 +                      return GIT_DIR_BARE;
                }
 -              offset = offset_parent;
 +
 +              if (offset <= min_offset)
 +                      return GIT_DIR_HIT_CEILING;
 +
 +              while (--offset > ceil_offset && !is_dir_sep(dir->buf[offset]))
 +                      ; /* continue */
 +              if (offset <= ceil_offset)
 +                      return GIT_DIR_HIT_CEILING;
 +
 +              strbuf_setlen(dir, offset > min_offset ?  offset : min_offset);
 +              if (one_filesystem &&
 +                  current_device != get_device_or_die(dir->buf, NULL, offset))
 +                      return GIT_DIR_HIT_MOUNT_POINT;
 +      }
 +}
 +
 +const char *discover_git_directory(struct strbuf *gitdir)
 +{
 +      struct strbuf dir = STRBUF_INIT, err = STRBUF_INIT;
 +      size_t gitdir_offset = gitdir->len, cwd_len;
 +      struct repository_format candidate;
 +
 +      if (strbuf_getcwd(&dir))
 +              return NULL;
 +
 +      cwd_len = dir.len;
 +      if (setup_git_directory_gently_1(&dir, gitdir, 0) <= 0) {
 +              strbuf_release(&dir);
 +              return NULL;
 +      }
 +
 +      /*
 +       * The returned gitdir is relative to dir, and if dir does not reflect
 +       * the current working directory, we simply make the gitdir absolute.
 +       */
 +      if (dir.len < cwd_len && !is_absolute_path(gitdir->buf + gitdir_offset)) {
 +              /* Avoid a trailing "/." */
 +              if (!strcmp(".", gitdir->buf + gitdir_offset))
 +                      strbuf_setlen(gitdir, gitdir_offset);
 +              else
 +                      strbuf_addch(&dir, '/');
 +              strbuf_insert(gitdir, gitdir_offset, dir.buf, dir.len);
        }
 +
 +      strbuf_reset(&dir);
 +      strbuf_addf(&dir, "%s/config", gitdir->buf + gitdir_offset);
 +      read_repository_format(&candidate, dir.buf);
 +      strbuf_release(&dir);
 +
 +      if (verify_repository_format(&candidate, &err) < 0) {
 +              warning("ignoring git dir '%s': %s",
 +                      gitdir->buf + gitdir_offset, err.buf);
 +              strbuf_release(&err);
 +              return NULL;
 +      }
 +
 +      return gitdir->buf + gitdir_offset;
  }
  
  const char *setup_git_directory_gently(int *nongit_ok)
  {
 -      const char *prefix;
 +      static struct strbuf cwd = STRBUF_INIT;
 +      struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT;
 +      const char *prefix, *env_prefix;
 +
 +      /*
 +       * We may have read an incomplete configuration before
 +       * setting-up the git directory. If so, clear the cache so
 +       * that the next queries to the configuration reload complete
 +       * configuration (including the per-repo config file that we
 +       * ignored previously).
 +       */
 +      git_config_clear();
 +
 +      /*
 +       * Let's assume that we are in a git repository.
 +       * If it turns out later that we are somewhere else, the value will be
 +       * updated accordingly.
 +       */
 +      if (nongit_ok)
 +              *nongit_ok = 0;
 +
 +      if (strbuf_getcwd(&cwd))
 +              die_errno(_("Unable to read current working directory"));
 +      strbuf_addbuf(&dir, &cwd);
 +
 +      switch (setup_git_directory_gently_1(&dir, &gitdir, 1)) {
 +      case GIT_DIR_NONE:
 +              prefix = NULL;
 +              break;
 +      case GIT_DIR_EXPLICIT:
 +              prefix = setup_explicit_git_dir(gitdir.buf, &cwd, nongit_ok);
 +              break;
 +      case GIT_DIR_DISCOVERED:
 +              if (dir.len < cwd.len && chdir(dir.buf))
 +                      die(_("Cannot change to '%s'"), dir.buf);
 +              prefix = setup_discovered_git_dir(gitdir.buf, &cwd, dir.len,
 +                                                nongit_ok);
 +              break;
 +      case GIT_DIR_BARE:
 +              if (dir.len < cwd.len && chdir(dir.buf))
 +                      die(_("Cannot change to '%s'"), dir.buf);
 +              prefix = setup_bare_git_dir(&cwd, dir.len, nongit_ok);
 +              break;
 +      case GIT_DIR_HIT_CEILING:
 +              prefix = setup_nongit(cwd.buf, nongit_ok);
 +              break;
 +      case GIT_DIR_HIT_MOUNT_POINT:
 +              if (nongit_ok) {
 +                      *nongit_ok = 1;
 +                      strbuf_release(&cwd);
 +                      strbuf_release(&dir);
 +                      return NULL;
 +              }
 +              die(_("Not a git repository (or any parent up to mount point %s)\n"
 +                    "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set)."),
 +                  dir.buf);
 +      default:
 +              die("BUG: unhandled setup_git_directory_1() result");
 +      }
 +
 +      env_prefix = getenv(GIT_TOPLEVEL_PREFIX_ENVIRONMENT);
 +      if (env_prefix)
 +              prefix = env_prefix;
  
 -      prefix = setup_git_directory_gently_1(nongit_ok);
        if (prefix)
                setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);
        else
        startup_info->have_repository = !nongit_ok || !*nongit_ok;
        startup_info->prefix = prefix;
  
 +      strbuf_release(&dir);
 +      strbuf_release(&gitdir);
 +
        return prefix;
  }
  
diff --combined sha1_name.c
index e9ffe685d58366dc3802986519e3e2fd22c151a4,af7500037d743ab818906ab179fd6e37729afeba..5126853bb5bda5f291039588db96064ffbd1ee5c
  
  static int get_sha1_oneline(const char *, unsigned char *, struct commit_list *);
  
 -typedef int (*disambiguate_hint_fn)(const unsigned char *, void *);
 +typedef int (*disambiguate_hint_fn)(const struct object_id *, void *);
  
  struct disambiguate_state {
        int len; /* length of prefix in hex chars */
 -      char hex_pfx[GIT_SHA1_HEXSZ + 1];
 -      unsigned char bin_pfx[GIT_SHA1_RAWSZ];
 +      char hex_pfx[GIT_MAX_HEXSZ + 1];
 +      struct object_id bin_pfx;
  
        disambiguate_hint_fn fn;
        void *cb_data;
 -      unsigned char candidate[GIT_SHA1_RAWSZ];
 +      struct object_id candidate;
        unsigned candidate_exists:1;
        unsigned candidate_checked:1;
        unsigned candidate_ok:1;
@@@ -29,7 -29,7 +29,7 @@@
        unsigned always_call_fn:1;
  };
  
 -static void update_candidates(struct disambiguate_state *ds, const unsigned char *current)
 +static void update_candidates(struct disambiguate_state *ds, const struct object_id *current)
  {
        if (ds->always_call_fn) {
                ds->ambiguous = ds->fn(current, ds->cb_data) ? 1 : 0;
        }
        if (!ds->candidate_exists) {
                /* this is the first candidate */
 -              hashcpy(ds->candidate, current);
 +              oidcpy(&ds->candidate, current);
                ds->candidate_exists = 1;
                return;
 -      } else if (!hashcmp(ds->candidate, current)) {
 +      } else if (!oidcmp(&ds->candidate, current)) {
                /* the same as what we already have seen */
                return;
        }
        }
  
        if (!ds->candidate_checked) {
 -              ds->candidate_ok = ds->fn(ds->candidate, ds->cb_data);
 +              ds->candidate_ok = ds->fn(&ds->candidate, ds->cb_data);
                ds->disambiguate_fn_used = 1;
                ds->candidate_checked = 1;
        }
  
        if (!ds->candidate_ok) {
                /* discard the candidate; we know it does not satisfy fn */
 -              hashcpy(ds->candidate, current);
 +              oidcpy(&ds->candidate, current);
                ds->candidate_checked = 0;
                return;
        }
@@@ -80,7 -80,7 +80,7 @@@
  static void find_short_object_filename(struct disambiguate_state *ds)
  {
        struct alternate_object_database *alt;
 -      char hex[GIT_SHA1_HEXSZ];
 +      char hex[GIT_MAX_HEXSZ];
        static struct alternate_object_database *fakeent;
  
        if (!fakeent) {
                        continue;
  
                while (!ds->ambiguous && (de = readdir(dir)) != NULL) {
 -                      unsigned char sha1[20];
 +                      struct object_id oid;
  
 -                      if (strlen(de->d_name) != 38)
 +                      if (strlen(de->d_name) != GIT_SHA1_HEXSZ - 2)
                                continue;
                        if (memcmp(de->d_name, ds->hex_pfx + 2, ds->len - 2))
                                continue;
 -                      memcpy(hex + 2, de->d_name, 38);
 -                      if (!get_sha1_hex(hex, sha1))
 -                              update_candidates(ds, sha1);
 +                      memcpy(hex + 2, de->d_name, GIT_SHA1_HEXSZ - 2);
 +                      if (!get_oid_hex(hex, &oid))
 +                              update_candidates(ds, &oid);
                }
                closedir(dir);
        }
@@@ -140,7 -140,7 +140,7 @@@ static void unique_in_pack(struct packe
                           struct disambiguate_state *ds)
  {
        uint32_t num, last, i, first = 0;
 -      const unsigned char *current = NULL;
 +      const struct object_id *current = NULL;
  
        open_pack_index(p);
        num = p->num_objects;
                int cmp;
  
                current = nth_packed_object_sha1(p, mid);
 -              cmp = hashcmp(ds->bin_pfx, current);
 +              cmp = hashcmp(ds->bin_pfx.hash, current);
                if (!cmp) {
                        first = mid;
                        break;
         * 0, 1 or more objects that actually match(es).
         */
        for (i = first; i < num && !ds->ambiguous; i++) {
 -              current = nth_packed_object_sha1(p, i);
 -              if (!match_sha(ds->len, ds->bin_pfx, current))
 +              struct object_id oid;
 +              current = nth_packed_object_oid(&oid, p, i);
 +              if (!match_sha(ds->len, ds->bin_pfx.hash, current->hash))
                        break;
                update_candidates(ds, current);
        }
@@@ -214,66 -213,66 +214,66 @@@ static int finish_object_disambiguation
                 * same repository!
                 */
                ds->candidate_ok = (!ds->disambiguate_fn_used ||
 -                                  ds->fn(ds->candidate, ds->cb_data));
 +                                  ds->fn(&ds->candidate, ds->cb_data));
  
        if (!ds->candidate_ok)
                return SHORT_NAME_AMBIGUOUS;
  
 -      hashcpy(sha1, ds->candidate);
 +      hashcpy(sha1, ds->candidate.hash);
        return 0;
  }
  
 -static int disambiguate_commit_only(const unsigned char *sha1, void *cb_data_unused)
 +static int disambiguate_commit_only(const struct object_id *oid, void *cb_data_unused)
  {
 -      int kind = sha1_object_info(sha1, NULL);
 +      int kind = sha1_object_info(oid->hash, NULL);
        return kind == OBJ_COMMIT;
  }
  
 -static int disambiguate_committish_only(const unsigned char *sha1, void *cb_data_unused)
 +static int disambiguate_committish_only(const struct object_id *oid, void *cb_data_unused)
  {
        struct object *obj;
        int kind;
  
 -      kind = sha1_object_info(sha1, NULL);
 +      kind = sha1_object_info(oid->hash, NULL);
        if (kind == OBJ_COMMIT)
                return 1;
        if (kind != OBJ_TAG)
                return 0;
  
        /* We need to do this the hard way... */
 -      obj = deref_tag(parse_object(sha1), NULL, 0);
 +      obj = deref_tag(parse_object(oid), NULL, 0);
        if (obj && obj->type == OBJ_COMMIT)
                return 1;
        return 0;
  }
  
 -static int disambiguate_tree_only(const unsigned char *sha1, void *cb_data_unused)
 +static int disambiguate_tree_only(const struct object_id *oid, void *cb_data_unused)
  {
 -      int kind = sha1_object_info(sha1, NULL);
 +      int kind = sha1_object_info(oid->hash, NULL);
        return kind == OBJ_TREE;
  }
  
 -static int disambiguate_treeish_only(const unsigned char *sha1, void *cb_data_unused)
 +static int disambiguate_treeish_only(const struct object_id *oid, void *cb_data_unused)
  {
        struct object *obj;
        int kind;
  
 -      kind = sha1_object_info(sha1, NULL);
 +      kind = sha1_object_info(oid->hash, NULL);
        if (kind == OBJ_TREE || kind == OBJ_COMMIT)
                return 1;
        if (kind != OBJ_TAG)
                return 0;
  
        /* We need to do this the hard way... */
 -      obj = deref_tag(parse_object(sha1), NULL, 0);
 +      obj = deref_tag(parse_object(oid), NULL, 0);
        if (obj && (obj->type == OBJ_TREE || obj->type == OBJ_COMMIT))
                return 1;
        return 0;
  }
  
 -static int disambiguate_blob_only(const unsigned char *sha1, void *cb_data_unused)
 +static int disambiguate_blob_only(const struct object_id *oid, void *cb_data_unused)
  {
 -      int kind = sha1_object_info(sha1, NULL);
 +      int kind = sha1_object_info(oid->hash, NULL);
        return kind == OBJ_BLOB;
  }
  
@@@ -333,7 -332,7 +333,7 @@@ static int init_object_disambiguation(c
                ds->hex_pfx[i] = c;
                if (!(i & 1))
                        val <<= 4;
 -              ds->bin_pfx[i >> 1] |= val;
 +              ds->bin_pfx.hash[i >> 1] |= val;
        }
  
        ds->len = len;
        return 0;
  }
  
 -static int show_ambiguous_object(const unsigned char *sha1, void *data)
 +static int show_ambiguous_object(const struct object_id *oid, void *data)
  {
        const struct disambiguate_state *ds = data;
        struct strbuf desc = STRBUF_INIT;
        int type;
  
 -      if (ds->fn && !ds->fn(sha1, ds->cb_data))
 +
 +      if (ds->fn && !ds->fn(oid, ds->cb_data))
                return 0;
  
 -      type = sha1_object_info(sha1, NULL);
 +      type = sha1_object_info(oid->hash, NULL);
        if (type == OBJ_COMMIT) {
 -              struct commit *commit = lookup_commit(sha1);
 +              struct commit *commit = lookup_commit(oid);
                if (commit) {
                        struct pretty_print_context pp = {0};
                        pp.date_mode.type = DATE_SHORT;
                        format_commit_message(commit, " %ad - %s", &desc, &pp);
                }
        } else if (type == OBJ_TAG) {
 -              struct tag *tag = lookup_tag(sha1);
 +              struct tag *tag = lookup_tag(oid);
                if (!parse_tag(tag) && tag->tag)
                        strbuf_addf(&desc, " %s", tag->tag);
        }
  
        advise("  %s %s%s",
 -             find_unique_abbrev(sha1, DEFAULT_ABBREV),
 +             find_unique_abbrev(oid->hash, DEFAULT_ABBREV),
               typename(type) ? typename(type) : "unknown type",
               desc.buf);
  
@@@ -424,15 -422,15 +424,15 @@@ static int get_short_sha1(const char *n
        return status;
  }
  
 -static int collect_ambiguous(const unsigned char *sha1, void *data)
 +static int collect_ambiguous(const struct object_id *oid, void *data)
  {
 -      sha1_array_append(data, sha1);
 +      oid_array_append(data, oid);
        return 0;
  }
  
  int for_each_abbrev(const char *prefix, each_abbrev_fn fn, void *cb_data)
  {
 -      struct sha1_array collect = SHA1_ARRAY_INIT;
 +      struct oid_array collect = OID_ARRAY_INIT;
        struct disambiguate_state ds;
        int ret;
  
        find_short_object_filename(&ds);
        find_short_packed_object(&ds);
  
 -      ret = sha1_array_for_each_unique(&collect, fn, cb_data);
 -      sha1_array_clear(&collect);
 +      ret = oid_array_for_each_unique(&collect, fn, cb_data);
 +      oid_array_clear(&collect);
        return ret;
  }
  
@@@ -511,7 -509,7 +511,7 @@@ int find_unique_abbrev_r(char *hex, con
  const char *find_unique_abbrev(const unsigned char *sha1, int len)
  {
        static int bufno;
 -      static char hexbuffer[4][GIT_SHA1_HEXSZ + 1];
 +      static char hexbuffer[4][GIT_MAX_HEXSZ + 1];
        char *hex = hexbuffer[bufno];
        bufno = (bufno + 1) % ARRAY_SIZE(hexbuffer);
        find_unique_abbrev_r(hex, sha1, len);
@@@ -551,7 -549,7 +551,7 @@@ static inline int at_mark(const char *s
        for (i = 0; i < nr; i++) {
                int suffix_len = strlen(suffix[i]);
                if (suffix_len <= len
 -                  && !memcmp(string, suffix[i], suffix_len))
 +                  && !strncasecmp(string, suffix[i], suffix_len))
                        return suffix_len;
        }
        return 0;
@@@ -660,8 -658,8 +660,8 @@@ static int get_sha1_basic(const char *s
  
        if (reflog_len) {
                int nth, i;
 -              unsigned long at_time;
 -              unsigned long co_time;
 +              timestamp_t at_time;
 +              timestamp_t co_time;
                int co_tz, co_cnt;
  
                /* Is it asking for N-th entry, or approxidate? */
  static int get_parent(const char *name, int len,
                      unsigned char *result, int idx)
  {
 -      unsigned char sha1[20];
 -      int ret = get_sha1_1(name, len, sha1, GET_SHA1_COMMITTISH);
 +      struct object_id oid;
 +      int ret = get_sha1_1(name, len, oid.hash, GET_SHA1_COMMITTISH);
        struct commit *commit;
        struct commit_list *p;
  
        if (ret)
                return ret;
 -      commit = lookup_commit_reference(sha1);
 +      commit = lookup_commit_reference(&oid);
        if (parse_commit(commit))
                return -1;
        if (!idx) {
  static int get_nth_ancestor(const char *name, int len,
                            unsigned char *result, int generation)
  {
 -      unsigned char sha1[20];
 +      struct object_id oid;
        struct commit *commit;
        int ret;
  
 -      ret = get_sha1_1(name, len, sha1, GET_SHA1_COMMITTISH);
 +      ret = get_sha1_1(name, len, oid.hash, GET_SHA1_COMMITTISH);
        if (ret)
                return ret;
 -      commit = lookup_commit_reference(sha1);
 +      commit = lookup_commit_reference(&oid);
        if (!commit)
                return -1;
  
@@@ -776,7 -774,7 +776,7 @@@ struct object *peel_to_type(const char 
        if (name && !namelen)
                namelen = strlen(name);
        while (1) {
 -              if (!o || (!o->parsed && !parse_object(o->oid.hash)))
 +              if (!o || (!o->parsed && !parse_object(&o->oid)))
                        return NULL;
                if (expected_type == OBJ_ANY || o->type == expected_type)
                        return o;
  static int peel_onion(const char *name, int len, unsigned char *sha1,
                      unsigned lookup_flags)
  {
 -      unsigned char outer[20];
 +      struct object_id outer;
        const char *sp;
        unsigned int expected_type = 0;
        struct object *o;
        else if (expected_type == OBJ_TREE)
                lookup_flags |= GET_SHA1_TREEISH;
  
 -      if (get_sha1_1(name, sp - name - 2, outer, lookup_flags))
 +      if (get_sha1_1(name, sp - name - 2, outer.hash, lookup_flags))
                return -1;
  
 -      o = parse_object(outer);
 +      o = parse_object(&outer);
        if (!o)
                return -1;
        if (!expected_type) {
                o = deref_tag(o, name, sp - name - 2);
 -              if (!o || (!o->parsed && !parse_object(o->oid.hash)))
 +              if (!o || (!o->parsed && !parse_object(&o->oid)))
                        return -1;
                hashcpy(sha1, o->oid.hash);
                return 0;
@@@ -981,7 -979,7 +981,7 @@@ static int handle_one_ref(const char *p
                          int flag, void *cb_data)
  {
        struct commit_list **list = cb_data;
 -      struct object *object = parse_object(oid->hash);
 +      struct object *object = parse_object(oid);
        if (!object)
                return 0;
        if (object->type == OBJ_TAG) {
@@@ -1027,7 -1025,7 +1027,7 @@@ static int get_sha1_oneline(const char 
                int matches;
  
                commit = pop_most_recent_commit(&list, ONELINE_SEEN);
 -              if (!parse_object(commit->object.oid.hash))
 +              if (!parse_object(&commit->object.oid))
                        continue;
                buf = get_commit_buffer(commit, NULL);
                p = strstr(buf, "\n\n");
@@@ -1053,8 -1051,8 +1053,8 @@@ struct grab_nth_branch_switch_cbdata 
        struct strbuf buf;
  };
  
 -static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
 -                                const char *email, unsigned long timestamp, int tz,
 +static int grab_nth_branch_switch(struct object_id *ooid, struct object_id *noid,
 +                                const char *email, timestamp_t timestamp, int tz,
                                  const char *message, void *cb_data)
  {
        struct grab_nth_branch_switch_cbdata *cb = cb_data;
@@@ -1136,13 -1134,13 +1136,13 @@@ int get_oid_mb(const char *name, struc
        }
        if (st)
                return st;
 -      one = lookup_commit_reference_gently(oid_tmp.hash, 0);
 +      one = lookup_commit_reference_gently(&oid_tmp, 0);
        if (!one)
                return -1;
  
        if (get_sha1_committish(dots[3] ? (dots + 3) : "HEAD", oid_tmp.hash))
                return -1;
 -      two = lookup_commit_reference_gently(oid_tmp.hash, 0);
 +      two = lookup_commit_reference_gently(&oid_tmp, 0);
        if (!two)
                return -1;
        mbs = get_merge_bases(one, two);
@@@ -1408,7 -1406,7 +1408,7 @@@ static void diagnose_invalid_sha1_path(
        if (file_exists(filename))
                die("Path '%s' exists on disk, but not in '%.*s'.",
                    filename, object_name_len, object_name);
-       if (errno == ENOENT || errno == ENOTDIR) {
+       if (is_missing_file_error(errno)) {
                char *fullname = xstrfmt("%s%s", prefix, filename);
  
                if (!get_tree_entry(tree_sha1, fullname,
@@@ -1473,7 -1471,7 +1473,7 @@@ static void diagnose_invalid_index_path
  
        if (file_exists(filename))
                die("Path '%s' exists on disk, but not in the index.", filename);
-       if (errno == ENOENT || errno == ENOTDIR)
+       if (is_missing_file_error(errno))
                die("Path '%s' does not exist (neither on disk nor in the index).",
                    filename);
  
@@@ -1511,7 -1509,6 +1511,7 @@@ static int get_sha1_with_context_1(cons
  
        memset(oc, 0, sizeof(*oc));
        oc->mode = S_IFINVALID;
 +      strbuf_init(&oc->symlink_path, 0);
        ret = get_sha1_1(name, namelen, sha1, flags);
        if (!ret)
                return ret;
                        namelen = strlen(cp);
                }
  
 -              strlcpy(oc->path, cp, sizeof(oc->path));
 +              if (flags & GET_SHA1_RECORD_PATH)
 +                      oc->path = xstrdup(cp);
  
                if (!active_cache)
                        read_cache();
                                }
                        }
                        hashcpy(oc->tree, tree_sha1);
 -                      strlcpy(oc->path, filename, sizeof(oc->path));
 +                      if (flags & GET_SHA1_RECORD_PATH)
 +                              oc->path = xstrdup(filename);
  
                        free(new_filename);
                        return ret;
@@@ -1641,9 -1636,9 +1641,9 @@@ void maybe_die_on_misspelt_object_name(
        get_sha1_with_context_1(name, GET_SHA1_ONLY_TO_DIE, prefix, sha1, &oc);
  }
  
 -int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc)
 +int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *oc)
  {
        if (flags & GET_SHA1_FOLLOW_SYMLINKS && flags & GET_SHA1_ONLY_TO_DIE)
                die("BUG: incompatible flags for get_sha1_with_context");
 -      return get_sha1_with_context_1(str, flags, NULL, sha1, orc);
 +      return get_sha1_with_context_1(str, flags, NULL, sha1, oc);
  }
diff --combined wrapper.c
index d83741770949f457b364896b6ff8632c0a700d69,2fbbd813590a36c90bc1cc7177373752b6eae981..708e98a96585f8f9a800e9ddae942b94ea7372b8
+++ b/wrapper.c
@@@ -583,8 -583,8 +583,8 @@@ void warn_on_inaccessible(const char *p
  
  static int access_error_is_ok(int err, unsigned flag)
  {
-       return err == ENOENT || err == ENOTDIR ||
-               ((flag & ACCESS_EACCES_OK) && err == EACCES);
+       return (is_missing_file_error(err) ||
+               ((flag & ACCESS_EACCES_OK) && err == EACCES));
  }
  
  int access_or_warn(const char *path, int mode, unsigned flag)
@@@ -655,16 -655,3 +655,16 @@@ void sleep_millisec(int millisec
  {
        poll(NULL, 0, millisec);
  }
 +
 +int xgethostname(char *buf, size_t len)
 +{
 +      /*
 +       * If the full hostname doesn't fit in buf, POSIX does not
 +       * specify whether the buffer will be null-terminated, so to
 +       * be safe, do it ourselves.
 +       */
 +      int ret = gethostname(buf, len);
 +      if (!ret)
 +              buf[len - 1] = 0;
 +      return ret;
 +}