Imported Upstream version 2.23.3
[platform/upstream/git.git] / convert.c
index 6602155..94ff837 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -1,8 +1,13 @@
 #include "cache.h"
+#include "config.h"
+#include "object-store.h"
 #include "attr.h"
 #include "run-command.h"
 #include "quote.h"
 #include "sigchain.h"
+#include "pkt-line.h"
+#include "sub-process.h"
+#include "utf8.h"
 
 /*
  * convert.c - convert a file when checking it out and checking it in.
  * translation when the "text" attribute or "auto_crlf" option is set.
  */
 
+/* Stat bits: When BIN is set, the txt bits are unset */
+#define CONVERT_STAT_BITS_TXT_LF    0x1
+#define CONVERT_STAT_BITS_TXT_CRLF  0x2
+#define CONVERT_STAT_BITS_BIN       0x4
+
 enum crlf_action {
-       CRLF_GUESS = -1,
-       CRLF_BINARY = 0,
+       CRLF_UNDEFINED,
+       CRLF_BINARY,
        CRLF_TEXT,
-       CRLF_INPUT,
-       CRLF_CRLF,
-       CRLF_AUTO
+       CRLF_TEXT_INPUT,
+       CRLF_TEXT_CRLF,
+       CRLF_AUTO,
+       CRLF_AUTO_INPUT,
+       CRLF_AUTO_CRLF
 };
 
 struct text_stat {
        /* NUL, CR, LF and CRLF counts */
-       unsigned nul, cr, lf, crlf;
+       unsigned nul, lonecr, lonelf, crlf;
 
        /* These are just approximations! */
        unsigned printable, nonprintable;
@@ -39,13 +51,15 @@ static void gather_stats(const char *buf, unsigned long size, struct text_stat *
        for (i = 0; i < size; i++) {
                unsigned char c = buf[i];
                if (c == '\r') {
-                       stats->cr++;
-                       if (i+1 < size && buf[i+1] == '\n')
+                       if (i+1 < size && buf[i+1] == '\n') {
                                stats->crlf++;
+                               i++;
+                       } else
+                               stats->lonecr++;
                        continue;
                }
                if (c == '\n') {
-                       stats->lf++;
+                       stats->lonelf++;
                        continue;
                }
                if (c == 127)
@@ -75,23 +89,85 @@ static void gather_stats(const char *buf, unsigned long size, struct text_stat *
 
 /*
  * The same heuristics as diff.c::mmfile_is_binary()
+ * We treat files with bare CR as binary
  */
-static int is_binary(unsigned long size, struct text_stat *stats)
+static int convert_is_binary(const struct text_stat *stats)
 {
-
+       if (stats->lonecr)
+               return 1;
        if (stats->nul)
                return 1;
        if ((stats->printable >> 7) < stats->nonprintable)
                return 1;
-       /*
-        * Other heuristics? Average line length might be relevant,
-        * as might LF vs CR vs CRLF counts..
-        *
-        * NOTE! It might be normal to have a low ratio of CRLF to LF
-        * (somebody starts with a LF-only file and edits it with an editor
-        * that adds CRLF only to lines that are added..). But do  we
-        * want to support CR-only? Probably not.
-        */
+       return 0;
+}
+
+static unsigned int gather_convert_stats(const char *data, unsigned long size)
+{
+       struct text_stat stats;
+       int ret = 0;
+       if (!data || !size)
+               return 0;
+       gather_stats(data, size, &stats);
+       if (convert_is_binary(&stats))
+               ret |= CONVERT_STAT_BITS_BIN;
+       if (stats.crlf)
+               ret |= CONVERT_STAT_BITS_TXT_CRLF;
+       if (stats.lonelf)
+               ret |=  CONVERT_STAT_BITS_TXT_LF;
+
+       return ret;
+}
+
+static const char *gather_convert_stats_ascii(const char *data, unsigned long size)
+{
+       unsigned int convert_stats = gather_convert_stats(data, size);
+
+       if (convert_stats & CONVERT_STAT_BITS_BIN)
+               return "-text";
+       switch (convert_stats) {
+       case CONVERT_STAT_BITS_TXT_LF:
+               return "lf";
+       case CONVERT_STAT_BITS_TXT_CRLF:
+               return "crlf";
+       case CONVERT_STAT_BITS_TXT_LF | CONVERT_STAT_BITS_TXT_CRLF:
+               return "mixed";
+       default:
+               return "none";
+       }
+}
+
+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_index(istate, path, &sz);
+       ret = gather_convert_stats_ascii(data, sz);
+       free(data);
+       return ret;
+}
+
+const char *get_wt_convert_stats_ascii(const char *path)
+{
+       const char *ret = "";
+       struct strbuf sb = STRBUF_INIT;
+       if (strbuf_read_file(&sb, path, 0) >= 0)
+               ret = gather_convert_stats_ascii(sb.buf, sb.len);
+       strbuf_release(&sb);
+       return ret;
+}
+
+static int text_eol_is_crlf(void)
+{
+       if (auto_crlf == AUTO_CRLF_TRUE)
+               return 1;
+       else if (auto_crlf == AUTO_CRLF_INPUT)
+               return 0;
+       if (core_eol == EOL_CRLF)
+               return 1;
+       if (core_eol == EOL_UNSET && EOL_NATIVE == EOL_CRLF)
+               return 1;
        return 0;
 }
 
@@ -100,103 +176,341 @@ static enum eol output_eol(enum crlf_action crlf_action)
        switch (crlf_action) {
        case CRLF_BINARY:
                return EOL_UNSET;
-       case CRLF_CRLF:
+       case CRLF_TEXT_CRLF:
                return EOL_CRLF;
-       case CRLF_INPUT:
+       case CRLF_TEXT_INPUT:
+               return EOL_LF;
+       case CRLF_UNDEFINED:
+       case CRLF_AUTO_CRLF:
+               return EOL_CRLF;
+       case CRLF_AUTO_INPUT:
                return EOL_LF;
-       case CRLF_GUESS:
-               if (!auto_crlf)
-                       return EOL_UNSET;
-               /* fall through */
        case CRLF_TEXT:
        case CRLF_AUTO:
-               if (auto_crlf == AUTO_CRLF_TRUE)
-                       return EOL_CRLF;
-               else if (auto_crlf == AUTO_CRLF_INPUT)
-                       return EOL_LF;
-               else if (core_eol == EOL_UNSET)
-                       return EOL_NATIVE;
+               /* fall through */
+               return text_eol_is_crlf() ? EOL_CRLF : EOL_LF;
        }
+       warning(_("illegal crlf_action %d"), (int)crlf_action);
        return core_eol;
 }
 
-static void check_safe_crlf(const char *path, enum crlf_action crlf_action,
-                            struct text_stat *stats, enum safe_crlf checksafe)
+static void check_global_conv_flags_eol(const char *path, enum crlf_action crlf_action,
+                           struct text_stat *old_stats, struct text_stat *new_stats,
+                           int conv_flags)
 {
-       if (!checksafe)
-               return;
-
-       if (output_eol(crlf_action) == EOL_LF) {
+       if (old_stats->crlf && !new_stats->crlf ) {
                /*
-                * CRLFs would not be restored by checkout:
-                * check if we'd remove CRLFs
+                * CRLFs would not be restored by checkout
                 */
-               if (stats->crlf) {
-                       if (checksafe == SAFE_CRLF_WARN)
-                               warning("CRLF will be replaced by LF in %s.\nThe file will have its original line endings in your working directory.", path);
-                       else /* i.e. SAFE_CRLF_FAIL */
-                               die("CRLF would be replaced by LF in %s.", path);
-               }
-       } else if (output_eol(crlf_action) == EOL_CRLF) {
+               if (conv_flags & CONV_EOL_RNDTRP_DIE)
+                       die(_("CRLF would be replaced by LF in %s"), path);
+               else if (conv_flags & CONV_EOL_RNDTRP_WARN)
+                       warning(_("CRLF will be replaced by LF in %s.\n"
+                                 "The file will have its original line"
+                                 " endings in your working directory"), path);
+       } else if (old_stats->lonelf && !new_stats->lonelf ) {
                /*
-                * CRLFs would be added by checkout:
-                * check if we have "naked" LFs
+                * CRLFs would be added by checkout
                 */
-               if (stats->lf != stats->crlf) {
-                       if (checksafe == SAFE_CRLF_WARN)
-                               warning("LF will be replaced by CRLF in %s.\nThe file will have its original line endings in your working directory.", path);
-                       else /* i.e. SAFE_CRLF_FAIL */
-                               die("LF would be replaced by CRLF in %s", path);
-               }
+               if (conv_flags & CONV_EOL_RNDTRP_DIE)
+                       die(_("LF would be replaced by CRLF in %s"), path);
+               else if (conv_flags & CONV_EOL_RNDTRP_WARN)
+                       warning(_("LF will be replaced by CRLF in %s.\n"
+                                 "The file will have its original line"
+                                 " endings in your working directory"), path);
        }
 }
 
-static int has_cr_in_index(const char *path)
+static int has_crlf_in_index(const struct index_state *istate, const char *path)
 {
-       int pos, len;
        unsigned long sz;
-       enum object_type type;
        void *data;
-       int has_cr;
-       struct index_state *istate = &the_index;
+       const char *crp;
+       int has_crlf = 0;
+
+       data = read_blob_data_from_index(istate, path, &sz);
+       if (!data)
+               return 0;
+
+       crp = memchr(data, '\r', sz);
+       if (crp) {
+               unsigned int ret_stats;
+               ret_stats = gather_convert_stats(data, sz);
+               if (!(ret_stats & CONVERT_STAT_BITS_BIN) &&
+                   (ret_stats & CONVERT_STAT_BITS_TXT_CRLF))
+                       has_crlf = 1;
+       }
+       free(data);
+       return has_crlf;
+}
+
+static int will_convert_lf_to_crlf(struct text_stat *stats,
+                                  enum crlf_action crlf_action)
+{
+       if (output_eol(crlf_action) != EOL_CRLF)
+               return 0;
+       /* No "naked" LF? Nothing to convert, regardless. */
+       if (!stats->lonelf)
+               return 0;
+
+       if (crlf_action == CRLF_AUTO || crlf_action == CRLF_AUTO_INPUT || crlf_action == CRLF_AUTO_CRLF) {
+               /* If we have any CR or CRLF line endings, we do not touch it */
+               /* This is the new safer autocrlf-handling */
+               if (stats->lonecr || stats->crlf)
+                       return 0;
+
+               if (convert_is_binary(stats))
+                       return 0;
+       }
+       return 1;
+
+}
 
-       len = strlen(path);
-       pos = index_name_pos(istate, path, len);
-       if (pos < 0) {
+static int validate_encoding(const char *path, const char *enc,
+                     const char *data, size_t len, int die_on_error)
+{
+       /* We only check for UTF here as UTF?? can be an alias for UTF-?? */
+       if (istarts_with(enc, "UTF")) {
                /*
-                * We might be in the middle of a merge, in which
-                * case we would read stage #2 (ours).
+                * Check for detectable errors in UTF encodings
                 */
-               int i;
-               for (i = -pos - 1;
-                    (pos < 0 && i < istate->cache_nr &&
-                     !strcmp(istate->cache[i]->name, path));
-                    i++)
-                       if (ce_stage(istate->cache[i]) == 2)
-                               pos = i;
+               if (has_prohibited_utf_bom(enc, data, len)) {
+                       const char *error_msg = _(
+                               "BOM is prohibited in '%s' if encoded as %s");
+                       /*
+                        * This advice is shown for UTF-??BE and UTF-??LE encodings.
+                        * We cut off the last two characters of the encoding name
+                        * to generate the encoding name suitable for BOMs.
+                        */
+                       const char *advise_msg = _(
+                               "The file '%s' contains a byte order "
+                               "mark (BOM). Please use UTF-%s as "
+                               "working-tree-encoding.");
+                       const char *stripped = NULL;
+                       char *upper = xstrdup_toupper(enc);
+                       upper[strlen(upper)-2] = '\0';
+                       if (!skip_prefix(upper, "UTF-", &stripped))
+                               skip_prefix(stripped, "UTF", &stripped);
+                       advise(advise_msg, path, stripped);
+                       free(upper);
+                       if (die_on_error)
+                               die(error_msg, path, enc);
+                       else {
+                               return error(error_msg, path, enc);
+                       }
+
+               } else if (is_missing_required_utf_bom(enc, data, len)) {
+                       const char *error_msg = _(
+                               "BOM is required in '%s' if encoded as %s");
+                       const char *advise_msg = _(
+                               "The file '%s' is missing a byte order "
+                               "mark (BOM). Please use UTF-%sBE or UTF-%sLE "
+                               "(depending on the byte order) as "
+                               "working-tree-encoding.");
+                       const char *stripped = NULL;
+                       char *upper = xstrdup_toupper(enc);
+                       if (!skip_prefix(upper, "UTF-", &stripped))
+                               skip_prefix(stripped, "UTF", &stripped);
+                       advise(advise_msg, path, stripped, stripped);
+                       free(upper);
+                       if (die_on_error)
+                               die(error_msg, path, enc);
+                       else {
+                               return error(error_msg, path, enc);
+                       }
+               }
+
+       }
+       return 0;
+}
+
+static void trace_encoding(const char *context, const char *path,
+                          const char *encoding, const char *buf, size_t len)
+{
+       static struct trace_key coe = TRACE_KEY_INIT(WORKING_TREE_ENCODING);
+       struct strbuf trace = STRBUF_INIT;
+       int i;
+
+       strbuf_addf(&trace, "%s (%s, considered %s):\n", context, path, encoding);
+       for (i = 0; i < len && buf; ++i) {
+               strbuf_addf(
+                       &trace, "| \033[2m%2i:\033[0m %2x \033[2m%c\033[0m%c",
+                       i,
+                       (unsigned char) buf[i],
+                       (buf[i] > 32 && buf[i] < 127 ? buf[i] : ' '),
+                       ((i+1) % 8 && (i+1) < len ? ' ' : '\n')
+               );
        }
-       if (pos < 0)
+       strbuf_addchars(&trace, '\n', 1);
+
+       trace_strbuf(&coe, &trace);
+       strbuf_release(&trace);
+}
+
+static int check_roundtrip(const char *enc_name)
+{
+       /*
+        * check_roundtrip_encoding contains a string of comma and/or
+        * space separated encodings (eg. "UTF-16, ASCII, CP1125").
+        * Search for the given encoding in that string.
+        */
+       const char *found = strcasestr(check_roundtrip_encoding, enc_name);
+       const char *next;
+       int len;
+       if (!found)
+               return 0;
+       next = found + strlen(enc_name);
+       len = strlen(check_roundtrip_encoding);
+       return (found && (
+                       /*
+                        * check that the found encoding is at the
+                        * beginning of check_roundtrip_encoding or
+                        * that it is prefixed with a space or comma
+                        */
+                       found == check_roundtrip_encoding || (
+                               (isspace(found[-1]) || found[-1] == ',')
+                       )
+               ) && (
+                       /*
+                        * check that the found encoding is at the
+                        * end of check_roundtrip_encoding or
+                        * that it is suffixed with a space or comma
+                        */
+                       next == check_roundtrip_encoding + len || (
+                               next < check_roundtrip_encoding + len &&
+                               (isspace(next[0]) || next[0] == ',')
+                       )
+               ));
+}
+
+static const char *default_encoding = "UTF-8";
+
+static int encode_to_git(const char *path, const char *src, size_t src_len,
+                        struct strbuf *buf, const char *enc, int conv_flags)
+{
+       char *dst;
+       size_t dst_len;
+       int die_on_error = conv_flags & CONV_WRITE_OBJECT;
+
+       /*
+        * No encoding is specified or there is nothing to encode.
+        * Tell the caller that the content was not modified.
+        */
+       if (!enc || (src && !src_len))
                return 0;
-       data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz);
-       if (!data || type != OBJ_BLOB) {
-               free(data);
+
+       /*
+        * Looks like we got called from "would_convert_to_git()".
+        * This means Git wants to know if it would encode (= modify!)
+        * the content. Let's answer with "yes", since an encoding was
+        * specified.
+        */
+       if (!buf && !src)
+               return 1;
+
+       if (validate_encoding(path, enc, src, src_len, die_on_error))
                return 0;
+
+       trace_encoding("source", path, enc, src, src_len);
+       dst = reencode_string_len(src, src_len, default_encoding, enc,
+                                 &dst_len);
+       if (!dst) {
+               /*
+                * We could add the blob "as-is" to Git. However, on checkout
+                * we would try to reencode to the original encoding. This
+                * would fail and we would leave the user with a messed-up
+                * working tree. Let's try to avoid this by screaming loud.
+                */
+               const char* msg = _("failed to encode '%s' from %s to %s");
+               if (die_on_error)
+                       die(msg, path, enc, default_encoding);
+               else {
+                       error(msg, path, enc, default_encoding);
+                       return 0;
+               }
        }
+       trace_encoding("destination", path, default_encoding, dst, dst_len);
 
-       has_cr = memchr(data, '\r', sz) != NULL;
-       free(data);
-       return has_cr;
+       /*
+        * UTF supports lossless conversion round tripping [1] and conversions
+        * between UTF and other encodings are mostly round trip safe as
+        * Unicode aims to be a superset of all other character encodings.
+        * However, certain encodings (e.g. SHIFT-JIS) are known to have round
+        * trip issues [2]. Check the round trip conversion for all encodings
+        * listed in core.checkRoundtripEncoding.
+        *
+        * The round trip check is only performed if content is written to Git.
+        * This ensures that no information is lost during conversion to/from
+        * the internal UTF-8 representation.
+        *
+        * Please note, the code below is not tested because I was not able to
+        * generate a faulty round trip without an iconv error. Iconv errors
+        * are already caught above.
+        *
+        * [1] http://unicode.org/faq/utf_bom.html#gen2
+        * [2] https://support.microsoft.com/en-us/help/170559/prb-conversion-problem-between-shift-jis-and-unicode
+        */
+       if (die_on_error && check_roundtrip(enc)) {
+               char *re_src;
+               size_t re_src_len;
+
+               re_src = reencode_string_len(dst, dst_len,
+                                            enc, default_encoding,
+                                            &re_src_len);
+
+               trace_printf("Checking roundtrip encoding for %s...\n", enc);
+               trace_encoding("reencoded source", path, enc,
+                              re_src, re_src_len);
+
+               if (!re_src || src_len != re_src_len ||
+                   memcmp(src, re_src, src_len)) {
+                       const char* msg = _("encoding '%s' from %s to %s and "
+                                           "back is not the same");
+                       die(msg, path, enc, default_encoding);
+               }
+
+               free(re_src);
+       }
+
+       strbuf_attach(buf, dst, dst_len, dst_len + 1);
+       return 1;
+}
+
+static int encode_to_worktree(const char *path, const char *src, size_t src_len,
+                             struct strbuf *buf, const char *enc)
+{
+       char *dst;
+       size_t dst_len;
+
+       /*
+        * No encoding is specified or there is nothing to encode.
+        * Tell the caller that the content was not modified.
+        */
+       if (!enc || (src && !src_len))
+               return 0;
+
+       dst = reencode_string_len(src, src_len, enc, default_encoding,
+                                 &dst_len);
+       if (!dst) {
+               error(_("failed to encode '%s' from %s to %s"),
+                     path, default_encoding, enc);
+               return 0;
+       }
+
+       strbuf_attach(buf, dst, dst_len, dst_len + 1);
+       return 1;
 }
 
-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)
+                      enum crlf_action crlf_action, int conv_flags)
 {
        struct text_stat stats;
        char *dst;
+       int convert_crlf_into_lf;
 
        if (crlf_action == CRLF_BINARY ||
-           (crlf_action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE) ||
            (src && !len))
                return 0;
 
@@ -208,36 +522,39 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
                return 1;
 
        gather_stats(src, len, &stats);
+       /* Optimization: No CRLF? Nothing to convert, regardless. */
+       convert_crlf_into_lf = !!stats.crlf;
 
-       if (crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS) {
-               /*
-                * We're currently not going to even try to convert stuff
-                * that has bare CR characters. Does anybody do that crazy
-                * stuff?
-                */
-               if (stats.cr != stats.crlf)
+       if (crlf_action == CRLF_AUTO || crlf_action == CRLF_AUTO_INPUT || crlf_action == CRLF_AUTO_CRLF) {
+               if (convert_is_binary(&stats))
                        return 0;
-
                /*
-                * And add some heuristics for binary vs text, of course...
+                * If the file in the index has any CR in it, do not
+                * convert.  This is the new safer autocrlf handling,
+                * unless we want to renormalize in a merge or
+                * cherry-pick.
                 */
-               if (is_binary(len, &stats))
-                       return 0;
-
-               if (crlf_action == CRLF_GUESS) {
-                       /*
-                        * If the file in the index has any CR in it, do not convert.
-                        * This is the new safer autocrlf handling.
-                        */
-                       if (has_cr_in_index(path))
-                               return 0;
+               if ((!(conv_flags & CONV_EOL_RENORMALIZE)) &&
+                   has_crlf_in_index(istate, path))
+                       convert_crlf_into_lf = 0;
+       }
+       if (((conv_flags & CONV_EOL_RNDTRP_WARN) ||
+            ((conv_flags & CONV_EOL_RNDTRP_DIE) && len))) {
+               struct text_stat new_stats;
+               memcpy(&new_stats, &stats, sizeof(new_stats));
+               /* simulate "git add" */
+               if (convert_crlf_into_lf) {
+                       new_stats.lonelf += new_stats.crlf;
+                       new_stats.crlf = 0;
+               }
+               /* simulate "git checkout" */
+               if (will_convert_lf_to_crlf(&new_stats, crlf_action)) {
+                       new_stats.crlf += new_stats.lonelf;
+                       new_stats.lonelf = 0;
                }
+               check_global_conv_flags_eol(path, crlf_action, &stats, &new_stats, conv_flags);
        }
-
-       check_safe_crlf(path, crlf_action, &stats, checksafe);
-
-       /* Optimization: No CR? Nothing to convert, regardless. */
-       if (!stats.cr)
+       if (!convert_crlf_into_lf)
                return 0;
 
        /*
@@ -251,7 +568,7 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
        if (strbuf_avail(buf) + buf->len < len)
                strbuf_grow(buf, len - buf->len);
        dst = buf->buf;
-       if (crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS) {
+       if (crlf_action == CRLF_AUTO || crlf_action == CRLF_AUTO_INPUT || crlf_action == CRLF_AUTO_CRLF) {
                /*
                 * If we guessed, we already know we rejected a file with
                 * lone CR, and we can strip a CR without looking at what
@@ -273,7 +590,7 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
        return 1;
 }
 
-static int crlf_to_worktree(const char *path, const char *src, size_t len,
+static int crlf_to_worktree(const char *src, size_t len,
                            struct strbuf *buf, enum crlf_action crlf_action)
 {
        char *to_free = NULL;
@@ -283,36 +600,14 @@ static int crlf_to_worktree(const char *path, const char *src, size_t len,
                return 0;
 
        gather_stats(src, len, &stats);
-
-       /* No LF? Nothing to convert, regardless. */
-       if (!stats.lf)
-               return 0;
-
-       /* Was it already in CRLF format? */
-       if (stats.lf == stats.crlf)
+       if (!will_convert_lf_to_crlf(&stats, crlf_action))
                return 0;
 
-       if (crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS) {
-               if (crlf_action == CRLF_GUESS) {
-                       /* If we have any CR or CRLF line endings, we do not touch it */
-                       /* This is the new safer autocrlf-handling */
-                       if (stats.cr > 0 || stats.crlf > 0)
-                               return 0;
-               }
-
-               /* If we have any bare CR characters, we're not going to touch it */
-               if (stats.cr != stats.crlf)
-                       return 0;
-
-               if (is_binary(len, &stats))
-                       return 0;
-       }
-
        /* are we "faking" in place editing ? */
        if (src == buf->buf)
                to_free = strbuf_detach(buf, NULL);
 
-       strbuf_grow(buf, len + stats.lf - stats.crlf);
+       strbuf_grow(buf, len + stats.lonelf);
        for (;;) {
                const char *nl = memchr(src, '\n', len);
                if (!nl)
@@ -335,16 +630,17 @@ static int crlf_to_worktree(const char *path, const char *src, size_t len,
 struct filter_params {
        const char *src;
        unsigned long size;
+       int fd;
        const char *cmd;
        const char *path;
 };
 
-static int filter_buffer(int in, int out, void *data)
+static int filter_buffer_or_fd(int in, int out, void *data)
 {
        /*
         * Spawn cmd and feed the buffer contents through its stdin.
         */
-       struct child_process child_process;
+       struct child_process child_process = CHILD_PROCESS_INIT;
        struct filter_params *params = (struct filter_params *)data;
        int write_err, status;
        const char *argv[] = { NULL, NULL };
@@ -367,35 +663,48 @@ static int filter_buffer(int in, int out, void *data)
 
        argv[0] = cmd.buf;
 
-       memset(&child_process, 0, sizeof(child_process));
        child_process.argv = argv;
        child_process.use_shell = 1;
        child_process.in = -1;
        child_process.out = out;
 
-       if (start_command(&child_process))
-               return error("cannot fork to run external filter %s", params->cmd);
+       if (start_command(&child_process)) {
+               strbuf_release(&cmd);
+               return error(_("cannot fork to run external filter '%s'"),
+                            params->cmd);
+       }
 
        sigchain_push(SIGPIPE, SIG_IGN);
 
-       write_err = (write_in_full(child_process.in, params->src, params->size) < 0);
+       if (params->src) {
+               write_err = (write_in_full(child_process.in,
+                                          params->src, params->size) < 0);
+               if (errno == EPIPE)
+                       write_err = 0;
+       } else {
+               write_err = copy_fd(params->fd, child_process.in);
+               if (write_err == COPY_WRITE_ERROR && errno == EPIPE)
+                       write_err = 0;
+       }
+
        if (close(child_process.in))
                write_err = 1;
        if (write_err)
-               error("cannot feed the input to external filter %s", params->cmd);
+               error(_("cannot feed the input to external filter '%s'"),
+                     params->cmd);
 
        sigchain_pop(SIGPIPE);
 
        status = finish_command(&child_process);
        if (status)
-               error("external filter %s failed %d", params->cmd, status);
+               error(_("external filter '%s' failed %d"), params->cmd, status);
 
        strbuf_release(&cmd);
        return (write_err || status);
 }
 
-static int apply_filter(const char *path, const char *src, size_t len,
-                        struct strbuf *dst, const char *cmd)
+static int apply_single_file_filter(const char *path, const char *src, size_t len, int fd,
+                                   struct strbuf *dst, const char *cmd)
 {
        /*
         * Create a pipeline to have the command filter the buffer's
@@ -403,23 +712,18 @@ static int apply_filter(const char *path, const char *src, size_t len,
         *
         * (child --> cmd) --> us
         */
-       int ret = 1;
+       int err = 0;
        struct strbuf nbuf = STRBUF_INIT;
        struct async async;
        struct filter_params params;
 
-       if (!cmd)
-               return 0;
-
-       if (!dst)
-               return 1;
-
        memset(&async, 0, sizeof(async));
-       async.proc = filter_buffer;
+       async.proc = filter_buffer_or_fd;
        async.data = &params;
        async.out = -1;
        params.src = src;
        params.size = len;
+       params.fd = fd;
        params.cmd = cmd;
        params.path = path;
 
@@ -427,24 +731,237 @@ static int apply_filter(const char *path, const char *src, size_t len,
        if (start_async(&async))
                return 0;       /* error was already reported */
 
-       if (strbuf_read(&nbuf, async.out, len) < 0) {
-               error("read from external filter %s failed", cmd);
-               ret = 0;
+       if (strbuf_read(&nbuf, async.out, 0) < 0) {
+               err = error(_("read from external filter '%s' failed"), cmd);
        }
        if (close(async.out)) {
-               error("read from external filter %s failed", cmd);
-               ret = 0;
+               err = error(_("read from external filter '%s' failed"), cmd);
        }
        if (finish_async(&async)) {
-               error("external filter %s failed", cmd);
-               ret = 0;
+               err = error(_("external filter '%s' failed"), cmd);
        }
 
-       if (ret) {
+       if (!err) {
                strbuf_swap(dst, &nbuf);
        }
        strbuf_release(&nbuf);
-       return ret;
+       return !err;
+}
+
+#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! */
+       unsigned int supported_capabilities;
+};
+
+static int subprocess_map_initialized;
+static struct hashmap subprocess_map;
+
+static int start_multi_file_filter_fn(struct subprocess_entry *subprocess)
+{
+       static int versions[] = {2, 0};
+       static struct subprocess_capability capabilities[] = {
+               { "clean",  CAP_CLEAN  },
+               { "smudge", CAP_SMUDGE },
+               { "delay",  CAP_DELAY  },
+               { NULL, 0 }
+       };
+       struct cmd2process *entry = (struct cmd2process *)subprocess;
+       return subprocess_handshake(subprocess, "git-filter", versions, NULL,
+                                   capabilities,
+                                   &entry->supported_capabilities);
+}
+
+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,
+                                  struct delayed_checkout *dco)
+{
+       int err;
+       int can_delay = 0;
+       struct cmd2process *entry;
+       struct child_process *process;
+       struct strbuf nbuf = STRBUF_INIT;
+       struct strbuf filter_status = STRBUF_INIT;
+       const char *filter_type;
+
+       if (!subprocess_map_initialized) {
+               subprocess_map_initialized = 1;
+               hashmap_init(&subprocess_map, cmd2process_cmp, NULL, 0);
+               entry = NULL;
+       } else {
+               entry = (struct cmd2process *)subprocess_find_entry(&subprocess_map, cmd);
+       }
+
+       fflush(NULL);
+
+       if (!entry) {
+               entry = xmalloc(sizeof(*entry));
+               entry->supported_capabilities = 0;
+
+               if (subprocess_start(&subprocess_map, &entry->subprocess, cmd, start_multi_file_filter_fn)) {
+                       free(entry);
+                       return 0;
+               }
+       }
+       process = &entry->subprocess.process;
+
+       if (!(entry->supported_capabilities & wanted_capability))
+               return 0;
+
+       if (wanted_capability & CAP_CLEAN)
+               filter_type = "clean";
+       else if (wanted_capability & CAP_SMUDGE)
+               filter_type = "smudge";
+       else
+               die(_("unexpected filter type"));
+
+       sigchain_push(SIGPIPE, SIG_IGN);
+
+       assert(strlen(filter_type) < LARGE_PACKET_DATA_MAX - strlen("command=\n"));
+       err = packet_write_fmt_gently(process->in, "command=%s\n", filter_type);
+       if (err)
+               goto done;
+
+       err = strlen(path) > LARGE_PACKET_DATA_MAX - strlen("pathname=\n");
+       if (err) {
+               error(_("path name too long for external filter"));
+               goto done;
+       }
+
+       err = packet_write_fmt_gently(process->in, "pathname=%s\n", path);
+       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 (fd >= 0)
+               err = write_packetized_from_fd(fd, process->in);
+       else
+               err = write_packetized_from_buf(src, len, process->in);
+       if (err)
+               goto done;
+
+       err = subprocess_read_status(process->out, &filter_status);
+       if (err)
+               goto done;
+
+       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 = 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;
+
+       err = strcmp(filter_status.buf, "success");
+
+done:
+       sigchain_pop(SIGPIPE);
+
+       if (err)
+               handle_filter_error(&filter_status, entry, 0);
+       return !err;
 }
 
 static struct convert_driver {
@@ -452,12 +969,40 @@ static struct convert_driver {
        struct convert_driver *next;
        const char *smudge;
        const char *clean;
+       const char *process;
        int required;
 } *user_convert, **user_convert_tail;
 
+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,
+                       struct delayed_checkout *dco)
+{
+       const char *cmd = NULL;
+
+       if (!drv)
+               return 0;
+
+       if (!dst)
+               return 1;
+
+       if ((wanted_capability & CAP_CLEAN) && !drv->process && drv->clean)
+               cmd = drv->clean;
+       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, dco);
+
+       return 0;
+}
+
 static int read_convert_config(const char *var, const char *value, void *cb)
 {
-       const char *ep, *name;
+       const char *key, *name;
        int namelen;
        struct convert_driver *drv;
 
@@ -465,10 +1010,8 @@ static int read_convert_config(const char *var, const char *value, void *cb)
         * External conversion drivers are configured using
         * "filter.<name>.variable".
         */
-       if (prefixcmp(var, "filter.") || (ep = strrchr(var, '.')) == var + 6)
+       if (parse_config_key(var, "filter", &name, &namelen, &key) < 0 || !name)
                return 0;
-       name = var + 7;
-       namelen = ep - name;
        for (drv = user_convert; drv; drv = drv->next)
                if (!strncmp(drv->name, name, namelen) && !drv->name[namelen])
                        break;
@@ -479,8 +1022,6 @@ static int read_convert_config(const char *var, const char *value, void *cb)
                user_convert_tail = &(drv->next);
        }
 
-       ep++;
-
        /*
         * filter.<name>.smudge and filter.<name>.clean specifies
         * the command line:
@@ -490,13 +1031,16 @@ static int read_convert_config(const char *var, const char *value, void *cb)
         * The command-line will not be interpolated in any way.
         */
 
-       if (!strcmp("smudge", ep))
+       if (!strcmp("smudge", key))
                return git_config_string(&drv->smudge, var, value);
 
-       if (!strcmp("clean", ep))
+       if (!strcmp("clean", key))
                return git_config_string(&drv->clean, var, value);
 
-       if (!strcmp("required", ep)) {
+       if (!strcmp("process", key))
+               return git_config_string(&drv->process, var, value);
+
+       if (!strcmp("required", key)) {
                drv->required = git_config_bool(var, value);
                return 0;
        }
@@ -546,8 +1090,8 @@ static int count_ident(const char *cp, unsigned long size)
        return cnt;
 }
 
-static int ident_to_git(const char *path, const char *src, size_t len,
-                        struct strbuf *buf, int ident)
+static int ident_to_git(const char *src, size_t len,
+                       struct strbuf *buf, int ident)
 {
        char *dst, *dollar;
 
@@ -590,10 +1134,10 @@ static int ident_to_git(const char *path, const char *src, size_t len,
        return 1;
 }
 
-static int ident_to_worktree(const char *path, const char *src, size_t len,
-                             struct strbuf *buf, int ident)
+static int ident_to_worktree(const char *src, size_t len,
+                            struct strbuf *buf, int ident)
 {
-       unsigned char sha1[20];
+       struct object_id oid;
        char *to_free = NULL, *dollar, *spc;
        int cnt;
 
@@ -607,9 +1151,9 @@ static int ident_to_worktree(const char *path, const char *src, size_t len,
        /* are we "faking" in place editing ? */
        if (src == buf->buf)
                to_free = strbuf_detach(buf, NULL);
-       hash_sha1_file(src, len, "blob", sha1);
+       hash_object_file(src, len, "blob", &oid);
 
-       strbuf_grow(buf, len + cnt * 43);
+       strbuf_grow(buf, len + cnt * (the_hash_algo->hexsz + 3));
        for (;;) {
                /* step 1: run to the next '$' */
                dollar = memchr(src, '$', len);
@@ -664,7 +1208,7 @@ static int ident_to_worktree(const char *path, const char *src, size_t len,
 
                /* step 4: substitute */
                strbuf_addstr(buf, "Id: ");
-               strbuf_add(buf, sha1_to_hex(sha1), 40);
+               strbuf_addstr(buf, oid_to_hex(&oid));
                strbuf_addstr(buf, " $");
        }
        strbuf_add(buf, src, len);
@@ -673,7 +1217,25 @@ static int ident_to_worktree(const char *path, const char *src, size_t len,
        return 1;
 }
 
-static enum crlf_action git_path_check_crlf(const char *path, struct git_attr_check *check)
+static const char *git_path_check_encoding(struct attr_check_item *check)
+{
+       const char *value = check->value;
+
+       if (ATTR_UNSET(value) || !strlen(value))
+               return NULL;
+
+       if (ATTR_TRUE(value) || ATTR_FALSE(value)) {
+               die(_("true/false are no valid working-tree-encodings"));
+       }
+
+       /* Don't encode to the default encoding */
+       if (same_encoding(value, default_encoding))
+               return NULL;
+
+       return value;
+}
+
+static enum crlf_action git_path_check_crlf(struct attr_check_item *check)
 {
        const char *value = check->value;
 
@@ -684,13 +1246,13 @@ static enum crlf_action git_path_check_crlf(const char *path, struct git_attr_ch
        else if (ATTR_UNSET(value))
                ;
        else if (!strcmp(value, "input"))
-               return CRLF_INPUT;
+               return CRLF_TEXT_INPUT;
        else if (!strcmp(value, "auto"))
                return CRLF_AUTO;
-       return CRLF_GUESS;
+       return CRLF_UNDEFINED;
 }
 
-static enum eol git_path_check_eol(const char *path, struct git_attr_check *check)
+static enum eol git_path_check_eol(struct attr_check_item *check)
 {
        const char *value = check->value;
 
@@ -703,8 +1265,7 @@ static enum eol git_path_check_eol(const char *path, struct git_attr_check *chec
        return EOL_UNSET;
 }
 
-static struct convert_driver *git_path_check_convert(const char *path,
-                                            struct git_attr_check *check)
+static struct convert_driver *git_path_check_convert(struct attr_check_item *check)
 {
        const char *value = check->value;
        struct convert_driver *drv;
@@ -717,152 +1278,235 @@ static struct convert_driver *git_path_check_convert(const char *path,
        return NULL;
 }
 
-static int git_path_check_ident(const char *path, struct git_attr_check *check)
+static int git_path_check_ident(struct attr_check_item *check)
 {
        const char *value = check->value;
 
        return !!ATTR_TRUE(value);
 }
 
-static enum crlf_action input_crlf_action(enum crlf_action text_attr, enum eol eol_attr)
-{
-       if (text_attr == CRLF_BINARY)
-               return CRLF_BINARY;
-       if (eol_attr == EOL_LF)
-               return CRLF_INPUT;
-       if (eol_attr == EOL_CRLF)
-               return CRLF_CRLF;
-       return text_attr;
-}
-
 struct conv_attrs {
        struct convert_driver *drv;
-       enum crlf_action crlf_action;
-       enum eol eol_attr;
+       enum crlf_action attr_action; /* What attr says */
+       enum crlf_action crlf_action; /* When no attr is set, use core.autocrlf */
        int ident;
+       const char *working_tree_encoding; /* Supported encoding or default encoding if NULL */
 };
 
-static const char *conv_attr_name[] = {
-       "crlf", "ident", "filter", "eol", "text",
-};
-#define NUM_CONV_ATTRS ARRAY_SIZE(conv_attr_name)
-
-static void convert_attrs(struct conv_attrs *ca, const char *path)
+static void convert_attrs(const struct index_state *istate,
+                         struct conv_attrs *ca, const char *path)
 {
-       int i;
-       static struct git_attr_check ccheck[NUM_CONV_ATTRS];
+       static struct attr_check *check;
+       struct attr_check_item *ccheck = NULL;
 
-       if (!ccheck[0].attr) {
-               for (i = 0; i < NUM_CONV_ATTRS; i++)
-                       ccheck[i].attr = git_attr(conv_attr_name[i]);
+       if (!check) {
+               check = attr_check_initl("crlf", "ident", "filter",
+                                        "eol", "text", "working-tree-encoding",
+                                        NULL);
                user_convert_tail = &user_convert;
                git_config(read_convert_config, NULL);
        }
 
-       if (!git_check_attr(path, NUM_CONV_ATTRS, ccheck)) {
-               ca->crlf_action = git_path_check_crlf(path, ccheck + 4);
-               if (ca->crlf_action == CRLF_GUESS)
-                       ca->crlf_action = git_path_check_crlf(path, ccheck + 0);
-               ca->ident = git_path_check_ident(path, ccheck + 1);
-               ca->drv = git_path_check_convert(path, ccheck + 2);
-               ca->eol_attr = git_path_check_eol(path, ccheck + 3);
-       } else {
-               ca->drv = NULL;
-               ca->crlf_action = CRLF_GUESS;
-               ca->eol_attr = EOL_UNSET;
-               ca->ident = 0;
+       git_check_attr(istate, path, check);
+       ccheck = check->items;
+       ca->crlf_action = git_path_check_crlf(ccheck + 4);
+       if (ca->crlf_action == CRLF_UNDEFINED)
+               ca->crlf_action = git_path_check_crlf(ccheck + 0);
+       ca->ident = git_path_check_ident(ccheck + 1);
+       ca->drv = git_path_check_convert(ccheck + 2);
+       if (ca->crlf_action != CRLF_BINARY) {
+               enum eol eol_attr = git_path_check_eol(ccheck + 3);
+               if (ca->crlf_action == CRLF_AUTO && eol_attr == EOL_LF)
+                       ca->crlf_action = CRLF_AUTO_INPUT;
+               else if (ca->crlf_action == CRLF_AUTO && eol_attr == EOL_CRLF)
+                       ca->crlf_action = CRLF_AUTO_CRLF;
+               else if (eol_attr == EOL_LF)
+                       ca->crlf_action = CRLF_TEXT_INPUT;
+               else if (eol_attr == EOL_CRLF)
+                       ca->crlf_action = CRLF_TEXT_CRLF;
        }
+       ca->working_tree_encoding = git_path_check_encoding(ccheck + 5);
+
+       /* Save attr and make a decision for action */
+       ca->attr_action = ca->crlf_action;
+       if (ca->crlf_action == CRLF_TEXT)
+               ca->crlf_action = text_eol_is_crlf() ? CRLF_TEXT_CRLF : CRLF_TEXT_INPUT;
+       if (ca->crlf_action == CRLF_UNDEFINED && auto_crlf == AUTO_CRLF_FALSE)
+               ca->crlf_action = CRLF_BINARY;
+       if (ca->crlf_action == CRLF_UNDEFINED && auto_crlf == AUTO_CRLF_TRUE)
+               ca->crlf_action = CRLF_AUTO_CRLF;
+       if (ca->crlf_action == CRLF_UNDEFINED && auto_crlf == AUTO_CRLF_INPUT)
+               ca->crlf_action = CRLF_AUTO_INPUT;
 }
 
-int convert_to_git(const char *path, const char *src, size_t len,
-                   struct strbuf *dst, enum safe_crlf checksafe)
+int would_convert_to_git_filter_fd(const struct index_state *istate, const char *path)
+{
+       struct conv_attrs ca;
+
+       convert_attrs(istate, &ca, path);
+       if (!ca.drv)
+               return 0;
+
+       /*
+        * Apply a filter to an fd only if the filter is required to succeed.
+        * We must die if the filter fails, because the original data before
+        * filtering is not available.
+        */
+       if (!ca.drv->required)
+               return 0;
+
+       return apply_filter(path, NULL, 0, -1, NULL, ca.drv, CAP_CLEAN, NULL);
+}
+
+const char *get_convert_attr_ascii(const struct index_state *istate, const char *path)
 {
-       int ret = 0;
-       const char *filter = NULL;
-       int required = 0;
        struct conv_attrs ca;
 
-       convert_attrs(&ca, path);
-       if (ca.drv) {
-               filter = ca.drv->clean;
-               required = ca.drv->required;
+       convert_attrs(istate, &ca, path);
+       switch (ca.attr_action) {
+       case CRLF_UNDEFINED:
+               return "";
+       case CRLF_BINARY:
+               return "-text";
+       case CRLF_TEXT:
+               return "text";
+       case CRLF_TEXT_INPUT:
+               return "text eol=lf";
+       case CRLF_TEXT_CRLF:
+               return "text eol=crlf";
+       case CRLF_AUTO:
+               return "text=auto";
+       case CRLF_AUTO_CRLF:
+               return "text=auto eol=crlf";
+       case CRLF_AUTO_INPUT:
+               return "text=auto eol=lf";
        }
+       return "";
+}
+
+int convert_to_git(const struct index_state *istate,
+                  const char *path, const char *src, size_t len,
+                  struct strbuf *dst, int conv_flags)
+{
+       int ret = 0;
+       struct conv_attrs ca;
+
+       convert_attrs(istate, &ca, path);
 
-       ret |= apply_filter(path, src, len, dst, filter);
-       if (!ret && required)
-               die("%s: clean filter '%s' failed", path, ca.drv->name);
+       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);
 
        if (ret && dst) {
                src = dst->buf;
                len = dst->len;
        }
-       ca.crlf_action = input_crlf_action(ca.crlf_action, ca.eol_attr);
-       ret |= crlf_to_git(path, src, len, dst, ca.crlf_action, checksafe);
+
+       ret |= encode_to_git(path, src, len, dst, ca.working_tree_encoding, conv_flags);
        if (ret && dst) {
                src = dst->buf;
                len = dst->len;
        }
-       return ret | ident_to_git(path, src, len, dst, ca.ident);
+
+       if (!(conv_flags & CONV_EOL_KEEP_CRLF)) {
+               ret |= crlf_to_git(istate, path, src, len, dst, ca.crlf_action, conv_flags);
+               if (ret && dst) {
+                       src = dst->buf;
+                       len = dst->len;
+               }
+       }
+       return ret | ident_to_git(src, len, dst, ca.ident);
+}
+
+void convert_to_git_filter_fd(const struct index_state *istate,
+                             const char *path, int fd, struct strbuf *dst,
+                             int conv_flags)
+{
+       struct conv_attrs ca;
+       convert_attrs(istate, &ca, path);
+
+       assert(ca.drv);
+       assert(ca.drv->clean || ca.drv->process);
+
+       if (!apply_filter(path, NULL, 0, fd, dst, ca.drv, CAP_CLEAN, NULL))
+               die(_("%s: clean filter '%s' failed"), path, ca.drv->name);
+
+       encode_to_git(path, dst->buf, dst->len, dst, ca.working_tree_encoding, conv_flags);
+       crlf_to_git(istate, path, dst->buf, dst->len, dst, ca.crlf_action, conv_flags);
+       ident_to_git(dst->buf, dst->len, dst, ca.ident);
 }
 
-static int convert_to_working_tree_internal(const char *path, const char *src,
+static int convert_to_working_tree_internal(const struct index_state *istate,
+                                           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;
-       const char *filter = NULL;
-       int required = 0;
        struct conv_attrs ca;
 
-       convert_attrs(&ca, path);
-       if (ca.drv) {
-               filter = ca.drv->smudge;
-               required = ca.drv->required;
-       }
+       convert_attrs(istate, &ca, path);
 
-       ret |= ident_to_worktree(path, src, len, dst, ca.ident);
+       ret |= ident_to_worktree(src, len, dst, ca.ident);
        if (ret) {
                src = dst->buf;
                len = dst->len;
        }
        /*
         * CRLF conversion can be skipped if normalizing, unless there
-        * is a smudge filter.  The filter might expect CRLFs.
+        * is a smudge or process filter (even if the process filter doesn't
+        * support smudge).  The filters might expect CRLFs.
         */
-       if (filter || !normalizing) {
-               ca.crlf_action = input_crlf_action(ca.crlf_action, ca.eol_attr);
-               ret |= crlf_to_worktree(path, src, len, dst, ca.crlf_action);
+       if ((ca.drv && (ca.drv->smudge || ca.drv->process)) || !normalizing) {
+               ret |= crlf_to_worktree(src, len, dst, ca.crlf_action);
                if (ret) {
                        src = dst->buf;
                        len = dst->len;
                }
        }
 
-       ret_filter = apply_filter(path, src, len, dst, filter);
-       if (!ret_filter && required)
-               die("%s: smudge filter %s failed", path, ca.drv->name);
+       ret |= encode_to_worktree(path, src, len, dst, ca.working_tree_encoding);
+       if (ret) {
+               src = dst->buf;
+               len = dst->len;
+       }
+
+       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 convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst)
+int async_convert_to_working_tree(const struct index_state *istate,
+                                 const char *path, const char *src,
+                                 size_t len, struct strbuf *dst,
+                                 void *dco)
+{
+       return convert_to_working_tree_internal(istate, path, src, len, dst, 0, dco);
+}
+
+int convert_to_working_tree(const struct index_state *istate,
+                           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(istate, 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(istate, 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_FALSE);
+       return ret | convert_to_git(istate, path, src, len, dst, CONV_EOL_RENORMALIZE);
 }
 
 /*****************************************************************
  *
- * Streaming converison support
+ * Streaming conversion support
  *
  *****************************************************************/
 
@@ -1141,16 +1785,16 @@ struct ident_filter {
        struct stream_filter filter;
        struct strbuf left;
        int state;
-       char ident[45]; /* ": x40 $" */
+       char ident[GIT_MAX_HEXSZ + 5]; /* ": x40 $" */
 };
 
 static int is_foreign_ident(const char *str)
 {
        int i;
 
-       if (prefixcmp(str, "$Id: "))
+       if (!skip_prefix(str, "$Id: ", &str))
                return 0;
-       for (i = 5; str[i]; i++) {
+       for (i = 0; str[i]; i++) {
                if (isspace(str[i]) && str[i+1] != '$')
                        return 1;
        }
@@ -1185,8 +1829,9 @@ static int ident_filter_fn(struct stream_filter *filter,
                switch (ident->state) {
                default:
                        strbuf_add(&ident->left, head, ident->state);
+                       /* fallthrough */
                case IDENT_SKIPPING:
-                       /* fallthru */
+                       /* fallthrough */
                case IDENT_DRAINING:
                        ident_drain(ident, &output, osize_p);
                }
@@ -1265,11 +1910,12 @@ static struct stream_filter_vtbl ident_vtbl = {
        ident_free_fn,
 };
 
-static struct stream_filter *ident_filter(const unsigned char *sha1)
+static struct stream_filter *ident_filter(const struct object_id *oid)
 {
        struct ident_filter *ident = xmalloc(sizeof(*ident));
 
-       sprintf(ident->ident, ": %s $", sha1_to_hex(sha1));
+       xsnprintf(ident->ident, sizeof(ident->ident),
+                 ": %s $", oid_to_hex(oid));
        strbuf_init(&ident->left, 0);
        ident->filter.vtbl = &ident_vtbl;
        ident->state = 0;
@@ -1284,29 +1930,30 @@ static struct stream_filter *ident_filter(const unsigned char *sha1)
  * Note that you would be crazy to set CRLF, smuge/clean or ident to a
  * large binary blob you would want us not to slurp into the memory!
  */
-struct stream_filter *get_stream_filter(const char *path, const unsigned char *sha1)
+struct stream_filter *get_stream_filter(const struct index_state *istate,
+                                       const char *path,
+                                       const struct object_id *oid)
 {
        struct conv_attrs ca;
-       enum crlf_action crlf_action;
        struct stream_filter *filter = NULL;
 
-       convert_attrs(&ca, path);
-
-       if (ca.drv && (ca.drv->smudge || ca.drv->clean))
-               return filter;
+       convert_attrs(istate, &ca, path);
+       if (ca.drv && (ca.drv->process || ca.drv->smudge || ca.drv->clean))
+               return NULL;
 
-       if (ca.ident)
-               filter = ident_filter(sha1);
+       if (ca.working_tree_encoding)
+               return NULL;
 
-       crlf_action = input_crlf_action(ca.crlf_action, ca.eol_attr);
+       if (ca.crlf_action == CRLF_AUTO || ca.crlf_action == CRLF_AUTO_CRLF)
+               return NULL;
 
-       if ((crlf_action == CRLF_BINARY) || (crlf_action == CRLF_INPUT) ||
-           (crlf_action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE))
-               filter = cascade_filter(filter, &null_filter_singleton);
+       if (ca.ident)
+               filter = ident_filter(oid);
 
-       else if (output_eol(crlf_action) == EOL_CRLF &&
-                !(crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS))
+       if (output_eol(ca.crlf_action) == EOL_CRLF)
                filter = cascade_filter(filter, lf_to_crlf_filter());
+       else
+               filter = cascade_filter(filter, &null_filter_singleton);
 
        return filter;
 }