]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'dt/name-hash-dir-entry-fix' into maint
authorJunio C Hamano <gitster@pobox.com>
Tue, 3 Nov 2015 23:32:40 +0000 (15:32 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 3 Nov 2015 23:32:40 +0000 (15:32 -0800)
The name-hash subsystem that is used to cope with case insensitive
filesystems keeps track of directories and their on-filesystem
cases for all the paths in the index by holding a pointer to a
randomly chosen cache entry that is inside the directory (for its
ce->ce_name component).  This pointer was not updated even when the
cache entry was removed from the index, leading to use after free.
This was fixed by recording the path for each directory instead of
borrowing cache entries and restructuring the API somewhat.

* dt/name-hash-dir-entry-fix:
  name-hash: don't reuse cache_entry in dir_entry

1  2 
cache.h
dir.c
read-cache.c

diff --combined cache.h
index d941224de5952292050bee72fea1e9ec2b1376c6,1afe5374b9f2bf4436c80209c219b3c6beb3c515..a9aaa0396a893c2669789ea18ee14ecafac965b0
+++ b/cache.h
@@@ -43,14 -43,6 +43,14 @@@ int git_deflate_end_gently(git_zstream 
  int git_deflate(git_zstream *, int flush);
  unsigned long git_deflate_bound(git_zstream *, unsigned long);
  
 +/* The length in bytes and in hex digits of an object name (SHA-1 value). */
 +#define GIT_SHA1_RAWSZ 20
 +#define GIT_SHA1_HEXSZ (2 * GIT_SHA1_RAWSZ)
 +
 +struct object_id {
 +      unsigned char hash[GIT_SHA1_RAWSZ];
 +};
 +
  #if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT)
  #define DTYPE(de)     ((de)->d_type)
  #else
   *
   * The value 0160000 is not normally a valid mode, and
   * also just happens to be S_IFDIR + S_IFLNK
 - *
 - * NOTE! We *really* shouldn't depend on the S_IFxxx macros
 - * always having the same values everywhere. We should use
 - * our internal git values for these things, and then we can
 - * translate that to the OS-specific value. It just so
 - * happens that everybody shares the same bit representation
 - * in the UNIX world (and apparently wider too..)
   */
  #define S_IFGITLINK   0160000
  #define S_ISGITLINK(m)        (((m) & S_IFMT) == S_IFGITLINK)
@@@ -297,11 -296,8 +297,11 @@@ static inline unsigned int canon_mode(u
  #define RESOLVE_UNDO_CHANGED  (1 << 4)
  #define CACHE_TREE_CHANGED    (1 << 5)
  #define SPLIT_INDEX_ORDERED   (1 << 6)
 +#define UNTRACKED_CHANGED     (1 << 7)
  
  struct split_index;
 +struct untracked_cache;
 +
  struct index_state {
        struct cache_entry **cache;
        unsigned int version;
        struct hashmap name_hash;
        struct hashmap dir_hash;
        unsigned char sha1[20];
 +      struct untracked_cache *untracked;
  };
  
  extern struct index_state the_index;
@@@ -382,7 -377,6 +382,7 @@@ static inline enum object_type object_t
  
  /* Double-check local_repo_env below if you add to this list. */
  #define GIT_DIR_ENVIRONMENT "GIT_DIR"
 +#define GIT_COMMON_DIR_ENVIRONMENT "GIT_COMMON_DIR"
  #define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE"
  #define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
  #define GIT_PREFIX_ENVIRONMENT "GIT_PREFIX"
  #define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH"
  #define CEILING_DIRECTORIES_ENVIRONMENT "GIT_CEILING_DIRECTORIES"
  #define NO_REPLACE_OBJECTS_ENVIRONMENT "GIT_NO_REPLACE_OBJECTS"
 +#define GIT_REPLACE_REF_BASE_ENVIRONMENT "GIT_REPLACE_REF_BASE"
  #define GITATTRIBUTES_FILE ".gitattributes"
  #define INFOATTRIBUTES_FILE "info/attributes"
  #define ATTRIBUTE_MACRO_PREFIX "[attr]"
@@@ -437,28 -430,15 +437,28 @@@ extern int is_inside_git_dir(void)
  extern char *git_work_tree_cfg;
  extern int is_inside_work_tree(void);
  extern const char *get_git_dir(void);
 +extern const char *get_git_common_dir(void);
  extern int is_git_directory(const char *path);
  extern char *get_object_directory(void);
  extern char *get_index_file(void);
  extern char *get_graft_file(void);
  extern int set_git_dir(const char *path);
 +extern int get_common_dir_noenv(struct strbuf *sb, const char *gitdir);
 +extern int get_common_dir(struct strbuf *sb, const char *gitdir);
  extern const char *get_git_namespace(void);
  extern const char *strip_namespace(const char *namespaced_ref);
  extern const char *get_git_work_tree(void);
 -extern const char *read_gitfile(const char *path);
 +
 +#define READ_GITFILE_ERR_STAT_FAILED 1
 +#define READ_GITFILE_ERR_NOT_A_FILE 2
 +#define READ_GITFILE_ERR_OPEN_FAILED 3
 +#define READ_GITFILE_ERR_READ_FAILED 4
 +#define READ_GITFILE_ERR_INVALID_FORMAT 5
 +#define READ_GITFILE_ERR_NO_PATH 6
 +#define READ_GITFILE_ERR_NOT_A_REPO 7
 +#define READ_GITFILE_ERR_TOO_LARGE 8
 +extern const char *read_gitfile_gently(const char *path, int *return_error_code);
 +#define read_gitfile(path) read_gitfile_gently((path), NULL)
  extern const char *resolve_gitdir(const char *suspect);
  extern void set_git_work_tree(const char *tree);
  
@@@ -521,7 -501,8 +521,8 @@@ extern int write_locked_index(struct in
  extern int discard_index(struct index_state *);
  extern int unmerged_index(const struct index_state *);
  extern int verify_path(const char *path);
- extern struct cache_entry *index_dir_exists(struct index_state *istate, const char *name, int namelen);
+ extern int index_dir_exists(struct index_state *istate, const char *name, int namelen);
+ extern void adjust_dirname_case(struct index_state *istate, char *name);
  extern struct cache_entry *index_file_exists(struct index_state *istate, const char *name, int namelen, int igncase);
  extern int index_name_pos(const struct index_state *, const char *name, int namelen);
  #define ADD_CACHE_OK_TO_ADD 1         /* Ok to add */
@@@ -579,8 -560,6 +580,8 @@@ extern void fill_stat_data(struct stat_
   * INODE_CHANGED, and DATA_CHANGED.
   */
  extern int match_stat_data(const struct stat_data *sd, struct stat *st);
 +extern int match_stat_data_racy(const struct index_state *istate,
 +                              const struct stat_data *sd, struct stat *st);
  
  extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
  
@@@ -597,6 -576,8 +598,6 @@@ extern void update_index_if_able(struc
  extern int hold_locked_index(struct lock_file *, int);
  extern void set_alternate_index_output(const char *);
  
 -extern int delete_ref(const char *, const unsigned char *sha1, int delopt);
 -
  /* Environment bits from configuration mechanism */
  extern int trust_executable_bit;
  extern int trust_ctime;
@@@ -632,7 -613,6 +633,7 @@@ extern unsigned long pack_size_limit_cf
   * been sought but there were none.
   */
  extern int check_replace_refs;
 +extern char *git_replace_ref_base;
  
  extern int fsync_object_files;
  extern int core_preload_index;
@@@ -640,15 -620,6 +641,15 @@@ extern int core_apply_sparse_checkout
  extern int precomposed_unicode;
  extern int protect_hfs;
  extern int protect_ntfs;
 +extern int git_db_env, git_index_env, git_graft_env, git_common_dir_env;
 +
 +/*
 + * Include broken refs in all ref iterations, which will
 + * generally choke dangerous operations rather than letting
 + * them silently proceed without taking the broken ref into
 + * account.
 + */
 +extern int ref_paranoia;
  
  /*
   * The character that begins a commented line in user-editable file
@@@ -697,15 -668,8 +698,15 @@@ extern char *notes_ref_name
  
  extern int grafts_replace_parents;
  
 +/*
 + * GIT_REPO_VERSION is the version we write by default. The
 + * _READ variant is the highest number we know how to
 + * handle.
 + */
  #define GIT_REPO_VERSION 0
 +#define GIT_REPO_VERSION_READ 1
  extern int repository_format_version;
 +extern int repository_format_precious_objects;
  extern int check_repository_format(void);
  
  #define MTIME_CHANGED 0x0001
  #define DATA_CHANGED    0x0020
  #define TYPE_CHANGED    0x0040
  
 +/*
 + * Return a statically allocated filename, either generically (mkpath), in
 + * the repository directory (git_path), or in a submodule's repository
 + * directory (git_path_submodule). In all cases, note that the result
 + * may be overwritten by another call to _any_ of the functions. Consider
 + * using the safer "dup" or "strbuf" formats below (in some cases, the
 + * unsafe versions have already been removed).
 + */
 +extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 +extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 +
  extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
 -extern char *git_snpath(char *buf, size_t n, const char *fmt, ...)
 +extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 +      __attribute__((format (printf, 2, 3)));
 +extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,
 +                                    const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
  extern char *git_pathdup(const char *fmt, ...)
        __attribute__((format (printf, 1, 2)));
  extern char *mkpathdup(const char *fmt, ...)
        __attribute__((format (printf, 1, 2)));
 -
 -/* Return a statically allocated filename matching the sha1 signature */
 -extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 -extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 -extern char *git_path_submodule(const char *path, const char *fmt, ...)
 +extern char *git_pathdup_submodule(const char *path, const char *fmt, ...)
        __attribute__((format (printf, 2, 3)));
  
 +extern void report_linked_checkout_garbage(void);
 +
 +/*
 + * You can define a static memoized git path like:
 + *
 + *    static GIT_PATH_FUNC(git_path_foo, "FOO");
 + *
 + * or use one of the global ones below.
 + */
 +#define GIT_PATH_FUNC(func, filename) \
 +      const char *func(void) \
 +      { \
 +              static char *ret; \
 +              if (!ret) \
 +                      ret = git_pathdup(filename); \
 +              return ret; \
 +      }
 +
 +const char *git_path_cherry_pick_head(void);
 +const char *git_path_revert_head(void);
 +const char *git_path_squash_msg(void);
 +const char *git_path_merge_msg(void);
 +const char *git_path_merge_rr(void);
 +const char *git_path_merge_mode(void);
 +const char *git_path_merge_head(void);
 +const char *git_path_fetch_head(void);
 +const char *git_path_shallow(void);
 +
  /*
   * Return the name of the file in the local object database that would
   * be used to store a loose object with the specified sha1.  The
@@@ -792,13 -718,13 +793,13 @@@ extern char *sha1_pack_name(const unsig
  extern char *sha1_pack_index_name(const unsigned char *sha1);
  
  extern const char *find_unique_abbrev(const unsigned char *sha1, int);
 -extern const unsigned char null_sha1[20];
 +extern const unsigned char null_sha1[GIT_SHA1_RAWSZ];
  
  static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2)
  {
        int i;
  
 -      for (i = 0; i < 20; i++, sha1++, sha2++) {
 +      for (i = 0; i < GIT_SHA1_RAWSZ; i++, sha1++, sha2++) {
                if (*sha1 != *sha2)
                        return *sha1 - *sha2;
        }
        return 0;
  }
  
 +static inline int oidcmp(const struct object_id *oid1, const struct object_id *oid2)
 +{
 +      return hashcmp(oid1->hash, oid2->hash);
 +}
 +
  static inline int is_null_sha1(const unsigned char *sha1)
  {
        return !hashcmp(sha1, null_sha1);
  }
  
 +static inline int is_null_oid(const struct object_id *oid)
 +{
 +      return !hashcmp(oid->hash, null_sha1);
 +}
 +
  static inline void hashcpy(unsigned char *sha_dst, const unsigned char *sha_src)
  {
 -      memcpy(sha_dst, sha_src, 20);
 +      memcpy(sha_dst, sha_src, GIT_SHA1_RAWSZ);
 +}
 +
 +static inline void oidcpy(struct object_id *dst, const struct object_id *src)
 +{
 +      hashcpy(dst->hash, src->hash);
  }
 +
  static inline void hashclr(unsigned char *hash)
  {
 -      memset(hash, 0, 20);
 +      memset(hash, 0, GIT_SHA1_RAWSZ);
 +}
 +
 +static inline void oidclr(struct object_id *oid)
 +{
 +      hashclr(oid->hash);
  }
  
 +
  #define EMPTY_TREE_SHA1_HEX \
        "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
  #define EMPTY_TREE_SHA1_BIN_LITERAL \
@@@ -912,6 -816,7 +913,6 @@@ enum scld_error safe_create_leading_dir
  enum scld_error safe_create_leading_directories_const(const char *path);
  
  int mkdir_in_gitdir(const char *path);
 -extern void home_config_paths(char **global, char **xdg, char *file);
  extern char *expand_user_path(const char *path);
  const char *enter_repo(const char *path, int strict);
  static inline int is_absolute_path(const char *path)
@@@ -931,16 -836,8 +932,16 @@@ char *strip_path_suffix(const char *pat
  int daemon_avoid_alias(const char *path);
  extern int is_ntfs_dotgit(const char *name);
  
 +/**
 + * Return a newly allocated string with the evaluation of
 + * "$XDG_CONFIG_HOME/git/$filename" if $XDG_CONFIG_HOME is non-empty, otherwise
 + * "$HOME/.config/git/$filename". Return NULL upon error.
 + */
 +extern char *xdg_config_home(const char *filename);
 +
  /* object replacement */
  #define LOOKUP_REPLACE_OBJECT 1
 +#define LOOKUP_UNKNOWN_OBJECT 2
  extern void *read_sha1_file_extended(const unsigned char *sha1, enum object_type *type, unsigned long *size, unsigned flag);
  static inline void *read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
  {
@@@ -977,7 -874,6 +978,7 @@@ static inline const unsigned char *look
  extern int sha1_object_info(const unsigned char *, unsigned long *);
  extern int hash_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1);
  extern int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
 +extern int hash_sha1_file_literally(const void *buf, unsigned long len, const char *type, unsigned char *sha1, unsigned flags);
  extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
  extern int force_object_loose(const unsigned char *sha1, time_t mtime);
  extern int git_open_noatime(const char *name);
@@@ -990,7 -886,7 +991,7 @@@ extern int do_check_packed_object_crc
  
  extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
  
 -extern int move_temp_to_file(const char *tmpfile, const char *filename);
 +extern int finalize_object_file(const char *tmpfile, const char *filename);
  
  extern int has_sha1_pack(const unsigned char *sha1);
  
   * Return true iff we have an object named sha1, whether local or in
   * an alternate object database, and whether packed or loose.  This
   * function does not respect replace references.
 + *
 + * If the QUICK flag is set, do not re-check the pack directory
 + * when we cannot find the object (this means we may give a false
 + * negative answer if another process is simultaneously repacking).
   */
 -extern int has_sha1_file(const unsigned char *sha1);
 +#define HAS_SHA1_QUICK 0x1
 +extern int has_sha1_file_with_flags(const unsigned char *sha1, int flags);
 +static inline int has_sha1_file(const unsigned char *sha1)
 +{
 +      return has_sha1_file_with_flags(sha1, 0);
 +}
  
  /*
   * Return true iff an alternate object database has a loose object
@@@ -1035,21 -922,15 +1036,21 @@@ struct object_context 
        unsigned char tree[20];
        char path[PATH_MAX];
        unsigned mode;
 +      /*
 +       * symlink_path is only used by get_tree_entry_follow_symlinks,
 +       * and only for symlinks that point outside the repository.
 +       */
 +      struct strbuf symlink_path;
  };
  
 -#define GET_SHA1_QUIETLY        01
 -#define GET_SHA1_COMMIT         02
 -#define GET_SHA1_COMMITTISH     04
 -#define GET_SHA1_TREE          010
 -#define GET_SHA1_TREEISH       020
 -#define GET_SHA1_BLOB        040
 -#define GET_SHA1_ONLY_TO_DIE 04000
 +#define GET_SHA1_QUIETLY           01
 +#define GET_SHA1_COMMIT            02
 +#define GET_SHA1_COMMITTISH        04
 +#define GET_SHA1_TREE             010
 +#define GET_SHA1_TREEISH          020
 +#define GET_SHA1_BLOB             040
 +#define GET_SHA1_FOLLOW_SYMLINKS 0100
 +#define GET_SHA1_ONLY_TO_DIE    04000
  
  extern int get_sha1(const char *str, unsigned char *sha1);
  extern int get_sha1_commit(const char *str, unsigned char *sha1);
@@@ -1071,14 -952,78 +1072,14 @@@ extern int for_each_abbrev(const char *
   * null-terminated string.
   */
  extern int get_sha1_hex(const char *hex, unsigned char *sha1);
 +extern int get_oid_hex(const char *hex, struct object_id *sha1);
  
  extern char *sha1_to_hex(const unsigned char *sha1);  /* static buffer result! */
 -extern int read_ref_full(const char *refname, int resolve_flags,
 -                       unsigned char *sha1, int *flags);
 -extern int read_ref(const char *refname, unsigned char *sha1);
 +extern char *oid_to_hex(const struct object_id *oid); /* same static buffer as sha1_to_hex */
  
 -/*
 - * Resolve a reference, recursively following symbolic refererences.
 - *
 - * Store the referred-to object's name in sha1 and return the name of
 - * the non-symbolic reference that ultimately pointed at it.  The
 - * return value, if not NULL, is a pointer into either a static buffer
 - * or the input ref.
 - *
 - * If the reference cannot be resolved to an object, the behavior
 - * depends on the RESOLVE_REF_READING flag:
 - *
 - * - If RESOLVE_REF_READING is set, return NULL.
 - *
 - * - If RESOLVE_REF_READING is not set, clear sha1 and return the name of
 - *   the last reference name in the chain, which will either be a non-symbolic
 - *   reference or an undefined reference.  If this is a prelude to
 - *   "writing" to the ref, the return value is the name of the ref
 - *   that will actually be created or changed.
 - *
 - * If the RESOLVE_REF_NO_RECURSE flag is passed, only resolves one
 - * level of symbolic reference.  The value stored in sha1 for a symbolic
 - * reference will always be null_sha1 in this case, and the return
 - * value is the reference that the symref refers to directly.
 - *
 - * If flags is non-NULL, set the value that it points to the
 - * combination of REF_ISPACKED (if the reference was found among the
 - * packed references), REF_ISSYMREF (if the initial reference was a
 - * symbolic reference), REF_BAD_NAME (if the reference name is ill
 - * formed --- see RESOLVE_REF_ALLOW_BAD_NAME below), and REF_ISBROKEN
 - * (if the ref is malformed or has a bad name). See refs.h for more detail
 - * on each flag.
 - *
 - * If ref is not a properly-formatted, normalized reference, return
 - * NULL.  If more than MAXDEPTH recursive symbolic lookups are needed,
 - * give up and return NULL.
 - *
 - * RESOLVE_REF_ALLOW_BAD_NAME allows resolving refs even when their
 - * name is invalid according to git-check-ref-format(1).  If the name
 - * is bad then the value stored in sha1 will be null_sha1 and the two
 - * flags REF_ISBROKEN and REF_BAD_NAME will be set.
 - *
 - * Even with RESOLVE_REF_ALLOW_BAD_NAME, names that escape the refs/
 - * directory and do not consist of all caps and underscores cannot be
 - * resolved. The function returns NULL for such ref names.
 - * Caps and underscores refers to the special refs, such as HEAD,
 - * FETCH_HEAD and friends, that all live outside of the refs/ directory.
 - */
 -#define RESOLVE_REF_READING 0x01
 -#define RESOLVE_REF_NO_RECURSE 0x02
 -#define RESOLVE_REF_ALLOW_BAD_NAME 0x04
 -extern const char *resolve_ref_unsafe(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);
 -extern char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);
 -
 -extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
 -extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
  extern int interpret_branch_name(const char *str, int len, struct strbuf *);
  extern int get_sha1_mb(const char *str, unsigned char *sha1);
  
 -/*
 - * Return true iff abbrev_name is a possible abbreviation for
 - * full_name according to the rules defined by ref_rev_parse_rules in
 - * refs.c.
 - */
 -extern int refname_match(const char *abbrev_name, const char *full_name);
 -
 -extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg);
  extern int validate_headref(const char *ref);
  
  extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
@@@ -1094,30 -1039,18 +1095,30 @@@ extern void *read_object_with_reference
  extern struct object *peel_to_type(const char *name, int namelen,
                                   struct object *o, enum object_type);
  
 -enum date_mode {
 -      DATE_NORMAL = 0,
 -      DATE_RELATIVE,
 -      DATE_SHORT,
 -      DATE_LOCAL,
 -      DATE_ISO8601,
 -      DATE_ISO8601_STRICT,
 -      DATE_RFC2822,
 -      DATE_RAW
 +struct date_mode {
 +      enum date_mode_type {
 +              DATE_NORMAL = 0,
 +              DATE_RELATIVE,
 +              DATE_SHORT,
 +              DATE_LOCAL,
 +              DATE_ISO8601,
 +              DATE_ISO8601_STRICT,
 +              DATE_RFC2822,
 +              DATE_STRFTIME,
 +              DATE_RAW
 +      } type;
 +      const char *strftime_fmt;
  };
  
 -const char *show_date(unsigned long time, int timezone, enum date_mode mode);
 +/*
 + * Convenience helper for passing a constant type, like:
 + *
 + *   show_date(t, tz, DATE_MODE(NORMAL));
 + */
 +#define DATE_MODE(t) date_mode_from_type(DATE_##t)
 +struct date_mode *date_mode_from_type(enum date_mode_type type);
 +
 +const char *show_date(unsigned long time, int timezone, const struct date_mode *mode);
  void show_date_relative(unsigned long time, int tz, const struct timeval *now,
                        struct strbuf *timebuf);
  int parse_date(const char *date, struct strbuf *out);
@@@ -1127,7 -1060,7 +1128,7 @@@ void datestamp(struct strbuf *out)
  #define approxidate(s) approxidate_careful((s), NULL)
  unsigned long approxidate_careful(const char *, int *);
  unsigned long approxidate_relative(const char *date, const struct timeval *now);
 -enum date_mode parse_date_format(const char *format);
 +void parse_date_format(const char *format, struct date_mode *mode);
  int date_overflows(unsigned long date);
  
  #define IDENT_STRICT         1
@@@ -1164,8 -1097,7 +1165,8 @@@ extern int split_ident_line(struct iden
   * the ident_split. It will also sanity-check the values and produce
   * a well-known sentinel date if they appear bogus.
   */
 -const char *show_ident_date(const struct ident_split *id, enum date_mode mode);
 +const char *show_ident_date(const struct ident_split *id,
 +                          const struct date_mode *mode);
  
  /*
   * Compare split idents for equality or strict ordering. Note that we
@@@ -1242,7 -1174,6 +1243,7 @@@ extern struct packed_git 
        int pack_fd;
        unsigned pack_local:1,
                 pack_keep:1,
 +               freshened:1,
                 do_not_close:1;
        unsigned char sha1[20];
        /* something like ".git/objects/pack/xxxxx.pack" */
@@@ -1283,7 -1214,6 +1284,7 @@@ extern void close_pack_index(struct pac
  
  extern unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned long *);
  extern void close_pack_windows(struct packed_git *);
 +extern void close_all_packs(void);
  extern void unuse_pack(struct pack_window **);
  extern void free_pack_by_name(const char *);
  extern void clear_delta_base_cache(void);
@@@ -1332,10 -1262,6 +1333,10 @@@ extern int unpack_object_header(struct 
   *
   * Any callback that is NULL will be ignored. Callbacks returning non-zero
   * will end the iteration.
 + *
 + * In the "buf" variant, "path" is a strbuf which will also be used as a
 + * scratch buffer, but restored to its original contents before
 + * the function returns.
   */
  typedef int each_loose_object_fn(const unsigned char *sha1,
                                 const char *path,
@@@ -1351,24 -1277,17 +1352,24 @@@ int for_each_loose_file_in_objdir(cons
                                  each_loose_cruft_fn cruft_cb,
                                  each_loose_subdir_fn subdir_cb,
                                  void *data);
 +int for_each_loose_file_in_objdir_buf(struct strbuf *path,
 +                                    each_loose_object_fn obj_cb,
 +                                    each_loose_cruft_fn cruft_cb,
 +                                    each_loose_subdir_fn subdir_cb,
 +                                    void *data);
  
  /*
   * Iterate over loose and packed objects in both the local
 - * repository and any alternates repositories.
 + * repository and any alternates repositories (unless the
 + * LOCAL_ONLY flag is set).
   */
 +#define FOR_EACH_OBJECT_LOCAL_ONLY 0x1
  typedef int each_packed_object_fn(const unsigned char *sha1,
                                  struct packed_git *pack,
                                  uint32_t pos,
                                  void *data);
 -extern int for_each_loose_object(each_loose_object_fn, void *);
 -extern int for_each_packed_object(each_packed_object_fn, void *);
 +extern int for_each_loose_object(each_loose_object_fn, void *, unsigned flags);
 +extern int for_each_packed_object(each_packed_object_fn, void *, unsigned flags);
  
  struct object_info {
        /* Request */
        unsigned long *sizep;
        unsigned long *disk_sizep;
        unsigned char *delta_base_sha1;
 +      struct strbuf *typename;
  
        /* Response */
        enum {
@@@ -1438,7 -1356,6 +1439,7 @@@ extern int git_config_with_options(conf
                                   int respect_includes);
  extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
  extern int git_parse_ulong(const char *, unsigned long *);
 +extern int git_parse_maybe_bool(const char *);
  extern int git_config_int(const char *, const char *);
  extern int64_t git_config_int64(const char *, const char *);
  extern unsigned long git_config_ulong(const char *, const char *);
@@@ -1450,7 -1367,6 +1451,7 @@@ extern int git_config_pathname(const ch
  extern int git_config_set_in_file(const char *, const char *, const char *);
  extern int git_config_set(const char *, const char *);
  extern int git_config_parse_key(const char *, char **, int *);
 +extern int git_config_key_is_valid(const char *key);
  extern int git_config_set_multivar(const char *, const char *, const char *, int);
  extern int git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int);
  extern int git_config_rename_section(const char *, const char *);
@@@ -1567,13 -1483,9 +1568,13 @@@ extern const char *git_mailmap_blob
  extern void maybe_flush_or_die(FILE *, const char *);
  __attribute__((format (printf, 2, 3)))
  extern void fprintf_or_die(FILE *, const char *fmt, ...);
 +
 +#define COPY_READ_ERROR (-2)
 +#define COPY_WRITE_ERROR (-3)
  extern int copy_fd(int ifd, int ofd);
  extern int copy_file(const char *dst, const char *src, int mode);
  extern int copy_file_with_time(const char *dst, const char *src, int mode);
 +
  extern void write_or_die(int fd, const void *buf, size_t count);
  extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg);
  extern int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg);
@@@ -1588,16 -1500,13 +1589,16 @@@ static inline ssize_t write_str_in_full
        return write_in_full(fd, str, strlen(str));
  }
  
 +extern int write_file(const char *path, const char *fmt, ...);
 +extern int write_file_gently(const char *path, const char *fmt, ...);
 +
  /* pager.c */
  extern void setup_pager(void);
  extern const char *pager_program;
  extern int pager_in_use(void);
  extern int pager_use_color;
  extern int term_columns(void);
 -extern int decimal_width(int);
 +extern int decimal_width(uintmax_t);
  extern int check_pager_config(const char *cmd);
  
  extern const char *editor_program;
@@@ -1659,6 -1568,7 +1660,6 @@@ extern int ws_blank_line(const char *li
  #define ws_tab_width(rule)     ((rule) & WS_TAB_WIDTH_MASK)
  
  /* ls-files */
 -int report_path_error(const char *ps_matched, const struct pathspec *pathspec, const char *prefix);
  void overlay_tree_on_cache(const char *tree_name, const char *prefix);
  
  char *alias_lookup(const char *alias);
@@@ -1713,6 -1623,5 +1714,6 @@@ int stat_validity_check(struct stat_val
  void stat_validity_update(struct stat_validity *sv, int fd);
  
  int versioncmp(const char *s1, const char *s2);
 +void sleep_millisec(int millisec);
  
  #endif /* CACHE_H */
diff --combined dir.c
index 118ba59a27f51d1966c03faa8c36627a0a8d6246,8545922ee2ddb5a218e173d938975c47bf76b335..88e89585eb1634e07a9316ccd6caf8ea3643e817
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -12,9 -12,6 +12,9 @@@
  #include "refs.h"
  #include "wildmatch.h"
  #include "pathspec.h"
 +#include "utf8.h"
 +#include "varint.h"
 +#include "ewah/ewok.h"
  
  struct path_simplify {
        int len;
@@@ -34,22 -31,8 +34,22 @@@ enum path_treatment 
        path_untracked
  };
  
 +/*
 + * Support data structure for our opendir/readdir/closedir wrappers
 + */
 +struct cached_dir {
 +      DIR *fdir;
 +      struct untracked_cache_dir *untracked;
 +      int nr_files;
 +      int nr_dirs;
 +
 +      struct dirent *de;
 +      const char *file;
 +      struct untracked_cache_dir *ucd;
 +};
 +
  static enum path_treatment read_directory_recursive(struct dir_struct *dir,
 -      const char *path, int len,
 +      const char *path, int len, struct untracked_cache_dir *untracked,
        int check_only, const struct path_simplify *simplify);
  static int get_dtype(struct dirent *de, const char *path, int len);
  
@@@ -394,47 -377,6 +394,47 @@@ int match_pathspec(const struct pathspe
        return negative ? 0 : positive;
  }
  
 +int report_path_error(const char *ps_matched,
 +                    const struct pathspec *pathspec,
 +                    const char *prefix)
 +{
 +      /*
 +       * Make sure all pathspec matched; otherwise it is an error.
 +       */
 +      int num, errors = 0;
 +      for (num = 0; num < pathspec->nr; num++) {
 +              int other, found_dup;
 +
 +              if (ps_matched[num])
 +                      continue;
 +              /*
 +               * The caller might have fed identical pathspec
 +               * twice.  Do not barf on such a mistake.
 +               * FIXME: parse_pathspec should have eliminated
 +               * duplicate pathspec.
 +               */
 +              for (found_dup = other = 0;
 +                   !found_dup && other < pathspec->nr;
 +                   other++) {
 +                      if (other == num || !ps_matched[other])
 +                              continue;
 +                      if (!strcmp(pathspec->items[other].original,
 +                                  pathspec->items[num].original))
 +                              /*
 +                               * Ok, we have a match already.
 +                               */
 +                              found_dup = 1;
 +              }
 +              if (found_dup)
 +                      continue;
 +
 +              error("pathspec '%s' did not match any file(s) known to git.",
 +                    pathspec->items[num].original);
 +              errors++;
 +      }
 +      return errors;
 +}
 +
  /*
   * Return the length of the "simple" part of a path match limiter.
   */
@@@ -524,8 -466,7 +524,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 char *path, size_t *size,
 +                                              struct sha1_stat *sha1_stat)
  {
        int pos, len;
        unsigned long sz;
                return NULL;
        }
        *size = xsize_t(sz);
 +      if (sha1_stat) {
 +              memset(&sha1_stat->stat, 0, sizeof(sha1_stat->stat));
 +              hashcpy(sha1_stat->sha1, active_cache[pos]->sha1);
 +      }
        return data;
  }
  
@@@ -592,93 -529,11 +592,93 @@@ static void trim_trailing_spaces(char *
                *last_space = '\0';
  }
  
 -int add_excludes_from_file_to_list(const char *fname,
 -                                 const char *base,
 -                                 int baselen,
 -                                 struct exclude_list *el,
 -                                 int check_index)
 +/*
 + * Given a subdirectory name and "dir" of the current directory,
 + * search the subdir in "dir" and return it, or create a new one if it
 + * does not exist in "dir".
 + *
 + * If "name" has the trailing slash, it'll be excluded in the search.
 + */
 +static struct untracked_cache_dir *lookup_untracked(struct untracked_cache *uc,
 +                                                  struct untracked_cache_dir *dir,
 +                                                  const char *name, int len)
 +{
 +      int first, last;
 +      struct untracked_cache_dir *d;
 +      if (!dir)
 +              return NULL;
 +      if (len && name[len - 1] == '/')
 +              len--;
 +      first = 0;
 +      last = dir->dirs_nr;
 +      while (last > first) {
 +              int cmp, next = (last + first) >> 1;
 +              d = dir->dirs[next];
 +              cmp = strncmp(name, d->name, len);
 +              if (!cmp && strlen(d->name) > len)
 +                      cmp = -1;
 +              if (!cmp)
 +                      return d;
 +              if (cmp < 0) {
 +                      last = next;
 +                      continue;
 +              }
 +              first = next+1;
 +      }
 +
 +      uc->dir_created++;
 +      d = xmalloc(sizeof(*d) + len + 1);
 +      memset(d, 0, sizeof(*d));
 +      memcpy(d->name, name, len);
 +      d->name[len] = '\0';
 +
 +      ALLOC_GROW(dir->dirs, dir->dirs_nr + 1, dir->dirs_alloc);
 +      memmove(dir->dirs + first + 1, dir->dirs + first,
 +              (dir->dirs_nr - first) * sizeof(*dir->dirs));
 +      dir->dirs_nr++;
 +      dir->dirs[first] = d;
 +      return d;
 +}
 +
 +static void do_invalidate_gitignore(struct untracked_cache_dir *dir)
 +{
 +      int i;
 +      dir->valid = 0;
 +      dir->untracked_nr = 0;
 +      for (i = 0; i < dir->dirs_nr; i++)
 +              do_invalidate_gitignore(dir->dirs[i]);
 +}
 +
 +static void invalidate_gitignore(struct untracked_cache *uc,
 +                               struct untracked_cache_dir *dir)
 +{
 +      uc->gitignore_invalidated++;
 +      do_invalidate_gitignore(dir);
 +}
 +
 +static void invalidate_directory(struct untracked_cache *uc,
 +                               struct untracked_cache_dir *dir)
 +{
 +      int i;
 +      uc->dir_invalidated++;
 +      dir->valid = 0;
 +      dir->untracked_nr = 0;
 +      for (i = 0; i < dir->dirs_nr; i++)
 +              dir->dirs[i]->recurse = 0;
 +}
 +
 +/*
 + * Given a file with name "fname", read it (either from disk, or from
 + * the index if "check_index" is non-zero), parse it and store the
 + * exclude rules in "el".
 + *
 + * If "ss" is not NULL, compute SHA-1 of the exclude file and fill
 + * stat data from disk (only valid if add_excludes returns zero). If
 + * ss_valid is non-zero, "ss" must contain good value as input.
 + */
 +static int add_excludes(const char *fname, const char *base, int baselen,
 +                      struct exclude_list *el, int check_index,
 +                      struct sha1_stat *sha1_stat)
  {
        struct stat st;
        int fd, i, lineno = 1;
                if (0 <= fd)
                        close(fd);
                if (!check_index ||
 -                  (buf = read_skip_worktree_file_from_index(fname, &size)) == NULL)
 +                  (buf = read_skip_worktree_file_from_index(fname, &size, sha1_stat)) == NULL)
                        return -1;
                if (size == 0) {
                        free(buf);
        } else {
                size = xsize_t(st.st_size);
                if (size == 0) {
 +                      if (sha1_stat) {
 +                              fill_stat_data(&sha1_stat->stat, &st);
 +                              hashcpy(sha1_stat->sha1, EMPTY_BLOB_SHA1_BIN);
 +                              sha1_stat->valid = 1;
 +                      }
                        close(fd);
                        return 0;
                }
                }
                buf[size++] = '\n';
                close(fd);
 +              if (sha1_stat) {
 +                      int pos;
 +                      if (sha1_stat->valid &&
 +                          !match_stat_data_racy(&the_index, &sha1_stat->stat, &st))
 +                              ; /* no content change, ss->sha1 still good */
 +                      else if (check_index &&
 +                               (pos = cache_name_pos(fname, strlen(fname))) >= 0 &&
 +                               !ce_stage(active_cache[pos]) &&
 +                               ce_uptodate(active_cache[pos]) &&
 +                               !would_convert_to_git(fname))
 +                              hashcpy(sha1_stat->sha1, active_cache[pos]->sha1);
 +                      else
 +                              hash_sha1_file(buf, size, "blob", sha1_stat->sha1);
 +                      fill_stat_data(&sha1_stat->stat, &st);
 +                      sha1_stat->valid = 1;
 +              }
        }
  
        el->filebuf = buf;
 +
 +      if (skip_utf8_bom(&buf, size))
 +              size -= buf - el->filebuf;
 +
        entry = buf;
 +
        for (i = 0; i < size; i++) {
                if (buf[i] == '\n') {
                        if (entry != buf + i && entry[0] != '#') {
        return 0;
  }
  
 +int add_excludes_from_file_to_list(const char *fname, const char *base,
 +                                 int baselen, struct exclude_list *el,
 +                                 int check_index)
 +{
 +      return add_excludes(fname, base, baselen, el, check_index, NULL);
 +}
 +
  struct exclude_list *add_exclude_list(struct dir_struct *dir,
                                      int group_type, const char *src)
  {
  /*
   * Used to set up core.excludesfile and .git/info/exclude lists.
   */
 -void add_excludes_from_file(struct dir_struct *dir, const char *fname)
 +static void add_excludes_from_file_1(struct dir_struct *dir, const char *fname,
 +                                   struct sha1_stat *sha1_stat)
  {
        struct exclude_list *el;
 +      /*
 +       * catch setup_standard_excludes() that's called before
 +       * dir->untracked is assigned. That function behaves
 +       * differently when dir->untracked is non-NULL.
 +       */
 +      if (!dir->untracked)
 +              dir->unmanaged_exclude_files++;
        el = add_exclude_list(dir, EXC_FILE, fname);
 -      if (add_excludes_from_file_to_list(fname, "", 0, el, 0) < 0)
 +      if (add_excludes(fname, "", 0, el, 0, sha1_stat) < 0)
                die("cannot use %s as an exclude file", fname);
  }
  
 +void add_excludes_from_file(struct dir_struct *dir, const char *fname)
 +{
 +      dir->unmanaged_exclude_files++; /* see validate_untracked_cache() */
 +      add_excludes_from_file_1(dir, fname, NULL);
 +}
 +
  int match_basename(const char *basename, int basenamelen,
                   const char *pattern, int prefix, int patternlen,
                   int flags)
@@@ -980,7 -788,6 +980,7 @@@ static void prep_exclude(struct dir_str
        struct exclude_list_group *group;
        struct exclude_list *el;
        struct exclude_stack *stk = NULL;
 +      struct untracked_cache_dir *untracked;
        int current;
  
        group = &dir->exclude_list_group[EXC_DIRS];
        /* Read from the parent directories and push them down. */
        current = stk ? stk->baselen : -1;
        strbuf_setlen(&dir->basebuf, current < 0 ? 0 : current);
 +      if (dir->untracked)
 +              untracked = stk ? stk->ucd : dir->untracked->root;
 +      else
 +              untracked = NULL;
 +
        while (current < baselen) {
                const char *cp;
 +              struct sha1_stat sha1_stat;
  
                stk = xcalloc(1, sizeof(*stk));
                if (current < 0) {
                        if (!cp)
                                die("oops in prep_exclude");
                        cp++;
 +                      untracked =
 +                              lookup_untracked(dir->untracked, untracked,
 +                                               base + current,
 +                                               cp - base - current);
                }
                stk->prev = dir->exclude_stack;
                stk->baselen = cp - base;
                stk->exclude_ix = group->nr;
 +              stk->ucd = untracked;
                el = add_exclude_list(dir, EXC_DIRS, NULL);
                strbuf_add(&dir->basebuf, base + current, stk->baselen - current);
                assert(stk->baselen == dir->basebuf.len);
                }
  
                /* Try to read per-directory file */
 -              if (dir->exclude_per_dir) {
 +              hashclr(sha1_stat.sha1);
 +              sha1_stat.valid = 0;
 +              if (dir->exclude_per_dir &&
 +                  /*
 +                   * If we know that no files have been added in
 +                   * this directory (i.e. valid_cached_dir() has
 +                   * been executed and set untracked->valid) ..
 +                   */
 +                  (!untracked || !untracked->valid ||
 +                   /*
 +                    * .. and .gitignore does not exist before
 +                    * (i.e. null exclude_sha1). Then we can skip
 +                    * loading .gitignore, which would result in
 +                    * ENOENT anyway.
 +                    */
 +                   !is_null_sha1(untracked->exclude_sha1))) {
                        /*
                         * dir->basebuf gets reused by the traversal, but we
                         * need fname to remain unchanged to ensure the src
                        strbuf_addbuf(&sb, &dir->basebuf);
                        strbuf_addstr(&sb, dir->exclude_per_dir);
                        el->src = strbuf_detach(&sb, NULL);
 -                      add_excludes_from_file_to_list(el->src, el->src,
 -                                                     stk->baselen, el, 1);
 +                      add_excludes(el->src, el->src, stk->baselen, el, 1,
 +                                   untracked ? &sha1_stat : NULL);
 +              }
 +              /*
 +               * NEEDSWORK: when untracked cache is enabled, prep_exclude()
 +               * will first be called in valid_cached_dir() then maybe many
 +               * times more in last_exclude_matching(). When the cache is
 +               * used, last_exclude_matching() will not be called and
 +               * reading .gitignore content will be a waste.
 +               *
 +               * So when it's called by valid_cached_dir() and we can get
 +               * .gitignore SHA-1 from the index (i.e. .gitignore is not
 +               * modified on work tree), we could delay reading the
 +               * .gitignore content until we absolutely need it in
 +               * last_exclude_matching(). Be careful about ignore rule
 +               * order, though, if you do that.
 +               */
 +              if (untracked &&
 +                  hashcmp(sha1_stat.sha1, untracked->exclude_sha1)) {
 +                      invalidate_gitignore(dir->untracked, untracked);
 +                      hashcpy(untracked->exclude_sha1, sha1_stat.sha1);
                }
                dir->exclude_stack = stk;
                current = stk->baselen;
@@@ -1202,29 -964,15 +1202,15 @@@ enum exist_status 
   */
  static enum exist_status directory_exists_in_index_icase(const char *dirname, int len)
  {
-       const struct cache_entry *ce = cache_dir_exists(dirname, len);
-       unsigned char endchar;
+       struct cache_entry *ce;
  
-       if (!ce)
-               return index_nonexistent;
-       endchar = ce->name[len];
-       /*
-        * The cache_entry structure returned will contain this dirname
-        * and possibly additional path components.
-        */
-       if (endchar == '/')
+       if (cache_dir_exists(dirname, len))
                return index_directory;
  
-       /*
-        * If there are no additional path components, then this cache_entry
-        * represents a submodule.  Submodules, despite being directories,
-        * are stored in the cache without a closing slash.
-        */
-       if (!endchar && S_ISGITLINK(ce->ce_mode))
+       ce = cache_file_exists(dirname, len, ignore_case);
+       if (ce && S_ISGITLINK(ce->ce_mode))
                return index_gitdir;
  
-       /* This should never be hit, but it exists just in case. */
        return index_nonexistent;
  }
  
@@@ -1296,8 -1044,7 +1282,8 @@@ static enum exist_status directory_exis
   *  (c) otherwise, we recurse into it.
   */
  static enum path_treatment treat_directory(struct dir_struct *dir,
 -      const char *dirname, int len, int exclude,
 +      struct untracked_cache_dir *untracked,
 +      const char *dirname, int len, int baselen, int exclude,
        const struct path_simplify *simplify)
  {
        /* The "len-1" is to strip the final '/' */
        if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
                return exclude ? path_excluded : path_untracked;
  
 -      return read_directory_recursive(dir, dirname, len, 1, simplify);
 +      untracked = lookup_untracked(dir->untracked, untracked,
 +                                   dirname + baselen, len - baselen);
 +      return read_directory_recursive(dir, dirname, len,
 +                                      untracked, 1, simplify);
  }
  
  /*
@@@ -1443,9 -1187,7 +1429,9 @@@ static int get_dtype(struct dirent *de
  }
  
  static enum path_treatment treat_one_path(struct dir_struct *dir,
 +                                        struct untracked_cache_dir *untracked,
                                          struct strbuf *path,
 +                                        int baselen,
                                          const struct path_simplify *simplify,
                                          int dtype, struct dirent *de)
  {
                return path_none;
        case DT_DIR:
                strbuf_addch(path, '/');
 -              return treat_directory(dir, path->buf, path->len, exclude,
 -                      simplify);
 +              return treat_directory(dir, untracked, path->buf, path->len,
 +                                     baselen, exclude, simplify);
        case DT_REG:
        case DT_LNK:
                return exclude ? path_excluded : path_untracked;
        }
  }
  
 +static enum path_treatment treat_path_fast(struct dir_struct *dir,
 +                                         struct untracked_cache_dir *untracked,
 +                                         struct cached_dir *cdir,
 +                                         struct strbuf *path,
 +                                         int baselen,
 +                                         const struct path_simplify *simplify)
 +{
 +      strbuf_setlen(path, baselen);
 +      if (!cdir->ucd) {
 +              strbuf_addstr(path, cdir->file);
 +              return path_untracked;
 +      }
 +      strbuf_addstr(path, cdir->ucd->name);
 +      /* treat_one_path() does this before it calls treat_directory() */
 +      if (path->buf[path->len - 1] != '/')
 +              strbuf_addch(path, '/');
 +      if (cdir->ucd->check_only)
 +              /*
 +               * check_only is set as a result of treat_directory() getting
 +               * to its bottom. Verify again the same set of directories
 +               * with check_only set.
 +               */
 +              return read_directory_recursive(dir, path->buf, path->len,
 +                                              cdir->ucd, 1, simplify);
 +      /*
 +       * We get path_recurse in the first run when
 +       * directory_exists_in_index() returns index_nonexistent. We
 +       * are sure that new changes in the index does not impact the
 +       * outcome. Return now.
 +       */
 +      return path_recurse;
 +}
 +
  static enum path_treatment treat_path(struct dir_struct *dir,
 -                                    struct dirent *de,
 +                                    struct untracked_cache_dir *untracked,
 +                                    struct cached_dir *cdir,
                                      struct strbuf *path,
                                      int baselen,
                                      const struct path_simplify *simplify)
  {
        int dtype;
 +      struct dirent *de = cdir->de;
  
 +      if (!de)
 +              return treat_path_fast(dir, untracked, cdir, path,
 +                                     baselen, simplify);
        if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
                return path_none;
        strbuf_setlen(path, baselen);
                return path_none;
  
        dtype = DTYPE(de);
 -      return treat_one_path(dir, path, simplify, dtype, de);
 +      return treat_one_path(dir, untracked, path, baselen, simplify, dtype, de);
 +}
 +
 +static void add_untracked(struct untracked_cache_dir *dir, const char *name)
 +{
 +      if (!dir)
 +              return;
 +      ALLOC_GROW(dir->untracked, dir->untracked_nr + 1,
 +                 dir->untracked_alloc);
 +      dir->untracked[dir->untracked_nr++] = xstrdup(name);
 +}
 +
 +static int valid_cached_dir(struct dir_struct *dir,
 +                          struct untracked_cache_dir *untracked,
 +                          struct strbuf *path,
 +                          int check_only)
 +{
 +      struct stat st;
 +
 +      if (!untracked)
 +              return 0;
 +
 +      if (stat(path->len ? path->buf : ".", &st)) {
 +              invalidate_directory(dir->untracked, untracked);
 +              memset(&untracked->stat_data, 0, sizeof(untracked->stat_data));
 +              return 0;
 +      }
 +      if (!untracked->valid ||
 +          match_stat_data_racy(&the_index, &untracked->stat_data, &st)) {
 +              if (untracked->valid)
 +                      invalidate_directory(dir->untracked, untracked);
 +              fill_stat_data(&untracked->stat_data, &st);
 +              return 0;
 +      }
 +
 +      if (untracked->check_only != !!check_only) {
 +              invalidate_directory(dir->untracked, untracked);
 +              return 0;
 +      }
 +
 +      /*
 +       * prep_exclude will be called eventually on this directory,
 +       * but it's called much later in last_exclude_matching(). We
 +       * need it now to determine the validity of the cache for this
 +       * path. The next calls will be nearly no-op, the way
 +       * prep_exclude() is designed.
 +       */
 +      if (path->len && path->buf[path->len - 1] != '/') {
 +              strbuf_addch(path, '/');
 +              prep_exclude(dir, path->buf, path->len);
 +              strbuf_setlen(path, path->len - 1);
 +      } else
 +              prep_exclude(dir, path->buf, path->len);
 +
 +      /* hopefully prep_exclude() haven't invalidated this entry... */
 +      return untracked->valid;
 +}
 +
 +static int open_cached_dir(struct cached_dir *cdir,
 +                         struct dir_struct *dir,
 +                         struct untracked_cache_dir *untracked,
 +                         struct strbuf *path,
 +                         int check_only)
 +{
 +      memset(cdir, 0, sizeof(*cdir));
 +      cdir->untracked = untracked;
 +      if (valid_cached_dir(dir, untracked, path, check_only))
 +              return 0;
 +      cdir->fdir = opendir(path->len ? path->buf : ".");
 +      if (dir->untracked)
 +              dir->untracked->dir_opened++;
 +      if (!cdir->fdir)
 +              return -1;
 +      return 0;
 +}
 +
 +static int read_cached_dir(struct cached_dir *cdir)
 +{
 +      if (cdir->fdir) {
 +              cdir->de = readdir(cdir->fdir);
 +              if (!cdir->de)
 +                      return -1;
 +              return 0;
 +      }
 +      while (cdir->nr_dirs < cdir->untracked->dirs_nr) {
 +              struct untracked_cache_dir *d = cdir->untracked->dirs[cdir->nr_dirs];
 +              if (!d->recurse) {
 +                      cdir->nr_dirs++;
 +                      continue;
 +              }
 +              cdir->ucd = d;
 +              cdir->nr_dirs++;
 +              return 0;
 +      }
 +      cdir->ucd = NULL;
 +      if (cdir->nr_files < cdir->untracked->untracked_nr) {
 +              struct untracked_cache_dir *d = cdir->untracked;
 +              cdir->file = d->untracked[cdir->nr_files++];
 +              return 0;
 +      }
 +      return -1;
 +}
 +
 +static void close_cached_dir(struct cached_dir *cdir)
 +{
 +      if (cdir->fdir)
 +              closedir(cdir->fdir);
 +      /*
 +       * We have gone through this directory and found no untracked
 +       * entries. Mark it valid.
 +       */
 +      if (cdir->untracked) {
 +              cdir->untracked->valid = 1;
 +              cdir->untracked->recurse = 1;
 +      }
  }
  
  /*
   */
  static enum path_treatment read_directory_recursive(struct dir_struct *dir,
                                    const char *base, int baselen,
 -                                  int check_only,
 +                                  struct untracked_cache_dir *untracked, int check_only,
                                    const struct path_simplify *simplify)
  {
 -      DIR *fdir;
 +      struct cached_dir cdir;
        enum path_treatment state, subdir_state, dir_state = path_none;
 -      struct dirent *de;
        struct strbuf path = STRBUF_INIT;
  
        strbuf_add(&path, base, baselen);
  
 -      fdir = opendir(path.len ? path.buf : ".");
 -      if (!fdir)
 +      if (open_cached_dir(&cdir, dir, untracked, &path, check_only))
                goto out;
  
 -      while ((de = readdir(fdir)) != NULL) {
 +      if (untracked)
 +              untracked->check_only = !!check_only;
 +
 +      while (!read_cached_dir(&cdir)) {
                /* check how the file or directory should be treated */
 -              state = treat_path(dir, de, &path, baselen, simplify);
 +              state = treat_path(dir, untracked, &cdir, &path, baselen, simplify);
 +
                if (state > dir_state)
                        dir_state = state;
  
                /* recurse into subdir if instructed by treat_path */
                if (state == path_recurse) {
 -                      subdir_state = read_directory_recursive(dir, path.buf,
 -                              path.len, check_only, simplify);
 +                      struct untracked_cache_dir *ud;
 +                      ud = lookup_untracked(dir->untracked, untracked,
 +                                            path.buf + baselen,
 +                                            path.len - baselen);
 +                      subdir_state =
 +                              read_directory_recursive(dir, path.buf, path.len,
 +                                                       ud, check_only, simplify);
                        if (subdir_state > dir_state)
                                dir_state = subdir_state;
                }
  
                if (check_only) {
                        /* abort early if maximum state has been reached */
 -                      if (dir_state == path_untracked)
 +                      if (dir_state == path_untracked) {
 +                              if (cdir.fdir)
 +                                      add_untracked(untracked, path.buf + baselen);
                                break;
 +                      }
                        /* skip the dir_add_* part */
                        continue;
                }
                        break;
  
                case path_untracked:
 -                      if (!(dir->flags & DIR_SHOW_IGNORED))
 -                              dir_add_name(dir, path.buf, path.len);
 +                      if (dir->flags & DIR_SHOW_IGNORED)
 +                              break;
 +                      dir_add_name(dir, path.buf, path.len);
 +                      if (cdir.fdir)
 +                              add_untracked(untracked, path.buf + baselen);
                        break;
  
                default:
                        break;
                }
        }
 -      closedir(fdir);
 +      close_cached_dir(&cdir);
   out:
        strbuf_release(&path);
  
@@@ -1829,7 -1406,7 +1815,7 @@@ static int treat_leading_path(struct di
                        break;
                if (simplify_away(sb.buf, sb.len, simplify))
                        break;
 -              if (treat_one_path(dir, &sb, simplify,
 +              if (treat_one_path(dir, NULL, &sb, baselen, simplify,
                                   DT_DIR, NULL) == path_none)
                        break; /* do not recurse into it */
                if (len <= baselen) {
        return rc;
  }
  
 +static const char *get_ident_string(void)
 +{
 +      static struct strbuf sb = STRBUF_INIT;
 +      struct utsname uts;
 +
 +      if (sb.len)
 +              return sb.buf;
 +      if (uname(&uts) < 0)
 +              die_errno(_("failed to get kernel name and information"));
 +      strbuf_addf(&sb, "Location %s, system %s %s %s", get_git_work_tree(),
 +                  uts.sysname, uts.release, uts.version);
 +      return sb.buf;
 +}
 +
 +static int ident_in_untracked(const struct untracked_cache *uc)
 +{
 +      const char *end = uc->ident.buf + uc->ident.len;
 +      const char *p   = uc->ident.buf;
 +
 +      for (p = uc->ident.buf; p < end; p += strlen(p) + 1)
 +              if (!strcmp(p, get_ident_string()))
 +                      return 1;
 +      return 0;
 +}
 +
 +void add_untracked_ident(struct untracked_cache *uc)
 +{
 +      if (ident_in_untracked(uc))
 +              return;
 +      strbuf_addstr(&uc->ident, get_ident_string());
 +      /* this strbuf contains a list of strings, save NUL too */
 +      strbuf_addch(&uc->ident, 0);
 +}
 +
 +static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *dir,
 +                                                    int base_len,
 +                                                    const struct pathspec *pathspec)
 +{
 +      struct untracked_cache_dir *root;
 +
 +      if (!dir->untracked || getenv("GIT_DISABLE_UNTRACKED_CACHE"))
 +              return NULL;
 +
 +      /*
 +       * We only support $GIT_DIR/info/exclude and core.excludesfile
 +       * as the global ignore rule files. Any other additions
 +       * (e.g. from command line) invalidate the cache. This
 +       * condition also catches running setup_standard_excludes()
 +       * before setting dir->untracked!
 +       */
 +      if (dir->unmanaged_exclude_files)
 +              return NULL;
 +
 +      /*
 +       * Optimize for the main use case only: whole-tree git
 +       * status. More work involved in treat_leading_path() if we
 +       * use cache on just a subset of the worktree. pathspec
 +       * support could make the matter even worse.
 +       */
 +      if (base_len || (pathspec && pathspec->nr))
 +              return NULL;
 +
 +      /* Different set of flags may produce different results */
 +      if (dir->flags != dir->untracked->dir_flags ||
 +          /*
 +           * See treat_directory(), case index_nonexistent. Without
 +           * this flag, we may need to also cache .git file content
 +           * for the resolve_gitlink_ref() call, which we don't.
 +           */
 +          !(dir->flags & DIR_SHOW_OTHER_DIRECTORIES) ||
 +          /* We don't support collecting ignore files */
 +          (dir->flags & (DIR_SHOW_IGNORED | DIR_SHOW_IGNORED_TOO |
 +                         DIR_COLLECT_IGNORED)))
 +              return NULL;
 +
 +      /*
 +       * If we use .gitignore in the cache and now you change it to
 +       * .gitexclude, everything will go wrong.
 +       */
 +      if (dir->exclude_per_dir != dir->untracked->exclude_per_dir &&
 +          strcmp(dir->exclude_per_dir, dir->untracked->exclude_per_dir))
 +              return NULL;
 +
 +      /*
 +       * EXC_CMDL is not considered in the cache. If people set it,
 +       * skip the cache.
 +       */
 +      if (dir->exclude_list_group[EXC_CMDL].nr)
 +              return NULL;
 +
 +      if (!ident_in_untracked(dir->untracked)) {
 +              warning(_("Untracked cache is disabled on this system."));
 +              return NULL;
 +      }
 +
 +      if (!dir->untracked->root) {
 +              const int len = sizeof(*dir->untracked->root);
 +              dir->untracked->root = xmalloc(len);
 +              memset(dir->untracked->root, 0, len);
 +      }
 +
 +      /* Validate $GIT_DIR/info/exclude and core.excludesfile */
 +      root = dir->untracked->root;
 +      if (hashcmp(dir->ss_info_exclude.sha1,
 +                  dir->untracked->ss_info_exclude.sha1)) {
 +              invalidate_gitignore(dir->untracked, root);
 +              dir->untracked->ss_info_exclude = dir->ss_info_exclude;
 +      }
 +      if (hashcmp(dir->ss_excludes_file.sha1,
 +                  dir->untracked->ss_excludes_file.sha1)) {
 +              invalidate_gitignore(dir->untracked, root);
 +              dir->untracked->ss_excludes_file = dir->ss_excludes_file;
 +      }
 +
 +      /* Make sure this directory is not dropped out at saving phase */
 +      root->recurse = 1;
 +      return root;
 +}
 +
  int read_directory(struct dir_struct *dir, const char *path, int len, const struct pathspec *pathspec)
  {
        struct path_simplify *simplify;
 +      struct untracked_cache_dir *untracked;
  
        /*
         * Check out create_simplify()
         * create_simplify().
         */
        simplify = create_simplify(pathspec ? pathspec->_raw : NULL);
 +      untracked = validate_untracked_cache(dir, len, pathspec);
 +      if (!untracked)
 +              /*
 +               * make sure untracked cache code path is disabled,
 +               * e.g. prep_exclude()
 +               */
 +              dir->untracked = NULL;
        if (!len || treat_leading_path(dir, path, len, simplify))
 -              read_directory_recursive(dir, path, len, 0, simplify);
 +              read_directory_recursive(dir, path, len, untracked, 0, simplify);
        free_simplify(simplify);
        qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
        qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);
 +      if (dir->untracked) {
 +              static struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS);
 +              trace_printf_key(&trace_untracked_stats,
 +                               "node creation: %u\n"
 +                               "gitignore invalidation: %u\n"
 +                               "directory invalidation: %u\n"
 +                               "opendir: %u\n",
 +                               dir->untracked->dir_created,
 +                               dir->untracked->gitignore_invalidated,
 +                               dir->untracked->dir_invalidated,
 +                               dir->untracked->dir_opened);
 +              if (dir->untracked == the_index.untracked &&
 +                  (dir->untracked->dir_opened ||
 +                   dir->untracked->gitignore_invalidated ||
 +                   dir->untracked->dir_invalidated))
 +                      the_index.cache_changed |= UNTRACKED_CHANGED;
 +              if (dir->untracked != the_index.untracked) {
 +                      free(dir->untracked);
 +                      dir->untracked = NULL;
 +              }
 +      }
        return dir->nr;
  }
  
@@@ -2030,15 -1459,6 +2016,15 @@@ int file_exists(const char *f
        return lstat(f, &sb) == 0;
  }
  
 +static int cmp_icase(char a, char b)
 +{
 +      if (a == b)
 +              return 0;
 +      if (ignore_case)
 +              return toupper(a) - toupper(b);
 +      return a - b;
 +}
 +
  /*
   * Given two normalized paths (a trailing slash is ok), if subdir is
   * outside dir, return -1.  Otherwise return the offset in subdir that
@@@ -2050,7 -1470,7 +2036,7 @@@ int dir_inside_of(const char *subdir, c
  
        assert(dir && subdir && *dir && *subdir);
  
 -      while (*dir && *subdir && *dir == *subdir) {
 +      while (*dir && *subdir && !cmp_icase(*dir, *subdir)) {
                dir++;
                subdir++;
                offset++;
@@@ -2185,26 -1605,21 +2171,26 @@@ int remove_dir_recursively(struct strbu
        return remove_dir_recurse(path, flag, NULL);
  }
  
 +static GIT_PATH_FUNC(git_path_info_exclude, "info/exclude")
 +
  void setup_standard_excludes(struct dir_struct *dir)
  {
        const char *path;
 -      char *xdg_path;
  
        dir->exclude_per_dir = ".gitignore";
 -      path = git_path("info/exclude");
 -      if (!excludes_file) {
 -              home_config_paths(NULL, &xdg_path, "ignore");
 -              excludes_file = xdg_path;
 -      }
 -      if (!access_or_warn(path, R_OK, 0))
 -              add_excludes_from_file(dir, path);
 +
 +      /* core.excludefile defaulting to $XDG_HOME/git/ignore */
 +      if (!excludes_file)
 +              excludes_file = xdg_config_home("ignore");
        if (excludes_file && !access_or_warn(excludes_file, R_OK, 0))
 -              add_excludes_from_file(dir, excludes_file);
 +              add_excludes_from_file_1(dir, excludes_file,
 +                                       dir->untracked ? &dir->ss_excludes_file : NULL);
 +
 +      /* per repository user preference */
 +      path = git_path_info_exclude();
 +      if (!access_or_warn(path, R_OK, 0))
 +              add_excludes_from_file_1(dir, path,
 +                                       dir->untracked ? &dir->ss_info_exclude : NULL);
  }
  
  int remove_path(const char *name)
@@@ -2256,448 -1671,3 +2242,448 @@@ void clear_directory(struct dir_struct 
        }
        strbuf_release(&dir->basebuf);
  }
 +
 +struct ondisk_untracked_cache {
 +      struct stat_data info_exclude_stat;
 +      struct stat_data excludes_file_stat;
 +      uint32_t dir_flags;
 +      unsigned char info_exclude_sha1[20];
 +      unsigned char excludes_file_sha1[20];
 +      char exclude_per_dir[FLEX_ARRAY];
 +};
 +
 +#define ouc_size(len) (offsetof(struct ondisk_untracked_cache, exclude_per_dir) + len + 1)
 +
 +struct write_data {
 +      int index;         /* number of written untracked_cache_dir */
 +      struct ewah_bitmap *check_only; /* from untracked_cache_dir */
 +      struct ewah_bitmap *valid;      /* from untracked_cache_dir */
 +      struct ewah_bitmap *sha1_valid; /* set if exclude_sha1 is not null */
 +      struct strbuf out;
 +      struct strbuf sb_stat;
 +      struct strbuf sb_sha1;
 +};
 +
 +static void stat_data_to_disk(struct stat_data *to, const struct stat_data *from)
 +{
 +      to->sd_ctime.sec  = htonl(from->sd_ctime.sec);
 +      to->sd_ctime.nsec = htonl(from->sd_ctime.nsec);
 +      to->sd_mtime.sec  = htonl(from->sd_mtime.sec);
 +      to->sd_mtime.nsec = htonl(from->sd_mtime.nsec);
 +      to->sd_dev        = htonl(from->sd_dev);
 +      to->sd_ino        = htonl(from->sd_ino);
 +      to->sd_uid        = htonl(from->sd_uid);
 +      to->sd_gid        = htonl(from->sd_gid);
 +      to->sd_size       = htonl(from->sd_size);
 +}
 +
 +static void write_one_dir(struct untracked_cache_dir *untracked,
 +                        struct write_data *wd)
 +{
 +      struct stat_data stat_data;
 +      struct strbuf *out = &wd->out;
 +      unsigned char intbuf[16];
 +      unsigned int intlen, value;
 +      int i = wd->index++;
 +
 +      /*
 +       * untracked_nr should be reset whenever valid is clear, but
 +       * for safety..
 +       */
 +      if (!untracked->valid) {
 +              untracked->untracked_nr = 0;
 +              untracked->check_only = 0;
 +      }
 +
 +      if (untracked->check_only)
 +              ewah_set(wd->check_only, i);
 +      if (untracked->valid) {
 +              ewah_set(wd->valid, i);
 +              stat_data_to_disk(&stat_data, &untracked->stat_data);
 +              strbuf_add(&wd->sb_stat, &stat_data, sizeof(stat_data));
 +      }
 +      if (!is_null_sha1(untracked->exclude_sha1)) {
 +              ewah_set(wd->sha1_valid, i);
 +              strbuf_add(&wd->sb_sha1, untracked->exclude_sha1, 20);
 +      }
 +
 +      intlen = encode_varint(untracked->untracked_nr, intbuf);
 +      strbuf_add(out, intbuf, intlen);
 +
 +      /* skip non-recurse directories */
 +      for (i = 0, value = 0; i < untracked->dirs_nr; i++)
 +              if (untracked->dirs[i]->recurse)
 +                      value++;
 +      intlen = encode_varint(value, intbuf);
 +      strbuf_add(out, intbuf, intlen);
 +
 +      strbuf_add(out, untracked->name, strlen(untracked->name) + 1);
 +
 +      for (i = 0; i < untracked->untracked_nr; i++)
 +              strbuf_add(out, untracked->untracked[i],
 +                         strlen(untracked->untracked[i]) + 1);
 +
 +      for (i = 0; i < untracked->dirs_nr; i++)
 +              if (untracked->dirs[i]->recurse)
 +                      write_one_dir(untracked->dirs[i], wd);
 +}
 +
 +void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked)
 +{
 +      struct ondisk_untracked_cache *ouc;
 +      struct write_data wd;
 +      unsigned char varbuf[16];
 +      int len = 0, varint_len;
 +      if (untracked->exclude_per_dir)
 +              len = strlen(untracked->exclude_per_dir);
 +      ouc = xmalloc(sizeof(*ouc) + len + 1);
 +      stat_data_to_disk(&ouc->info_exclude_stat, &untracked->ss_info_exclude.stat);
 +      stat_data_to_disk(&ouc->excludes_file_stat, &untracked->ss_excludes_file.stat);
 +      hashcpy(ouc->info_exclude_sha1, untracked->ss_info_exclude.sha1);
 +      hashcpy(ouc->excludes_file_sha1, untracked->ss_excludes_file.sha1);
 +      ouc->dir_flags = htonl(untracked->dir_flags);
 +      memcpy(ouc->exclude_per_dir, untracked->exclude_per_dir, len + 1);
 +
 +      varint_len = encode_varint(untracked->ident.len, varbuf);
 +      strbuf_add(out, varbuf, varint_len);
 +      strbuf_add(out, untracked->ident.buf, untracked->ident.len);
 +
 +      strbuf_add(out, ouc, ouc_size(len));
 +      free(ouc);
 +      ouc = NULL;
 +
 +      if (!untracked->root) {
 +              varint_len = encode_varint(0, varbuf);
 +              strbuf_add(out, varbuf, varint_len);
 +              return;
 +      }
 +
 +      wd.index      = 0;
 +      wd.check_only = ewah_new();
 +      wd.valid      = ewah_new();
 +      wd.sha1_valid = ewah_new();
 +      strbuf_init(&wd.out, 1024);
 +      strbuf_init(&wd.sb_stat, 1024);
 +      strbuf_init(&wd.sb_sha1, 1024);
 +      write_one_dir(untracked->root, &wd);
 +
 +      varint_len = encode_varint(wd.index, varbuf);
 +      strbuf_add(out, varbuf, varint_len);
 +      strbuf_addbuf(out, &wd.out);
 +      ewah_serialize_strbuf(wd.valid, out);
 +      ewah_serialize_strbuf(wd.check_only, out);
 +      ewah_serialize_strbuf(wd.sha1_valid, out);
 +      strbuf_addbuf(out, &wd.sb_stat);
 +      strbuf_addbuf(out, &wd.sb_sha1);
 +      strbuf_addch(out, '\0'); /* safe guard for string lists */
 +
 +      ewah_free(wd.valid);
 +      ewah_free(wd.check_only);
 +      ewah_free(wd.sha1_valid);
 +      strbuf_release(&wd.out);
 +      strbuf_release(&wd.sb_stat);
 +      strbuf_release(&wd.sb_sha1);
 +}
 +
 +static void free_untracked(struct untracked_cache_dir *ucd)
 +{
 +      int i;
 +      if (!ucd)
 +              return;
 +      for (i = 0; i < ucd->dirs_nr; i++)
 +              free_untracked(ucd->dirs[i]);
 +      for (i = 0; i < ucd->untracked_nr; i++)
 +              free(ucd->untracked[i]);
 +      free(ucd->untracked);
 +      free(ucd->dirs);
 +      free(ucd);
 +}
 +
 +void free_untracked_cache(struct untracked_cache *uc)
 +{
 +      if (uc)
 +              free_untracked(uc->root);
 +      free(uc);
 +}
 +
 +struct read_data {
 +      int index;
 +      struct untracked_cache_dir **ucd;
 +      struct ewah_bitmap *check_only;
 +      struct ewah_bitmap *valid;
 +      struct ewah_bitmap *sha1_valid;
 +      const unsigned char *data;
 +      const unsigned char *end;
 +};
 +
 +static void stat_data_from_disk(struct stat_data *to, const struct stat_data *from)
 +{
 +      to->sd_ctime.sec  = get_be32(&from->sd_ctime.sec);
 +      to->sd_ctime.nsec = get_be32(&from->sd_ctime.nsec);
 +      to->sd_mtime.sec  = get_be32(&from->sd_mtime.sec);
 +      to->sd_mtime.nsec = get_be32(&from->sd_mtime.nsec);
 +      to->sd_dev        = get_be32(&from->sd_dev);
 +      to->sd_ino        = get_be32(&from->sd_ino);
 +      to->sd_uid        = get_be32(&from->sd_uid);
 +      to->sd_gid        = get_be32(&from->sd_gid);
 +      to->sd_size       = get_be32(&from->sd_size);
 +}
 +
 +static int read_one_dir(struct untracked_cache_dir **untracked_,
 +                      struct read_data *rd)
 +{
 +      struct untracked_cache_dir ud, *untracked;
 +      const unsigned char *next, *data = rd->data, *end = rd->end;
 +      unsigned int value;
 +      int i, len;
 +
 +      memset(&ud, 0, sizeof(ud));
 +
 +      next = data;
 +      value = decode_varint(&next);
 +      if (next > end)
 +              return -1;
 +      ud.recurse         = 1;
 +      ud.untracked_alloc = value;
 +      ud.untracked_nr    = value;
 +      if (ud.untracked_nr)
 +              ud.untracked = xmalloc(sizeof(*ud.untracked) * ud.untracked_nr);
 +      data = next;
 +
 +      next = data;
 +      ud.dirs_alloc = ud.dirs_nr = decode_varint(&next);
 +      if (next > end)
 +              return -1;
 +      ud.dirs = xmalloc(sizeof(*ud.dirs) * ud.dirs_nr);
 +      data = next;
 +
 +      len = strlen((const char *)data);
 +      next = data + len + 1;
 +      if (next > rd->end)
 +              return -1;
 +      *untracked_ = untracked = xmalloc(sizeof(*untracked) + len);
 +      memcpy(untracked, &ud, sizeof(ud));
 +      memcpy(untracked->name, data, len + 1);
 +      data = next;
 +
 +      for (i = 0; i < untracked->untracked_nr; i++) {
 +              len = strlen((const char *)data);
 +              next = data + len + 1;
 +              if (next > rd->end)
 +                      return -1;
 +              untracked->untracked[i] = xstrdup((const char*)data);
 +              data = next;
 +      }
 +
 +      rd->ucd[rd->index++] = untracked;
 +      rd->data = data;
 +
 +      for (i = 0; i < untracked->dirs_nr; i++) {
 +              len = read_one_dir(untracked->dirs + i, rd);
 +              if (len < 0)
 +                      return -1;
 +      }
 +      return 0;
 +}
 +
 +static void set_check_only(size_t pos, void *cb)
 +{
 +      struct read_data *rd = cb;
 +      struct untracked_cache_dir *ud = rd->ucd[pos];
 +      ud->check_only = 1;
 +}
 +
 +static void read_stat(size_t pos, void *cb)
 +{
 +      struct read_data *rd = cb;
 +      struct untracked_cache_dir *ud = rd->ucd[pos];
 +      if (rd->data + sizeof(struct stat_data) > rd->end) {
 +              rd->data = rd->end + 1;
 +              return;
 +      }
 +      stat_data_from_disk(&ud->stat_data, (struct stat_data *)rd->data);
 +      rd->data += sizeof(struct stat_data);
 +      ud->valid = 1;
 +}
 +
 +static void read_sha1(size_t pos, void *cb)
 +{
 +      struct read_data *rd = cb;
 +      struct untracked_cache_dir *ud = rd->ucd[pos];
 +      if (rd->data + 20 > rd->end) {
 +              rd->data = rd->end + 1;
 +              return;
 +      }
 +      hashcpy(ud->exclude_sha1, rd->data);
 +      rd->data += 20;
 +}
 +
 +static void load_sha1_stat(struct sha1_stat *sha1_stat,
 +                         const struct stat_data *stat,
 +                         const unsigned char *sha1)
 +{
 +      stat_data_from_disk(&sha1_stat->stat, stat);
 +      hashcpy(sha1_stat->sha1, sha1);
 +      sha1_stat->valid = 1;
 +}
 +
 +struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz)
 +{
 +      const struct ondisk_untracked_cache *ouc;
 +      struct untracked_cache *uc;
 +      struct read_data rd;
 +      const unsigned char *next = data, *end = (const unsigned char *)data + sz;
 +      const char *ident;
 +      int ident_len, len;
 +
 +      if (sz <= 1 || end[-1] != '\0')
 +              return NULL;
 +      end--;
 +
 +      ident_len = decode_varint(&next);
 +      if (next + ident_len > end)
 +              return NULL;
 +      ident = (const char *)next;
 +      next += ident_len;
 +
 +      ouc = (const struct ondisk_untracked_cache *)next;
 +      if (next + ouc_size(0) > end)
 +              return NULL;
 +
 +      uc = xcalloc(1, sizeof(*uc));
 +      strbuf_init(&uc->ident, ident_len);
 +      strbuf_add(&uc->ident, ident, ident_len);
 +      load_sha1_stat(&uc->ss_info_exclude, &ouc->info_exclude_stat,
 +                     ouc->info_exclude_sha1);
 +      load_sha1_stat(&uc->ss_excludes_file, &ouc->excludes_file_stat,
 +                     ouc->excludes_file_sha1);
 +      uc->dir_flags = get_be32(&ouc->dir_flags);
 +      uc->exclude_per_dir = xstrdup(ouc->exclude_per_dir);
 +      /* NUL after exclude_per_dir is covered by sizeof(*ouc) */
 +      next += ouc_size(strlen(ouc->exclude_per_dir));
 +      if (next >= end)
 +              goto done2;
 +
 +      len = decode_varint(&next);
 +      if (next > end || len == 0)
 +              goto done2;
 +
 +      rd.valid      = ewah_new();
 +      rd.check_only = ewah_new();
 +      rd.sha1_valid = ewah_new();
 +      rd.data       = next;
 +      rd.end        = end;
 +      rd.index      = 0;
 +      rd.ucd        = xmalloc(sizeof(*rd.ucd) * len);
 +
 +      if (read_one_dir(&uc->root, &rd) || rd.index != len)
 +              goto done;
 +
 +      next = rd.data;
 +      len = ewah_read_mmap(rd.valid, next, end - next);
 +      if (len < 0)
 +              goto done;
 +
 +      next += len;
 +      len = ewah_read_mmap(rd.check_only, next, end - next);
 +      if (len < 0)
 +              goto done;
 +
 +      next += len;
 +      len = ewah_read_mmap(rd.sha1_valid, next, end - next);
 +      if (len < 0)
 +              goto done;
 +
 +      ewah_each_bit(rd.check_only, set_check_only, &rd);
 +      rd.data = next + len;
 +      ewah_each_bit(rd.valid, read_stat, &rd);
 +      ewah_each_bit(rd.sha1_valid, read_sha1, &rd);
 +      next = rd.data;
 +
 +done:
 +      free(rd.ucd);
 +      ewah_free(rd.valid);
 +      ewah_free(rd.check_only);
 +      ewah_free(rd.sha1_valid);
 +done2:
 +      if (next != end) {
 +              free_untracked_cache(uc);
 +              uc = NULL;
 +      }
 +      return uc;
 +}
 +
 +static void invalidate_one_directory(struct untracked_cache *uc,
 +                                   struct untracked_cache_dir *ucd)
 +{
 +      uc->dir_invalidated++;
 +      ucd->valid = 0;
 +      ucd->untracked_nr = 0;
 +}
 +
 +/*
 + * Normally when an entry is added or removed from a directory,
 + * invalidating that directory is enough. No need to touch its
 + * ancestors. When a directory is shown as "foo/bar/" in git-status
 + * however, deleting or adding an entry may have cascading effect.
 + *
 + * Say the "foo/bar/file" has become untracked, we need to tell the
 + * untracked_cache_dir of "foo" that "bar/" is not an untracked
 + * directory any more (because "bar" is managed by foo as an untracked
 + * "file").
 + *
 + * Similarly, if "foo/bar/file" moves from untracked to tracked and it
 + * was the last untracked entry in the entire "foo", we should show
 + * "foo/" instead. Which means we have to invalidate past "bar" up to
 + * "foo".
 + *
 + * This function traverses all directories from root to leaf. If there
 + * is a chance of one of the above cases happening, we invalidate back
 + * to root. Otherwise we just invalidate the leaf. There may be a more
 + * sophisticated way than checking for SHOW_OTHER_DIRECTORIES to
 + * detect these cases and avoid unnecessary invalidation, for example,
 + * checking for the untracked entry named "bar/" in "foo", but for now
 + * stick to something safe and simple.
 + */
 +static int invalidate_one_component(struct untracked_cache *uc,
 +                                  struct untracked_cache_dir *dir,
 +                                  const char *path, int len)
 +{
 +      const char *rest = strchr(path, '/');
 +
 +      if (rest) {
 +              int component_len = rest - path;
 +              struct untracked_cache_dir *d =
 +                      lookup_untracked(uc, dir, path, component_len);
 +              int ret =
 +                      invalidate_one_component(uc, d, rest + 1,
 +                                               len - (component_len + 1));
 +              if (ret)
 +                      invalidate_one_directory(uc, dir);
 +              return ret;
 +      }
 +
 +      invalidate_one_directory(uc, dir);
 +      return uc->dir_flags & DIR_SHOW_OTHER_DIRECTORIES;
 +}
 +
 +void untracked_cache_invalidate_path(struct index_state *istate,
 +                                   const char *path)
 +{
 +      if (!istate->untracked || !istate->untracked->root)
 +              return;
 +      invalidate_one_component(istate->untracked, istate->untracked->root,
 +                               path, strlen(path));
 +}
 +
 +void untracked_cache_remove_from_index(struct index_state *istate,
 +                                     const char *path)
 +{
 +      untracked_cache_invalidate_path(istate, path);
 +}
 +
 +void untracked_cache_add_to_index(struct index_state *istate,
 +                                const char *path)
 +{
 +      untracked_cache_invalidate_path(istate, path);
 +}
diff --combined read-cache.c
index 87204a50a5cfe2acbdb0563a68f88303a5eb7ae7,3c388c35aea1652af196b0a1e9aa630822809b90..de22df2e20166e9c0a0d3da69525612259a99363
@@@ -5,7 -5,6 +5,7 @@@
   */
  #define NO_THE_INDEX_COMPATIBILITY_MACROS
  #include "cache.h"
 +#include "tempfile.h"
  #include "lockfile.h"
  #include "cache-tree.h"
  #include "refs.h"
@@@ -40,12 -39,11 +40,12 @@@ static struct cache_entry *refresh_cach
  #define CACHE_EXT_TREE 0x54524545     /* "TREE" */
  #define CACHE_EXT_RESOLVE_UNDO 0x52455543 /* "REUC" */
  #define CACHE_EXT_LINK 0x6c696e6b       /* "link" */
 +#define CACHE_EXT_UNTRACKED 0x554E5452          /* "UNTR" */
  
  /* changes that can be kept in $GIT_DIR/index (basically all extensions) */
  #define EXTMASK (RESOLVE_UNDO_CHANGED | CACHE_TREE_CHANGED | \
                 CE_ENTRY_ADDED | CE_ENTRY_REMOVED | CE_ENTRY_CHANGED | \
 -               SPLIT_INDEX_ORDERED)
 +               SPLIT_INDEX_ORDERED | UNTRACKED_CHANGED)
  
  struct index_state the_index;
  static const char *alternate_index_output;
@@@ -81,7 -79,6 +81,7 @@@ void rename_index_entry_at(struct index
        memcpy(new->name, new_name, namelen + 1);
  
        cache_tree_invalidate_path(istate, old->name);
 +      untracked_cache_remove_from_index(istate, old->name);
        remove_index_entry_at(istate, nr);
        add_index_entry(istate, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
  }
@@@ -273,34 -270,20 +273,34 @@@ static int ce_match_stat_basic(const st
        return changed;
  }
  
 -static int is_racy_timestamp(const struct index_state *istate,
 -                           const struct cache_entry *ce)
 +static int is_racy_stat(const struct index_state *istate,
 +                      const struct stat_data *sd)
  {
 -      return (!S_ISGITLINK(ce->ce_mode) &&
 -              istate->timestamp.sec &&
 +      return (istate->timestamp.sec &&
  #ifdef USE_NSEC
                 /* nanosecond timestamped files can also be racy! */
 -              (istate->timestamp.sec < ce->ce_stat_data.sd_mtime.sec ||
 -               (istate->timestamp.sec == ce->ce_stat_data.sd_mtime.sec &&
 -                istate->timestamp.nsec <= ce->ce_stat_data.sd_mtime.nsec))
 +              (istate->timestamp.sec < sd->sd_mtime.sec ||
 +               (istate->timestamp.sec == sd->sd_mtime.sec &&
 +                istate->timestamp.nsec <= sd->sd_mtime.nsec))
  #else
 -              istate->timestamp.sec <= ce->ce_stat_data.sd_mtime.sec
 +              istate->timestamp.sec <= sd->sd_mtime.sec
  #endif
 -               );
 +              );
 +}
 +
 +static int is_racy_timestamp(const struct index_state *istate,
 +                           const struct cache_entry *ce)
 +{
 +      return (!S_ISGITLINK(ce->ce_mode) &&
 +              is_racy_stat(istate, &ce->ce_stat_data));
 +}
 +
 +int match_stat_data_racy(const struct index_state *istate,
 +                       const struct stat_data *sd, struct stat *st)
 +{
 +      if (is_racy_stat(istate, sd))
 +              return MTIME_CHANGED;
 +      return match_stat_data(sd, st);
  }
  
  int ie_match_stat(const struct index_state *istate,
@@@ -555,7 -538,6 +555,7 @@@ int remove_file_from_index(struct index
        if (pos < 0)
                pos = -pos-1;
        cache_tree_invalidate_path(istate, path);
 +      untracked_cache_remove_from_index(istate, path);
        while (pos < istate->cache_nr && !strcmp(istate->cache[pos]->name, path))
                remove_index_entry_at(istate, pos);
        return 0;
@@@ -679,38 -661,21 +679,24 @@@ int add_to_index(struct index_state *is
         * entry's directory case.
         */
        if (ignore_case) {
-               const char *startPtr = ce->name;
-               const char *ptr = startPtr;
-               while (*ptr) {
-                       while (*ptr && *ptr != '/')
-                               ++ptr;
-                       if (*ptr == '/') {
-                               struct cache_entry *foundce;
-                               ++ptr;
-                               foundce = index_dir_exists(istate, ce->name, ptr - ce->name - 1);
-                               if (foundce) {
-                                       memcpy((void *)startPtr, foundce->name + (startPtr - ce->name), ptr - startPtr);
-                                       startPtr = ptr;
-                               }
-                       }
-               }
+               adjust_dirname_case(istate, ce->name);
        }
  
        alias = index_file_exists(istate, ce->name, ce_namelen(ce), ignore_case);
        if (alias && !ce_stage(alias) && !ie_match_stat(istate, alias, st, ce_option)) {
                /* Nothing changed, really */
 -              free(ce);
                if (!S_ISGITLINK(alias->ce_mode))
                        ce_mark_uptodate(alias);
                alias->ce_flags |= CE_ADDED;
 +
 +              free(ce);
                return 0;
        }
        if (!intent_only) {
 -              if (index_path(ce->sha1, path, st, HASH_WRITE_OBJECT))
 +              if (index_path(ce->sha1, path, st, HASH_WRITE_OBJECT)) {
 +                      free(ce);
                        return error("unable to index file %s", path);
 +              }
        } else
                set_object_name_for_intent_to_add_entry(ce);
  
                    ce->ce_mode == alias->ce_mode);
  
        if (pretend)
 -              ;
 -      else if (add_index_entry(istate, ce, add_option))
 -              return error("unable to add %s to index",path);
 +              free(ce);
 +      else if (add_index_entry(istate, ce, add_option)) {
 +              free(ce);
 +              return error("unable to add %s to index", path);
 +      }
        if (verbose && !was_same)
                printf("add '%s'\n", path);
        return 0;
@@@ -748,7 -711,7 +734,7 @@@ struct cache_entry *make_cache_entry(un
                unsigned int refresh_options)
  {
        int size, len;
 -      struct cache_entry *ce;
 +      struct cache_entry *ce, *ret;
  
        if (!verify_path(path)) {
                error("Invalid path '%s'", path);
        ce->ce_namelen = len;
        ce->ce_mode = create_ce_mode(mode);
  
 -      return refresh_cache_entry(ce, refresh_options);
 +      ret = refresh_cache_entry(ce, refresh_options);
 +      if (ret != ce)
 +              free(ce);
 +      return ret;
  }
  
  int ce_same_name(const struct cache_entry *a, const struct cache_entry *b)
@@@ -1000,9 -960,6 +986,9 @@@ static int add_index_entry_with_check(s
        }
        pos = -pos-1;
  
 +      if (!(option & ADD_CACHE_KEEP_CACHE_TREE))
 +              untracked_cache_add_to_index(istate, ce->name);
 +
        /*
         * Inserting a merged entry ("stage 0") into the index
         * will always replace all non-merged entries..
@@@ -1393,9 -1350,6 +1379,9 @@@ static int read_index_extension(struct 
                if (read_link_extension(istate, data, sz))
                        return -1;
                break;
 +      case CACHE_EXT_UNTRACKED:
 +              istate->untracked = read_untracked_extension(data, sz);
 +              break;
        default:
                if (*ext < 'A' || 'Z' < *ext)
                        return error("index uses %.4s extension, which we do not understand",
@@@ -1512,25 -1466,18 +1498,25 @@@ static struct cache_entry *create_from_
        return ce;
  }
  
 -static void check_ce_order(struct cache_entry *ce, struct cache_entry *next_ce)
 +static void check_ce_order(struct index_state *istate)
  {
 -      int name_compare = strcmp(ce->name, next_ce->name);
 -      if (0 < name_compare)
 -              die("unordered stage entries in index");
 -      if (!name_compare) {
 -              if (!ce_stage(ce))
 -                      die("multiple stage entries for merged file '%s'",
 -                              ce->name);
 -              if (ce_stage(ce) > ce_stage(next_ce))
 -                      die("unordered stage entries for '%s'",
 -                              ce->name);
 +      unsigned int i;
 +
 +      for (i = 1; i < istate->cache_nr; i++) {
 +              struct cache_entry *ce = istate->cache[i - 1];
 +              struct cache_entry *next_ce = istate->cache[i];
 +              int name_compare = strcmp(ce->name, next_ce->name);
 +
 +              if (0 < name_compare)
 +                      die("unordered stage entries in index");
 +              if (!name_compare) {
 +                      if (!ce_stage(ce))
 +                              die("multiple stage entries for merged file '%s'",
 +                                  ce->name);
 +                      if (ce_stage(ce) > ce_stage(next_ce))
 +                              die("unordered stage entries for '%s'",
 +                                  ce->name);
 +              }
        }
  }
  
@@@ -1564,7 -1511,7 +1550,7 @@@ int do_read_index(struct index_state *i
        if (mmap_size < sizeof(struct cache_header) + 20)
                die("index file smaller than expected");
  
 -      mmap = xmmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
 +      mmap = xmmap(NULL, mmap_size, PROT_READ, MAP_PRIVATE, fd, 0);
        if (mmap == MAP_FAILED)
                die_errno("unable to map index file");
        close(fd);
                ce = create_from_disk(disk_ce, &consumed, previous_name);
                set_index_entry(istate, i, ce);
  
 -              if (i > 0)
 -                      check_ce_order(istate->cache[i - 1], ce);
 -
                src_offset += consumed;
        }
        strbuf_release(&previous_name_buf);
@@@ -1638,10 -1588,11 +1624,10 @@@ int read_index_from(struct index_state 
  
        ret = do_read_index(istate, path, 0);
        split_index = istate->split_index;
 -      if (!split_index)
 -              return ret;
 -
 -      if (is_null_sha1(split_index->base_sha1))
 +      if (!split_index || is_null_sha1(split_index->base_sha1)) {
 +              check_ce_order(istate);
                return ret;
 +      }
  
        if (split_index->base)
                discard_index(split_index->base);
                die("broken index, expect %s in %s, got %s",
                    sha1_to_hex(split_index->base_sha1),
                    git_path("sharedindex.%s",
 -                                   sha1_to_hex(split_index->base_sha1)),
 +                           sha1_to_hex(split_index->base_sha1)),
                    sha1_to_hex(split_index->base->sha1));
        merge_base_index(istate);
 +      check_ce_order(istate);
        return ret;
  }
  
@@@ -1691,8 -1641,6 +1677,8 @@@ int discard_index(struct index_state *i
        istate->cache = NULL;
        istate->cache_alloc = 0;
        discard_split_index(istate);
 +      free_untracked_cache(istate->untracked);
 +      istate->untracked = NULL;
        return 0;
  }
  
@@@ -2079,17 -2027,6 +2065,17 @@@ static int do_write_index(struct index_
                if (err)
                        return -1;
        }
 +      if (!strip_extensions && istate->untracked) {
 +              struct strbuf sb = STRBUF_INIT;
 +
 +              write_untracked_extension(&sb, istate->untracked);
 +              err = write_index_ext_header(&c, newfd, CACHE_EXT_UNTRACKED,
 +                                           sb.len) < 0 ||
 +                      ce_write(&c, newfd, sb.buf, sb.len) < 0;
 +              strbuf_release(&sb);
 +              if (err)
 +                      return -1;
 +      }
  
        if (ce_flush(&c, newfd, istate->sha1) || fstat(newfd, &st))
                return -1;
@@@ -2114,7 -2051,7 +2100,7 @@@ static int commit_locked_index(struct l
  static int do_write_locked_index(struct index_state *istate, struct lock_file *lock,
                                 unsigned flags)
  {
 -      int ret = do_write_index(istate, lock->fd, 0);
 +      int ret = do_write_index(istate, get_lock_file_fd(lock), 0);
        if (ret)
                return ret;
        assert((flags & (COMMIT_LOCK | CLOSE_LOCK)) !=
@@@ -2138,27 -2075,54 +2124,27 @@@ static int write_split_index(struct ind
        return ret;
  }
  
 -static char *temporary_sharedindex;
 -
 -static void remove_temporary_sharedindex(void)
 -{
 -      if (temporary_sharedindex) {
 -              unlink_or_warn(temporary_sharedindex);
 -              free(temporary_sharedindex);
 -              temporary_sharedindex = NULL;
 -      }
 -}
 -
 -static void remove_temporary_sharedindex_on_signal(int signo)
 -{
 -      remove_temporary_sharedindex();
 -      sigchain_pop(signo);
 -      raise(signo);
 -}
 +static struct tempfile temporary_sharedindex;
  
  static int write_shared_index(struct index_state *istate,
                              struct lock_file *lock, unsigned flags)
  {
        struct split_index *si = istate->split_index;
 -      static int installed_handler;
        int fd, ret;
  
 -      temporary_sharedindex = git_pathdup("sharedindex_XXXXXX");
 -      fd = mkstemp(temporary_sharedindex);
 +      fd = mks_tempfile(&temporary_sharedindex, git_path("sharedindex_XXXXXX"));
        if (fd < 0) {
 -              free(temporary_sharedindex);
 -              temporary_sharedindex = NULL;
                hashclr(si->base_sha1);
                return do_write_locked_index(istate, lock, flags);
        }
 -      if (!installed_handler) {
 -              atexit(remove_temporary_sharedindex);
 -              sigchain_push_common(remove_temporary_sharedindex_on_signal);
 -      }
        move_cache_to_base_index(istate);
        ret = do_write_index(si->base, fd, 1);
 -      close(fd);
        if (ret) {
 -              remove_temporary_sharedindex();
 +              delete_tempfile(&temporary_sharedindex);
                return ret;
        }
 -      ret = rename(temporary_sharedindex,
 -                   git_path("sharedindex.%s", sha1_to_hex(si->base->sha1)));
 -      free(temporary_sharedindex);
 -      temporary_sharedindex = NULL;
 +      ret = rename_tempfile(&temporary_sharedindex,
 +                            git_path("sharedindex.%s", sha1_to_hex(si->base->sha1)));
        if (!ret)
                hashcpy(si->base_sha1, si->base->sha1);
        return ret;