]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'ls/filter-process-delayed'
authorJunio C Hamano <gitster@pobox.com>
Fri, 11 Aug 2017 20:27:00 +0000 (13:27 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 11 Aug 2017 20:27:00 +0000 (13:27 -0700)
The filter-process interface learned to allow a process with long
latency give a "delayed" response.

* ls/filter-process-delayed:
  convert: add "status=delayed" to filter process protocol
  convert: refactor capabilities negotiation
  convert: move multiple file filter error handling to separate function
  convert: put the flags field before the flag itself for consistent style
  t0021: write "OUT <size>" only on success
  t0021: make debug log file name configurable
  t0021: keep filter log files on comparison

1  2 
Documentation/gitattributes.txt
builtin/checkout.c
cache.h
convert.c
convert.h
unpack-trees.c

index 2a2d7e2a4d2a9cab2af8bd881dd880ca2a52235a,4049a0b9a838b467337ced4cc9437fef0062e606..c4f2be2542d1fcd417c69596dafdf89bc3a93f00
@@@ -229,7 -229,7 +229,7 @@@ From a clean working directory
  
  -------------------------------------------------
  $ echo "* text=auto" >.gitattributes
 -$ rm .git/index     # Remove the index to re-scan the working directory
 +$ git read-tree --empty   # Clean index, force re-scan of working directory
  $ git add .
  $ git status        # Show files that will be normalized
  $ git commit -m "Introduce end-of-line normalization"
@@@ -425,8 -425,8 +425,8 @@@ packet:          git< capability=clea
  packet:          git< capability=smudge
  packet:          git< 0000
  ------------------------
- Supported filter capabilities in version 2 are "clean" and
"smudge".
+ Supported filter capabilities in version 2 are "clean", "smudge",
and "delay".
  
  Afterwards Git sends a list of "key=value" pairs terminated with
  a flush packet. The list will contain at least the filter command
@@@ -512,12 -512,73 +512,73 @@@ the protocol then Git will stop the fil
  with the next file that needs to be processed. Depending on the
  `filter.<driver>.required` flag Git will interpret that as error.
  
- After the filter has processed a blob it is expected to wait for
the next "key=value" list containing a command. Git will close
+ After the filter has processed a command it is expected to wait for
a "key=value" list containing the next command. Git will close
  the command pipe on exit. The filter is expected to detect EOF
  and exit gracefully on its own. Git will wait until the filter
  process has stopped.
  
+ Delay
+ ^^^^^
+ If the filter supports the "delay" capability, then Git can send the
+ flag "can-delay" after the filter command and pathname. This flag
+ denotes that the filter can delay filtering the current blob (e.g. to
+ compensate network latencies) by responding with no content but with
+ the status "delayed" and a flush packet.
+ ------------------------
+ packet:          git> command=smudge
+ packet:          git> pathname=path/testfile.dat
+ packet:          git> can-delay=1
+ packet:          git> 0000
+ packet:          git> CONTENT
+ packet:          git> 0000
+ packet:          git< status=delayed
+ packet:          git< 0000
+ ------------------------
+ If the filter supports the "delay" capability then it must support the
+ "list_available_blobs" command. If Git sends this command, then the
+ filter is expected to return a list of pathnames representing blobs
+ that have been delayed earlier and are now available.
+ The list must be terminated with a flush packet followed
+ by a "success" status that is also terminated with a flush packet. If
+ no blobs for the delayed paths are available, yet, then the filter is
+ expected to block the response until at least one blob becomes
+ available. The filter can tell Git that it has no more delayed blobs
+ by sending an empty list. As soon as the filter responds with an empty
+ list, Git stops asking. All blobs that Git has not received at this
+ point are considered missing and will result in an error.
+ ------------------------
+ packet:          git> command=list_available_blobs
+ packet:          git> 0000
+ packet:          git< pathname=path/testfile.dat
+ packet:          git< pathname=path/otherfile.dat
+ packet:          git< 0000
+ packet:          git< status=success
+ packet:          git< 0000
+ ------------------------
+ After Git received the pathnames, it will request the corresponding
+ blobs again. These requests contain a pathname and an empty content
+ section. The filter is expected to respond with the smudged content
+ in the usual way as explained above.
+ ------------------------
+ packet:          git> command=smudge
+ packet:          git> pathname=path/testfile.dat
+ packet:          git> 0000
+ packet:          git> 0000  # empty content!
+ packet:          git< status=success
+ packet:          git< 0000
+ packet:          git< SMUDGED_CONTENT
+ packet:          git< 0000
+ packet:          git< 0000  # empty list, keep "status=success" unchanged!
+ ------------------------
+ Example
+ ^^^^^^^
  A long running filter demo implementation can be found in
  `contrib/long-running-filter/example.pl` located in the Git
  core repository. If you develop your own long running filter
diff --combined builtin/checkout.c
index 9661e1bcba31ffa4f7b8a2fb1a9d2060cb8efda7,c1a256df8df4e8defbcb945755ff18354cfe656d..2d75ac66c7f8447e846e0bc38072b76bddc3672b
@@@ -1,5 -1,4 +1,5 @@@
  #include "builtin.h"
 +#include "config.h"
  #include "lockfile.h"
  #include "parse-options.h"
  #include "refs.h"
  #include "submodule-config.h"
  #include "submodule.h"
  
 -static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
 -
  static const char * const checkout_usage[] = {
        N_("git checkout [<options>] <branch>"),
        N_("git checkout [<options>] [<branch>] -- <file>..."),
        NULL,
  };
  
 -static int option_parse_recurse_submodules(const struct option *opt,
 -                                         const char *arg, int unset)
 -{
 -      if (unset) {
 -              recurse_submodules = RECURSE_SUBMODULES_OFF;
 -              return 0;
 -      }
 -      if (arg)
 -              recurse_submodules =
 -                      parse_update_recurse_submodules_arg(opt->long_name,
 -                                                          arg);
 -      else
 -              recurse_submodules = RECURSE_SUBMODULES_ON;
 -
 -      return 0;
 -}
 -
  struct checkout_opts {
        int patch_mode;
        int quiet;
@@@ -358,6 -376,8 +358,8 @@@ static int checkout_paths(const struct 
        state.force = 1;
        state.refresh_cache = 1;
        state.istate = &the_index;
+       enable_delayed_checkout(&state);
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
                if (ce->ce_flags & CE_MATCHED) {
                        pos = skip_same_name(ce, pos) - 1;
                }
        }
+       errs |= finish_delayed_checkout(&state);
  
        if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
                die(_("unable to write new index file"));
@@@ -858,7 -879,7 +861,7 @@@ static int git_checkout_config(const ch
        }
  
        if (starts_with(var, "submodule."))
 -              return parse_submodule_config_option(var, value);
 +              return submodule_config(var, value, NULL);
  
        return git_xmerge_config(var, value, NULL);
  }
@@@ -1166,9 -1187,9 +1169,9 @@@ int cmd_checkout(int argc, const char *
                                N_("second guess 'git checkout <no-such-branch>'")),
                OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
                         N_("do not check if another worktree is holding the given ref")),
 -              { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules,
 +              { OPTION_CALLBACK, 0, "recurse-submodules", NULL,
                            "checkout", "control recursive updating of submodules",
 -                          PARSE_OPT_OPTARG, option_parse_recurse_submodules },
 +                          PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
                OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")),
                OPT_END(),
        };
                git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
        }
  
 -      if (recurse_submodules != RECURSE_SUBMODULES_OFF) {
 -              git_config(submodule_config, NULL);
 -              if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT)
 -                      set_config_update_recurse_submodules(recurse_submodules);
 -      }
 -
        if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1)
                die(_("-b, -B and --orphan are mutually exclusive"));
  
diff --combined cache.h
index 55eeac8148b0597fd1a539736f4e81908bd96839,69b03b5dc7a887b1977b2cc7bc27f079a9be2208..1c69d2a05aaf1f5e737bd5229cfaf0fedbe6ca92
+++ b/cache.h
@@@ -11,8 -11,6 +11,8 @@@
  #include "string-list.h"
  #include "pack-revindex.h"
  #include "hash.h"
 +#include "path.h"
 +#include "sha1-array.h"
  
  #ifndef platform_SHA_CTX
  /*
@@@ -464,8 -462,6 +464,8 @@@ static inline enum object_type object_t
   */
  extern const char * const local_repo_env[];
  
 +extern void setup_git_env(void);
 +
  /*
   * Returns true iff we have a configured git repository (either via
   * setup_git_directory, or in the environment via $GIT_DIR).
@@@ -529,15 -525,12 +529,15 @@@ extern void set_git_work_tree(const cha
  
  extern void setup_work_tree(void);
  /*
 - * Find GIT_DIR of the repository that contains the current working directory,
 - * without changing the working directory or other global state. The result is
 - * appended to gitdir. The return value is either NULL if no repository was
 - * found, or pointing to the path inside gitdir's buffer.
 + * Find the commondir and gitdir of the repository that contains the current
 + * working directory, without changing the working directory or other global
 + * state. The result is appended to commondir and gitdir.  If the discovered
 + * gitdir does not correspond to a worktree, then 'commondir' and 'gitdir' will
 + * both have the same result appended to the buffer.  The return value is
 + * either 0 upon success and non-zero if no repository was found.
   */
 -extern const char *discover_git_directory(struct strbuf *gitdir);
 +extern int discover_git_directory(struct strbuf *commondir,
 +                                struct strbuf *gitdir);
  extern const char *setup_git_directory_gently(int *);
  extern const char *setup_git_directory(void);
  extern char *prefix_path(const char *prefix, int len, const char *path);
@@@ -773,6 -766,7 +773,6 @@@ 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
@@@ -894,6 -888,64 +894,6 @@@ extern void check_repository_format(voi
  #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 const char *git_common_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 void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 -      __attribute__((format (printf, 2, 3)));
 -extern void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)
 -      __attribute__((format (printf, 2, 3)));
 -extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
 -      __attribute__((format (printf, 2, 3)));
 -extern int 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)));
 -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
@@@ -974,13 -1026,6 +974,13 @@@ static inline void oidcpy(struct object
        hashcpy(dst->hash, src->hash);
  }
  
 +static inline struct object_id *oiddup(const struct object_id *src)
 +{
 +      struct object_id *dst = xmalloc(sizeof(struct object_id));
 +      oidcpy(dst, src);
 +      return dst;
 +}
 +
  static inline void hashclr(unsigned char *hash)
  {
        memset(hash, 0, GIT_SHA1_RAWSZ);
@@@ -1146,14 -1191,6 +1146,14 @@@ char *strip_path_suffix(const char *pat
  int daemon_avoid_alias(const char *path);
  extern int is_ntfs_dotgit(const char *name);
  
 +/*
 + * Returns true iff "str" could be confused as a command-line option when
 + * passed to a sub-program like "ssh". Note that this has nothing to do with
 + * shell-quoting, which should be handled separately; we're assuming here that
 + * the string makes it verbatim to the sub-program.
 + */
 +int looks_like_command_line_option(const char *str);
 +
  /**
   * Return a newly allocated string with the evaluation of
   * "$XDG_CONFIG_HOME/git/$filename" if $XDG_CONFIG_HOME is non-empty, otherwise
@@@ -1168,12 -1205,13 +1168,12 @@@ extern char *xdg_config_home(const cha
   */
  extern char *xdg_cache_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);
 +extern void *read_sha1_file_extended(const unsigned char *sha1,
 +                                   enum object_type *type,
 +                                   unsigned long *size, int lookup_replace);
  static inline void *read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
  {
 -      return read_sha1_file_extended(sha1, type, size, LOOKUP_REPLACE_OBJECT);
 +      return read_sha1_file_extended(sha1, type, size, 1);
  }
  
  /*
@@@ -1195,6 -1233,13 +1195,6 @@@ static inline const unsigned char *look
        return do_lookup_replace_object(sha1);
  }
  
 -static inline const unsigned char *lookup_replace_object_extended(const unsigned char *sha1, unsigned flag)
 -{
 -      if (!(flag & LOOKUP_REPLACE_OBJECT))
 -              return sha1;
 -      return lookup_replace_object(sha1);
 -}
 -
  /* Read and unpack a sha1 file into memory, write memory to a sha1 file */
  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);
@@@ -1231,10 -1276,15 +1231,10 @@@ int read_loose_object(const char *path
                      void **contents);
  
  /*
 - * 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).
 + * Convenience for sha1_object_info_extended() with a NULL struct
 + * object_info. OBJECT_INFO_SKIP_CACHED is automatically set; pass
 + * nonzero flags to also set other flags.
   */
 -#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)
  {
@@@ -1284,44 -1334,39 +1284,44 @@@ static inline int hex2chr(const char *s
  
  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;
 +      /*
 +       * If GET_OID_RECORD_PATH is set, this will record path (if any)
 +       * found when resolving the name. The caller is responsible for
 +       * releasing the memory.
 +       */
 +      char *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_FOLLOW_SYMLINKS 0100
 -#define GET_SHA1_ONLY_TO_DIE    04000
 -
 -#define GET_SHA1_DISAMBIGUATORS \
 -      (GET_SHA1_COMMIT | GET_SHA1_COMMITTISH | \
 -      GET_SHA1_TREE | GET_SHA1_TREEISH | \
 -      GET_SHA1_BLOB)
 -
 -extern int get_sha1(const char *str, unsigned char *sha1);
 -extern int get_sha1_commit(const char *str, unsigned char *sha1);
 -extern int get_sha1_committish(const char *str, unsigned char *sha1);
 -extern int get_sha1_tree(const char *str, unsigned char *sha1);
 -extern int get_sha1_treeish(const char *str, unsigned char *sha1);
 -extern int get_sha1_blob(const char *str, unsigned char *sha1);
 -extern void maybe_die_on_misspelt_object_name(const char *name, const char *prefix);
 -extern int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc);
 +#define GET_OID_QUIETLY           01
 +#define GET_OID_COMMIT            02
 +#define GET_OID_COMMITTISH        04
 +#define GET_OID_TREE             010
 +#define GET_OID_TREEISH          020
 +#define GET_OID_BLOB             040
 +#define GET_OID_FOLLOW_SYMLINKS 0100
 +#define GET_OID_RECORD_PATH     0200
 +#define GET_OID_ONLY_TO_DIE    04000
 +
 +#define GET_OID_DISAMBIGUATORS \
 +      (GET_OID_COMMIT | GET_OID_COMMITTISH | \
 +      GET_OID_TREE | GET_OID_TREEISH | \
 +      GET_OID_BLOB)
  
  extern int get_oid(const char *str, struct object_id *oid);
 +extern int get_oid_commit(const char *str, struct object_id *oid);
 +extern int get_oid_committish(const char *str, struct object_id *oid);
 +extern int get_oid_tree(const char *str, struct object_id *oid);
 +extern int get_oid_treeish(const char *str, struct object_id *oid);
 +extern int get_oid_blob(const char *str, struct object_id *oid);
 +extern void maybe_die_on_misspelt_object_name(const char *name, const char *prefix);
 +extern int get_oid_with_context(const char *str, unsigned flags, struct object_id *oid, struct object_context *oc);
 +
  
  typedef int each_abbrev_fn(const struct object_id *oid, void *);
  extern int for_each_abbrev(const char *prefix, each_abbrev_fn, void *);
@@@ -1499,6 -1544,7 +1499,7 @@@ struct checkout 
        struct index_state *istate;
        const char *base_dir;
        int base_dir_len;
+       struct delayed_checkout *delayed_checkout;
        unsigned force:1,
                 quiet:1,
                 not_new:1,
  
  #define TEMPORARY_FILENAME_LENGTH 25
  extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath);
+ extern void enable_delayed_checkout(struct checkout *state);
+ extern int finish_delayed_checkout(struct checkout *state);
  
  struct cache_def {
        struct strbuf path;
@@@ -1535,16 -1583,6 +1538,16 @@@ extern struct alternate_object_databas
        struct strbuf scratch;
        size_t base_len;
  
 +      /*
 +       * Used to store the results of readdir(3) calls when searching
 +       * for unique abbreviated hashes.  This cache is never
 +       * invalidated, thus it's racy and not necessarily accurate.
 +       * That's fine for its purpose; don't use it for tasks requiring
 +       * greater accuracy!
 +       */
 +      char loose_objects_subdir_seen[256];
 +      struct oid_array loose_objects_cache;
 +
        char path[FLEX_ARRAY];
  } *alt_odb_list;
  extern void prepare_alt_odb(void);
@@@ -1760,15 -1798,9 +1763,15 @@@ typedef int each_loose_object_fn(const 
  typedef int each_loose_cruft_fn(const char *basename,
                                const char *path,
                                void *data);
 -typedef int each_loose_subdir_fn(int nr,
 +typedef int each_loose_subdir_fn(unsigned int nr,
                                 const char *path,
                                 void *data);
 +int for_each_file_in_obj_subdir(unsigned int subdir_nr,
 +                              struct strbuf *path,
 +                              each_loose_object_fn obj_cb,
 +                              each_loose_cruft_fn cruft_cb,
 +                              each_loose_subdir_fn subdir_cb,
 +                              void *data);
  int for_each_loose_file_in_objdir(const char *path,
                                  each_loose_object_fn obj_cb,
                                  each_loose_cruft_fn cruft_cb,
@@@ -1800,7 -1832,6 +1803,7 @@@ struct object_info 
        off_t *disk_sizep;
        unsigned char *delta_base_sha1;
        struct strbuf *typename;
 +      void **contentp;
  
        /* Response */
        enum {
   */
  #define OBJECT_INFO_INIT {NULL}
  
 +/* Invoke lookup_replace_object() on the given hash */
 +#define OBJECT_INFO_LOOKUP_REPLACE 1
 +/* Allow reading from a loose object file of unknown/bogus type */
 +#define OBJECT_INFO_ALLOW_UNKNOWN_TYPE 2
 +/* Do not check cached storage */
 +#define OBJECT_INFO_SKIP_CACHED 4
 +/* Do not retry packed storage after checking packed and loose storage */
 +#define OBJECT_INFO_QUICK 8
  extern int sha1_object_info_extended(const unsigned char *, struct object_info *, unsigned flags);
  extern int packed_object_info(struct packed_git *pack, off_t offset, struct object_info *);
  
  /* Dumb servers support */
  extern int update_server_info(int);
  
 -/* git_config_parse_key() returns these negated: */
 -#define CONFIG_INVALID_KEY 1
 -#define CONFIG_NO_SECTION_OR_NAME 2
 -/* git_config_set_gently(), git_config_set_multivar_gently() return the above or these: */
 -#define CONFIG_NO_LOCK -1
 -#define CONFIG_INVALID_FILE 3
 -#define CONFIG_NO_WRITE 4
 -#define CONFIG_NOTHING_SET 5
 -#define CONFIG_INVALID_PATTERN 6
 -#define CONFIG_GENERIC_ERROR 7
 -
 -#define CONFIG_REGEX_NONE ((void *)1)
 -
 -struct git_config_source {
 -      unsigned int use_stdin:1;
 -      const char *file;
 -      const char *blob;
 -};
 -
 -enum config_origin_type {
 -      CONFIG_ORIGIN_BLOB,
 -      CONFIG_ORIGIN_FILE,
 -      CONFIG_ORIGIN_STDIN,
 -      CONFIG_ORIGIN_SUBMODULE_BLOB,
 -      CONFIG_ORIGIN_CMDLINE
 -};
 -
 -struct config_options {
 -      unsigned int respect_includes : 1;
 -      const char *git_dir;
 -};
 -
 -typedef int (*config_fn_t)(const char *, const char *, void *);
 -extern int git_default_config(const char *, const char *, void *);
 -extern int git_config_from_file(config_fn_t fn, const char *, void *);
 -extern int git_config_from_mem(config_fn_t fn, const enum config_origin_type,
 -                                      const char *name, const char *buf, size_t len, void *data);
 -extern int git_config_from_blob_sha1(config_fn_t fn, const char *name,
 -                                   const unsigned char *sha1, void *data);
 -extern void git_config_push_parameter(const char *text);
 -extern int git_config_from_parameters(config_fn_t fn, void *data);
 -extern void read_early_config(config_fn_t cb, void *data);
 -extern void git_config(config_fn_t fn, void *);
 -extern int git_config_with_options(config_fn_t fn, void *,
 -                                 struct git_config_source *config_source,
 -                                 const struct config_options *opts);
 -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 *);
 -extern ssize_t git_config_ssize_t(const char *, const char *);
 -extern int git_config_bool_or_int(const char *, const char *, int *);
 -extern int git_config_bool(const char *, const char *);
 -extern int git_config_maybe_bool(const char *, const char *);
 -extern int git_config_string(const char **, const char *, const char *);
 -extern int git_config_pathname(const char **, const char *, const char *);
 -extern int git_config_set_in_file_gently(const char *, const char *, const char *);
 -extern void git_config_set_in_file(const char *, const char *, const char *);
 -extern int git_config_set_gently(const char *, const char *);
 -extern void 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_gently(const char *, const char *, const char *, int);
 -extern void git_config_set_multivar(const char *, const char *, const char *, int);
 -extern int git_config_set_multivar_in_file_gently(const char *, const char *, const char *, const char *, int);
 -extern void 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 *);
 -extern int git_config_rename_section_in_file(const char *, const char *, const char *);
 -extern const char *git_etc_gitconfig(void);
 -extern int git_env_bool(const char *, int);
 -extern unsigned long git_env_ulong(const char *, unsigned long);
 -extern int git_config_system(void);
 -extern int config_error_nonbool(const char *);
 -#if defined(__GNUC__)
 -#define config_error_nonbool(s) (config_error_nonbool(s), const_error())
 -#endif
  extern const char *get_log_output_encoding(void);
  extern const char *get_commit_output_encoding(void);
  
 -extern int git_config_parse_parameter(const char *, config_fn_t fn, void *data);
 -
 -enum config_scope {
 -      CONFIG_SCOPE_UNKNOWN = 0,
 -      CONFIG_SCOPE_SYSTEM,
 -      CONFIG_SCOPE_GLOBAL,
 -      CONFIG_SCOPE_REPO,
 -      CONFIG_SCOPE_CMDLINE,
 -};
 -
 -extern enum config_scope current_config_scope(void);
 -extern const char *current_config_origin_type(void);
 -extern const char *current_config_name(void);
 -
 -struct config_include_data {
 -      int depth;
 -      config_fn_t fn;
 -      void *data;
 -      const struct config_options *opts;
 -};
 -#define CONFIG_INCLUDE_INIT { 0 }
 -extern int git_config_include(const char *name, const char *value, void *data);
 -
 -/*
 - * Match and parse a config key of the form:
 - *
 - *   section.(subsection.)?key
 - *
 - * (i.e., what gets handed to a config_fn_t). The caller provides the section;
 - * we return -1 if it does not match, 0 otherwise. The subsection and key
 - * out-parameters are filled by the function (and *subsection is NULL if it is
 - * missing).
 - *
 - * If the subsection pointer-to-pointer passed in is NULL, returns 0 only if
 - * there is no subsection at all.
 - */
 -extern int parse_config_key(const char *var,
 -                          const char *section,
 -                          const char **subsection, int *subsection_len,
 -                          const char **key);
 -
 -struct config_set_element {
 -      struct hashmap_entry ent;
 -      char *key;
 -      struct string_list value_list;
 -};
 -
 -struct configset_list_item {
 -      struct config_set_element *e;
 -      int value_index;
 -};
 -
 -/*
 - * the contents of the list are ordered according to their
 - * position in the config files and order of parsing the files.
 - * (i.e. key-value pair at the last position of .git/config will
 - * be at the last item of the list)
 - */
 -struct configset_list {
 -      struct configset_list_item *items;
 -      unsigned int nr, alloc;
 -};
 -
 -struct config_set {
 -      struct hashmap config_hash;
 -      int hash_initialized;
 -      struct configset_list list;
 -};
 -
 -extern void git_configset_init(struct config_set *cs);
 -extern int git_configset_add_file(struct config_set *cs, const char *filename);
 -extern int git_configset_get_value(struct config_set *cs, const char *key, const char **value);
 -extern const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key);
 -extern void git_configset_clear(struct config_set *cs);
 -extern int git_configset_get_string_const(struct config_set *cs, const char *key, const char **dest);
 -extern int git_configset_get_string(struct config_set *cs, const char *key, char **dest);
 -extern int git_configset_get_int(struct config_set *cs, const char *key, int *dest);
 -extern int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest);
 -extern int git_configset_get_bool(struct config_set *cs, const char *key, int *dest);
 -extern int git_configset_get_bool_or_int(struct config_set *cs, const char *key, int *is_bool, int *dest);
 -extern int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest);
 -extern int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest);
 -
 -extern int git_config_get_value(const char *key, const char **value);
 -extern const struct string_list *git_config_get_value_multi(const char *key);
 -extern void git_config_clear(void);
 -extern void git_config_iter(config_fn_t fn, void *data);
 -extern int git_config_get_string_const(const char *key, const char **dest);
 -extern int git_config_get_string(const char *key, char **dest);
 -extern int git_config_get_int(const char *key, int *dest);
 -extern int git_config_get_ulong(const char *key, unsigned long *dest);
 -extern int git_config_get_bool(const char *key, int *dest);
 -extern int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest);
 -extern int git_config_get_maybe_bool(const char *key, int *dest);
 -extern int git_config_get_pathname(const char *key, const char **dest);
 -extern int git_config_get_untracked_cache(void);
 -extern int git_config_get_split_index(void);
 -extern int git_config_get_max_percent_split_change(void);
 -
 -/* This dies if the configured or default date is in the future */
 -extern int git_config_get_expiry(const char *key, const char **output);
 -
  /*
   * This is a hack for test programs like test-dump-untracked-cache to
   * ensure that they do not modify the untracked cache when reading it.
   */
  extern int ignore_untracked_cache_config;
  
 -struct key_value_info {
 -      const char *filename;
 -      int linenr;
 -      enum config_origin_type origin_type;
 -      enum config_scope scope;
 -};
 -
 -extern NORETURN void git_die_config(const char *key, const char *err, ...) __attribute__((format(printf, 2, 3)));
 -extern NORETURN void git_die_config_linenr(const char *key, const char *filename, int linenr);
 -
  extern int committer_ident_sufficiently_given(void);
  extern int author_ident_sufficiently_given(void);
  
@@@ -1971,8 -2183,7 +1974,8 @@@ extern int ws_blank_line(const char *li
  #define ws_tab_width(rule)     ((rule) & WS_TAB_WIDTH_MASK)
  
  /* ls-files */
 -void overlay_tree_on_cache(const char *tree_name, const char *prefix);
 +void overlay_tree_on_index(struct index_state *istate,
 +                         const char *tree_name, const char *prefix);
  
  char *alias_lookup(const char *alias);
  int split_cmdline(char *cmdline, const char ***argv);
diff --combined convert.c
index deaf0ba7b30ffa4e0003649985ef1594ee2ac049,12a0b3eafb9f55a54d0163c99a9270e13add7b41..972bf8aec27594a94ad756431e551c2f30796c4d
+++ b/convert.c
@@@ -1,6 -1,4 +1,6 @@@
 +#define NO_THE_INDEX_COMPATIBILITY_MACROS
  #include "cache.h"
 +#include "config.h"
  #include "attr.h"
  #include "run-command.h"
  #include "quote.h"
@@@ -136,12 -134,11 +136,12 @@@ static const char *gather_convert_stats
        }
  }
  
 -const char *get_cached_convert_stats_ascii(const char *path)
 +const char *get_cached_convert_stats_ascii(const struct index_state *istate,
 +                                         const char *path)
  {
        const char *ret;
        unsigned long sz;
 -      void *data = read_blob_data_from_cache(path, &sz);
 +      void *data = read_blob_data_from_index(istate, path, &sz);
        ret = gather_convert_stats_ascii(data, sz);
        free(data);
        return ret;
@@@ -220,13 -217,13 +220,13 @@@ static void check_safe_crlf(const char 
        }
  }
  
 -static int has_cr_in_index(const char *path)
 +static int has_cr_in_index(const struct index_state *istate, const char *path)
  {
        unsigned long sz;
        void *data;
        int has_cr;
  
 -      data = read_blob_data_from_cache(path, &sz);
 +      data = read_blob_data_from_index(istate, path, &sz);
        if (!data)
                return 0;
        has_cr = memchr(data, '\r', sz) != NULL;
@@@ -256,8 -253,7 +256,8 @@@ static int will_convert_lf_to_crlf(size
  
  }
  
 -static int crlf_to_git(const char *path, const char *src, size_t len,
 +static int crlf_to_git(const struct index_state *istate,
 +                     const char *path, const char *src, size_t len,
                       struct strbuf *buf,
                       enum crlf_action crlf_action, enum safe_crlf checksafe)
  {
                 * unless we want to renormalize in a merge or
                 * cherry-pick.
                 */
 -              if ((checksafe != SAFE_CRLF_RENORMALIZE) && has_cr_in_index(path))
 +              if ((checksafe != SAFE_CRLF_RENORMALIZE) &&
 +                  has_cr_in_index(istate, path))
                        convert_crlf_into_lf = 0;
        }
        if ((checksafe == SAFE_CRLF_WARN ||
@@@ -501,6 -496,7 +501,7 @@@ static int apply_single_file_filter(con
  
  #define CAP_CLEAN    (1u<<0)
  #define CAP_SMUDGE   (1u<<1)
+ #define CAP_DELAY    (1u<<2)
  
  struct cmd2process {
        struct subprocess_entry subprocess; /* must be the first member! */
@@@ -512,7 -508,7 +513,7 @@@ static struct hashmap subprocess_map
  
  static int start_multi_file_filter_fn(struct subprocess_entry *subprocess)
  {
-       int err;
+       int err, i;
        struct cmd2process *entry = (struct cmd2process *)subprocess;
        struct string_list cap_list = STRING_LIST_INIT_NODUP;
        char *cap_buf;
        struct child_process *process = &subprocess->process;
        const char *cmd = subprocess->cmd;
  
+       static const struct {
+               const char *name;
+               unsigned int cap;
+       } known_caps[] = {
+               { "clean",  CAP_CLEAN  },
+               { "smudge", CAP_SMUDGE },
+               { "delay",  CAP_DELAY  },
+       };
        sigchain_push(SIGPIPE, SIG_IGN);
  
        err = packet_writel(process->in, "git-filter-client", "version=2", NULL);
        if (err)
                goto done;
  
-       err = packet_writel(process->in, "capability=clean", "capability=smudge", NULL);
+       for (i = 0; i < ARRAY_SIZE(known_caps); ++i) {
+               err = packet_write_fmt_gently(
+                       process->in, "capability=%s\n", known_caps[i].name);
+               if (err)
+                       goto done;
+       }
+       err = packet_flush_gently(process->in);
+       if (err)
+               goto done;
  
        for (;;) {
                cap_buf = packet_read_line(process->out, NULL);
                        continue;
  
                cap_name = cap_list.items[1].string;
-               if (!strcmp(cap_name, "clean")) {
-                       entry->supported_capabilities |= CAP_CLEAN;
-               } else if (!strcmp(cap_name, "smudge")) {
-                       entry->supported_capabilities |= CAP_SMUDGE;
-               } else {
-                       warning(
-                               "external filter '%s' requested unsupported filter capability '%s'",
-                               cmd, cap_name
-                       );
-               }
+               i = ARRAY_SIZE(known_caps) - 1;
+               while (i >= 0 && strcmp(cap_name, known_caps[i].name))
+                       i--;
+               if (i >= 0)
+                       entry->supported_capabilities |= known_caps[i].cap;
+               else
+                       warning("external filter '%s' requested unsupported filter capability '%s'",
+                       cmd, cap_name);
  
                string_list_clear(&cap_list, 0);
        }
@@@ -570,11 -582,36 +587,36 @@@ done
        return err;
  }
  
+ static void handle_filter_error(const struct strbuf *filter_status,
+                               struct cmd2process *entry,
+                               const unsigned int wanted_capability) {
+       if (!strcmp(filter_status->buf, "error"))
+               ; /* The filter signaled a problem with the file. */
+       else if (!strcmp(filter_status->buf, "abort") && wanted_capability) {
+               /*
+                * The filter signaled a permanent problem. Don't try to filter
+                * files with the same command for the lifetime of the current
+                * Git process.
+                */
+                entry->supported_capabilities &= ~wanted_capability;
+       } else {
+               /*
+                * Something went wrong with the protocol filter.
+                * Force shutdown and restart if another blob requires filtering.
+                */
+               error("external filter '%s' failed", entry->subprocess.cmd);
+               subprocess_stop(&subprocess_map, &entry->subprocess);
+               free(entry);
+       }
+ }
  static int apply_multi_file_filter(const char *path, const char *src, size_t len,
                                   int fd, struct strbuf *dst, const char *cmd,
-                                  const unsigned int wanted_capability)
+                                  const unsigned int wanted_capability,
+                                  struct delayed_checkout *dco)
  {
        int err;
+       int can_delay = 0;
        struct cmd2process *entry;
        struct child_process *process;
        struct strbuf nbuf = STRBUF_INIT;
  
        if (!subprocess_map_initialized) {
                subprocess_map_initialized = 1;
 -              hashmap_init(&subprocess_map, (hashmap_cmp_fn) cmd2process_cmp, 0);
 +              hashmap_init(&subprocess_map, (hashmap_cmp_fn) cmd2process_cmp,
 +                           NULL, 0);
                entry = NULL;
        } else {
                entry = (struct cmd2process *)subprocess_find_entry(&subprocess_map, cmd);
        }
        process = &entry->subprocess.process;
  
-       if (!(wanted_capability & entry->supported_capabilities))
+       if (!(entry->supported_capabilities & wanted_capability))
                return 0;
  
-       if (CAP_CLEAN & wanted_capability)
+       if (wanted_capability & CAP_CLEAN)
                filter_type = "clean";
-       else if (CAP_SMUDGE & wanted_capability)
+       else if (wanted_capability & CAP_SMUDGE)
                filter_type = "smudge";
        else
                die("unexpected filter type");
        if (err)
                goto done;
  
+       if ((entry->supported_capabilities & CAP_DELAY) &&
+           dco && dco->state == CE_CAN_DELAY) {
+               can_delay = 1;
+               err = packet_write_fmt_gently(process->in, "can-delay=1\n");
+               if (err)
+                       goto done;
+       }
        err = packet_flush_gently(process->in);
        if (err)
                goto done;
        if (err)
                goto done;
  
-       err = strcmp(filter_status.buf, "success");
+       if (can_delay && !strcmp(filter_status.buf, "delayed")) {
+               string_list_insert(&dco->filters, cmd);
+               string_list_insert(&dco->paths, path);
+       } else {
+               /* The filter got the blob and wants to send us a response. */
+               err = strcmp(filter_status.buf, "success");
+               if (err)
+                       goto done;
+               err = read_packetized_to_strbuf(process->out, &nbuf) < 0;
+               if (err)
+                       goto done;
+               err = subprocess_read_status(process->out, &filter_status);
+               if (err)
+                       goto done;
+               err = strcmp(filter_status.buf, "success");
+       }
+ done:
+       sigchain_pop(SIGPIPE);
+       if (err)
+               handle_filter_error(&filter_status, entry, wanted_capability);
+       else
+               strbuf_swap(dst, &nbuf);
+       strbuf_release(&nbuf);
+       return !err;
+ }
+ int async_query_available_blobs(const char *cmd, struct string_list *available_paths)
+ {
+       int err;
+       char *line;
+       struct cmd2process *entry;
+       struct child_process *process;
+       struct strbuf filter_status = STRBUF_INIT;
+       assert(subprocess_map_initialized);
+       entry = (struct cmd2process *)subprocess_find_entry(&subprocess_map, cmd);
+       if (!entry) {
+               error("external filter '%s' is not available anymore although "
+                     "not all paths have been filtered", cmd);
+               return 0;
+       }
+       process = &entry->subprocess.process;
+       sigchain_push(SIGPIPE, SIG_IGN);
+       err = packet_write_fmt_gently(
+               process->in, "command=list_available_blobs\n");
        if (err)
                goto done;
  
-       err = read_packetized_to_strbuf(process->out, &nbuf) < 0;
+       err = packet_flush_gently(process->in);
        if (err)
                goto done;
  
+       while ((line = packet_read_line(process->out, NULL))) {
+               const char *path;
+               if (skip_prefix(line, "pathname=", &path))
+                       string_list_insert(available_paths, xstrdup(path));
+               else
+                       ; /* ignore unknown keys */
+       }
        err = subprocess_read_status(process->out, &filter_status);
        if (err)
                goto done;
  done:
        sigchain_pop(SIGPIPE);
  
-       if (err) {
-               if (!strcmp(filter_status.buf, "error")) {
-                       /* The filter signaled a problem with the file. */
-               } else if (!strcmp(filter_status.buf, "abort")) {
-                       /*
-                        * The filter signaled a permanent problem. Don't try to filter
-                        * files with the same command for the lifetime of the current
-                        * Git process.
-                        */
-                        entry->supported_capabilities &= ~wanted_capability;
-               } else {
-                       /*
-                        * Something went wrong with the protocol filter.
-                        * Force shutdown and restart if another blob requires filtering.
-                        */
-                       error("external filter '%s' failed", cmd);
-                       subprocess_stop(&subprocess_map, &entry->subprocess);
-                       free(entry);
-               }
-       } else {
-               strbuf_swap(dst, &nbuf);
-       }
-       strbuf_release(&nbuf);
+       if (err)
+               handle_filter_error(&filter_status, entry, 0);
        return !err;
  }
  
@@@ -699,7 -781,8 +787,8 @@@ static struct convert_driver 
  
  static int apply_filter(const char *path, const char *src, size_t len,
                        int fd, struct strbuf *dst, struct convert_driver *drv,
-                       const unsigned int wanted_capability)
+                       const unsigned int wanted_capability,
+                       struct delayed_checkout *dco)
  {
        const char *cmd = NULL;
  
        if (!dst)
                return 1;
  
-       if ((CAP_CLEAN & wanted_capability) && !drv->process && drv->clean)
+       if ((wanted_capability & CAP_CLEAN) && !drv->process && drv->clean)
                cmd = drv->clean;
-       else if ((CAP_SMUDGE & wanted_capability) && !drv->process && drv->smudge)
+       else if ((wanted_capability & CAP_SMUDGE) && !drv->process && drv->smudge)
                cmd = drv->smudge;
  
        if (cmd && *cmd)
                return apply_single_file_filter(path, src, len, fd, dst, cmd);
        else if (drv->process && *drv->process)
-               return apply_multi_file_filter(path, src, len, fd, dst, drv->process, wanted_capability);
+               return apply_multi_file_filter(path, src, len, fd, dst,
+                       drv->process, wanted_capability, dco);
  
        return 0;
  }
@@@ -1058,7 -1142,7 +1148,7 @@@ int would_convert_to_git_filter_fd(cons
        if (!ca.drv->required)
                return 0;
  
-       return apply_filter(path, NULL, 0, -1, NULL, ca.drv, CAP_CLEAN);
+       return apply_filter(path, NULL, 0, -1, NULL, ca.drv, CAP_CLEAN, NULL);
  }
  
  const char *get_convert_attr_ascii(const char *path)
        return "";
  }
  
 -int convert_to_git(const char *path, const char *src, size_t len,
 +int convert_to_git(const struct index_state *istate,
 +                 const char *path, const char *src, size_t len,
                     struct strbuf *dst, enum safe_crlf checksafe)
  {
        int ret = 0;
  
        convert_attrs(&ca, path);
  
-       ret |= apply_filter(path, src, len, -1, dst, ca.drv, CAP_CLEAN);
+       ret |= apply_filter(path, src, len, -1, dst, ca.drv, CAP_CLEAN, NULL);
        if (!ret && ca.drv && ca.drv->required)
                die("%s: clean filter '%s' failed", path, ca.drv->name);
  
                src = dst->buf;
                len = dst->len;
        }
 -      ret |= crlf_to_git(path, src, len, dst, ca.crlf_action, checksafe);
 +      ret |= crlf_to_git(istate, path, src, len, dst, ca.crlf_action, checksafe);
        if (ret && dst) {
                src = dst->buf;
                len = dst->len;
        return ret | ident_to_git(path, src, len, dst, ca.ident);
  }
  
 -void convert_to_git_filter_fd(const char *path, int fd, struct strbuf *dst,
 +void convert_to_git_filter_fd(const struct index_state *istate,
 +                            const char *path, int fd, struct strbuf *dst,
                              enum safe_crlf checksafe)
  {
        struct conv_attrs ca;
        assert(ca.drv);
        assert(ca.drv->clean || ca.drv->process);
  
-       if (!apply_filter(path, NULL, 0, fd, dst, ca.drv, CAP_CLEAN))
+       if (!apply_filter(path, NULL, 0, fd, dst, ca.drv, CAP_CLEAN, NULL))
                die("%s: clean filter '%s' failed", path, ca.drv->name);
  
 -      crlf_to_git(path, dst->buf, dst->len, dst, ca.crlf_action, checksafe);
 +      crlf_to_git(istate, path, dst->buf, dst->len, dst, ca.crlf_action, checksafe);
        ident_to_git(path, dst->buf, dst->len, dst, ca.ident);
  }
  
  static int convert_to_working_tree_internal(const char *path, const char *src,
                                            size_t len, struct strbuf *dst,
-                                           int normalizing)
+                                           int normalizing, struct delayed_checkout *dco)
  {
        int ret = 0, ret_filter = 0;
        struct conv_attrs ca;
                }
        }
  
-       ret_filter = apply_filter(path, src, len, -1, dst, ca.drv, CAP_SMUDGE);
+       ret_filter = apply_filter(
+               path, src, len, -1, dst, ca.drv, CAP_SMUDGE, dco);
        if (!ret_filter && ca.drv && ca.drv->required)
                die("%s: smudge filter %s failed", path, ca.drv->name);
  
        return ret | ret_filter;
  }
  
+ int async_convert_to_working_tree(const char *path, const char *src,
+                                 size_t len, struct strbuf *dst,
+                                 void *dco)
+ {
+       return convert_to_working_tree_internal(path, src, len, dst, 0, dco);
+ }
  int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst)
  {
-       return convert_to_working_tree_internal(path, src, len, dst, 0);
+       return convert_to_working_tree_internal(path, src, len, dst, 0, NULL);
  }
  
 -int renormalize_buffer(const char *path, const char *src, size_t len, struct strbuf *dst)
 +int renormalize_buffer(const struct index_state *istate, const char *path,
 +                     const char *src, size_t len, struct strbuf *dst)
  {
-       int ret = convert_to_working_tree_internal(path, src, len, dst, 1);
+       int ret = convert_to_working_tree_internal(path, src, len, dst, 1, NULL);
        if (ret) {
                src = dst->buf;
                len = dst->len;
        }
 -      return ret | convert_to_git(path, src, len, dst, SAFE_CRLF_RENORMALIZE);
 +      return ret | convert_to_git(istate, path, src, len, dst, SAFE_CRLF_RENORMALIZE);
  }
  
  /*****************************************************************
diff --combined convert.h
index cecf59d1aa15f234473dc16520c0a6647e255280,643a5be6cc6376a226891d5264be1b2b1bdba801..6b06144281c797a08c10e0dc5bf72f97d260be88
+++ b/convert.h
@@@ -4,8 -4,8 +4,10 @@@
  #ifndef CONVERT_H
  #define CONVERT_H
  
+ #include "string-list.h"
 +struct index_state;
 +
  enum safe_crlf {
        SAFE_CRLF_FALSE = 0,
        SAFE_CRLF_FAIL = 1,
@@@ -34,29 -34,48 +36,53 @@@ enum eol 
  #endif
  };
  
+ enum ce_delay_state {
+       CE_NO_DELAY = 0,
+       CE_CAN_DELAY = 1,
+       CE_RETRY = 2
+ };
+ struct delayed_checkout {
+       /*
+        * State of the currently processed cache entry. If the state is
+        * CE_CAN_DELAY, then the filter can delay the current cache entry.
+        * If the state is CE_RETRY, then this signals the filter that the
+        * cache entry was requested before.
+        */
+       enum ce_delay_state state;
+       /* List of filter drivers that signaled delayed blobs. */
+       struct string_list filters;
+       /* List of delayed blobs identified by their path. */
+       struct string_list paths;
+ };
  extern enum eol core_eol;
 -extern const char *get_cached_convert_stats_ascii(const char *path);
 +extern const char *get_cached_convert_stats_ascii(const struct index_state *istate,
 +                                                const char *path);
  extern const char *get_wt_convert_stats_ascii(const char *path);
  extern const char *get_convert_attr_ascii(const char *path);
  
  /* returns 1 if *dst was used */
 -extern int convert_to_git(const char *path, const char *src, size_t len,
 +extern int convert_to_git(const struct index_state *istate,
 +                        const char *path, const char *src, size_t len,
                          struct strbuf *dst, enum safe_crlf checksafe);
  extern int convert_to_working_tree(const char *path, const char *src,
                                   size_t len, struct strbuf *dst);
 -extern int renormalize_buffer(const char *path, const char *src, size_t len,
+ extern int async_convert_to_working_tree(const char *path, const char *src,
+                                        size_t len, struct strbuf *dst,
+                                        void *dco);
+ extern int async_query_available_blobs(const char *cmd, struct string_list *available_paths);
 +extern int renormalize_buffer(const struct index_state *istate,
 +                            const char *path, const char *src, size_t len,
                              struct strbuf *dst);
 -static inline int would_convert_to_git(const char *path)
 +static inline int would_convert_to_git(const struct index_state *istate,
 +                                     const char *path)
  {
 -      return convert_to_git(path, NULL, 0, NULL, 0);
 +      return convert_to_git(istate, path, NULL, 0, NULL, 0);
  }
  /* Precondition: would_convert_to_git_filter_fd(path) == true */
 -extern void convert_to_git_filter_fd(const char *path, int fd,
 +extern void convert_to_git_filter_fd(const struct index_state *istate,
 +                                   const char *path, int fd,
                                     struct strbuf *dst,
                                     enum safe_crlf checksafe);
  extern int would_convert_to_git_filter_fd(const char *path);
diff --combined unpack-trees.c
index dd535bc8497e5d8202a94923fd0a68c3f46907fd,491a2562adc9f76db454af4cdd454c55c6359c1a..862cfce661e57e50f7c6030dd5a8eda0add0cca5
@@@ -1,6 -1,5 +1,6 @@@
  #define NO_THE_INDEX_COMPATIBILITY_MACROS
  #include "cache.h"
 +#include "config.h"
  #include "dir.h"
  #include "tree.h"
  #include "tree-walk.h"
@@@ -380,6 -379,7 +380,7 @@@ static int check_updates(struct unpack_
        if (should_update_submodules() && o->update && !o->dry_run)
                reload_gitmodules_file(index, &state);
  
+       enable_delayed_checkout(&state);
        for (i = 0; i < index->cache_nr; i++) {
                struct cache_entry *ce = index->cache[i];
  
                        }
                }
        }
+       errs |= finish_delayed_checkout(&state);
        stop_progress(&progress);
        if (o->update)
                git_attr_set_direction(GIT_ATTR_CHECKIN, NULL);