]> git.ipfire.org Git - thirdparty/git.git/blobdiff - apply.c
Merge branch 'pb/pull-fetch-doc'
[thirdparty/git.git] / apply.c
diff --git a/apply.c b/apply.c
index 892ede5a318f751c80fcceab036a8cbaee391774..144c19aaca80d943e237ffd84f215a49415e0d5a 100644 (file)
--- a/apply.c
+++ b/apply.c
 #include "rerere.h"
 #include "apply.h"
 
+struct gitdiff_data {
+       struct strbuf *root;
+       int linenr;
+       int p_value;
+};
+
 static void git_apply_config(void)
 {
        git_config_get_string_const("apply.whitespace", &apply_default_whitespace);
        git_config_get_string_const("apply.ignorewhitespace", &apply_default_ignorewhitespace);
-       git_config(git_default_config, NULL);
+       git_config(git_xmerge_config, NULL);
 }
 
 static int parse_whitespace_option(struct apply_state *state, const char *option)
@@ -56,6 +62,10 @@ static int parse_whitespace_option(struct apply_state *state, const char *option
                state->ws_error_action = correct_ws_error;
                return 0;
        }
+       /*
+        * Please update $__git_whitespacelist in git-completion.bash
+        * when you add new options.
+        */
        return error(_("unrecognized whitespace option '%s'"), option);
 }
 
@@ -197,40 +207,6 @@ struct fragment {
 #define BINARY_DELTA_DEFLATED  1
 #define BINARY_LITERAL_DEFLATED 2
 
-/*
- * This represents a "patch" to a file, both metainfo changes
- * such as creation/deletion, filemode and content changes represented
- * as a series of fragments.
- */
-struct patch {
-       char *new_name, *old_name, *def_name;
-       unsigned int old_mode, new_mode;
-       int is_new, is_delete;  /* -1 = unknown, 0 = false, 1 = true */
-       int rejected;
-       unsigned ws_rule;
-       int lines_added, lines_deleted;
-       int score;
-       int extension_linenr; /* first line specifying delete/new/rename/copy */
-       unsigned int is_toplevel_relative:1;
-       unsigned int inaccurate_eof:1;
-       unsigned int is_binary:1;
-       unsigned int is_copy:1;
-       unsigned int is_rename:1;
-       unsigned int recount:1;
-       unsigned int conflicted_threeway:1;
-       unsigned int direct_to_threeway:1;
-       unsigned int crlf_in_old:1;
-       struct fragment *fragments;
-       char *result;
-       size_t resultsize;
-       char old_oid_prefix[GIT_MAX_HEXSZ + 1];
-       char new_oid_prefix[GIT_MAX_HEXSZ + 1];
-       struct patch *next;
-
-       /* three-way fallback result */
-       struct object_id threeway_stage[3];
-};
-
 static void free_fragment_list(struct fragment *list)
 {
        while (list) {
@@ -465,7 +441,7 @@ static char *squash_slash(char *name)
        return name;
 }
 
-static char *find_name_gnu(struct apply_state *state,
+static char *find_name_gnu(struct strbuf *root,
                           const char *line,
                           int p_value)
 {
@@ -474,7 +450,7 @@ static char *find_name_gnu(struct apply_state *state,
 
        /*
         * Proposed "new-style" GNU patch/diff format; see
-        * http://marc.info/?l=git&m=112927316408690&w=2
+        * https://lore.kernel.org/git/7vll0wvb2a.fsf@assigned-by-dhcp.cox.net/
         */
        if (unquote_c_style(&name, line, NULL)) {
                strbuf_release(&name);
@@ -491,8 +467,8 @@ static char *find_name_gnu(struct apply_state *state,
        }
 
        strbuf_remove(&name, 0, cp - name.buf);
-       if (state->root.len)
-               strbuf_insert(&name, 0, state->root.buf, state->root.len);
+       if (root->len)
+               strbuf_insert(&name, 0, root->buf, root->len);
        return squash_slash(strbuf_detach(&name, NULL));
 }
 
@@ -655,7 +631,7 @@ static size_t diff_timestamp_len(const char *line, size_t len)
        return line + len - end;
 }
 
-static char *find_name_common(struct apply_state *state,
+static char *find_name_common(struct strbuf *root,
                              const char *line,
                              const char *def,
                              int p_value,
@@ -698,30 +674,30 @@ static char *find_name_common(struct apply_state *state,
                        return squash_slash(xstrdup(def));
        }
 
-       if (state->root.len) {
-               char *ret = xstrfmt("%s%.*s", state->root.buf, len, start);
+       if (root->len) {
+               char *ret = xstrfmt("%s%.*s", root->buf, len, start);
                return squash_slash(ret);
        }
 
        return squash_slash(xmemdupz(start, len));
 }
 
-static char *find_name(struct apply_state *state,
+static char *find_name(struct strbuf *root,
                       const char *line,
                       char *def,
                       int p_value,
                       int terminate)
 {
        if (*line == '"') {
-               char *name = find_name_gnu(state, line, p_value);
+               char *name = find_name_gnu(root, line, p_value);
                if (name)
                        return name;
        }
 
-       return find_name_common(state, line, def, p_value, NULL, terminate);
+       return find_name_common(root, line, def, p_value, NULL, terminate);
 }
 
-static char *find_name_traditional(struct apply_state *state,
+static char *find_name_traditional(struct strbuf *root,
                                   const char *line,
                                   char *def,
                                   int p_value)
@@ -730,7 +706,7 @@ static char *find_name_traditional(struct apply_state *state,
        size_t date_len;
 
        if (*line == '"') {
-               char *name = find_name_gnu(state, line, p_value);
+               char *name = find_name_gnu(root, line, p_value);
                if (name)
                        return name;
        }
@@ -738,10 +714,10 @@ static char *find_name_traditional(struct apply_state *state,
        len = strchrnul(line, '\n') - line;
        date_len = diff_timestamp_len(line, len);
        if (!date_len)
-               return find_name_common(state, line, def, p_value, NULL, TERM_TAB);
+               return find_name_common(root, line, def, p_value, NULL, TERM_TAB);
        len -= date_len;
 
-       return find_name_common(state, line, def, p_value, line + len, 0);
+       return find_name_common(root, line, def, p_value, line + len, 0);
 }
 
 /*
@@ -755,7 +731,7 @@ static int guess_p_value(struct apply_state *state, const char *nameline)
 
        if (is_dev_null(nameline))
                return -1;
-       name = find_name_traditional(state, nameline, NULL, 0);
+       name = find_name_traditional(&state->root, nameline, NULL, 0);
        if (!name)
                return -1;
        cp = strchr(name, '/');
@@ -879,17 +855,17 @@ static int parse_traditional_patch(struct apply_state *state,
        if (is_dev_null(first)) {
                patch->is_new = 1;
                patch->is_delete = 0;
-               name = find_name_traditional(state, second, NULL, state->p_value);
+               name = find_name_traditional(&state->root, second, NULL, state->p_value);
                patch->new_name = name;
        } else if (is_dev_null(second)) {
                patch->is_new = 0;
                patch->is_delete = 1;
-               name = find_name_traditional(state, first, NULL, state->p_value);
+               name = find_name_traditional(&state->root, first, NULL, state->p_value);
                patch->old_name = name;
        } else {
                char *first_name;
-               first_name = find_name_traditional(state, first, NULL, state->p_value);
-               name = find_name_traditional(state, second, first_name, state->p_value);
+               first_name = find_name_traditional(&state->root, first, NULL, state->p_value);
+               name = find_name_traditional(&state->root, second, first_name, state->p_value);
                free(first_name);
                if (has_epoch_timestamp(first)) {
                        patch->is_new = 1;
@@ -910,7 +886,7 @@ static int parse_traditional_patch(struct apply_state *state,
        return 0;
 }
 
-static int gitdiff_hdrend(struct apply_state *state,
+static int gitdiff_hdrend(struct gitdiff_data *state,
                          const char *line,
                          struct patch *patch)
 {
@@ -929,14 +905,14 @@ static int gitdiff_hdrend(struct apply_state *state,
 #define DIFF_OLD_NAME 0
 #define DIFF_NEW_NAME 1
 
-static int gitdiff_verify_name(struct apply_state *state,
+static int gitdiff_verify_name(struct gitdiff_data *state,
                               const char *line,
                               int isnull,
                               char **name,
                               int side)
 {
        if (!*name && !isnull) {
-               *name = find_name(state, line, NULL, state->p_value, TERM_TAB);
+               *name = find_name(state->root, line, NULL, state->p_value, TERM_TAB);
                return 0;
        }
 
@@ -945,7 +921,7 @@ static int gitdiff_verify_name(struct apply_state *state,
                if (isnull)
                        return error(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"),
                                     *name, state->linenr);
-               another = find_name(state, line, NULL, state->p_value, TERM_TAB);
+               another = find_name(state->root, line, NULL, state->p_value, TERM_TAB);
                if (!another || strcmp(another, *name)) {
                        free(another);
                        return error((side == DIFF_NEW_NAME) ?
@@ -961,7 +937,7 @@ static int gitdiff_verify_name(struct apply_state *state,
        return 0;
 }
 
-static int gitdiff_oldname(struct apply_state *state,
+static int gitdiff_oldname(struct gitdiff_data *state,
                           const char *line,
                           struct patch *patch)
 {
@@ -970,7 +946,7 @@ static int gitdiff_oldname(struct apply_state *state,
                                   DIFF_OLD_NAME);
 }
 
-static int gitdiff_newname(struct apply_state *state,
+static int gitdiff_newname(struct gitdiff_data *state,
                           const char *line,
                           struct patch *patch)
 {
@@ -988,21 +964,21 @@ static int parse_mode_line(const char *line, int linenr, unsigned int *mode)
        return 0;
 }
 
-static int gitdiff_oldmode(struct apply_state *state,
+static int gitdiff_oldmode(struct gitdiff_data *state,
                           const char *line,
                           struct patch *patch)
 {
        return parse_mode_line(line, state->linenr, &patch->old_mode);
 }
 
-static int gitdiff_newmode(struct apply_state *state,
+static int gitdiff_newmode(struct gitdiff_data *state,
                           const char *line,
                           struct patch *patch)
 {
        return parse_mode_line(line, state->linenr, &patch->new_mode);
 }
 
-static int gitdiff_delete(struct apply_state *state,
+static int gitdiff_delete(struct gitdiff_data *state,
                          const char *line,
                          struct patch *patch)
 {
@@ -1012,7 +988,7 @@ static int gitdiff_delete(struct apply_state *state,
        return gitdiff_oldmode(state, line, patch);
 }
 
-static int gitdiff_newfile(struct apply_state *state,
+static int gitdiff_newfile(struct gitdiff_data *state,
                           const char *line,
                           struct patch *patch)
 {
@@ -1022,47 +998,47 @@ static int gitdiff_newfile(struct apply_state *state,
        return gitdiff_newmode(state, line, patch);
 }
 
-static int gitdiff_copysrc(struct apply_state *state,
+static int gitdiff_copysrc(struct gitdiff_data *state,
                           const char *line,
                           struct patch *patch)
 {
        patch->is_copy = 1;
        free(patch->old_name);
-       patch->old_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
+       patch->old_name = find_name(state->root, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
        return 0;
 }
 
-static int gitdiff_copydst(struct apply_state *state,
+static int gitdiff_copydst(struct gitdiff_data *state,
                           const char *line,
                           struct patch *patch)
 {
        patch->is_copy = 1;
        free(patch->new_name);
-       patch->new_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
+       patch->new_name = find_name(state->root, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
        return 0;
 }
 
-static int gitdiff_renamesrc(struct apply_state *state,
+static int gitdiff_renamesrc(struct gitdiff_data *state,
                             const char *line,
                             struct patch *patch)
 {
        patch->is_rename = 1;
        free(patch->old_name);
-       patch->old_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
+       patch->old_name = find_name(state->root, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
        return 0;
 }
 
-static int gitdiff_renamedst(struct apply_state *state,
+static int gitdiff_renamedst(struct gitdiff_data *state,
                             const char *line,
                             struct patch *patch)
 {
        patch->is_rename = 1;
        free(patch->new_name);
-       patch->new_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
+       patch->new_name = find_name(state->root, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
        return 0;
 }
 
-static int gitdiff_similarity(struct apply_state *state,
+static int gitdiff_similarity(struct gitdiff_data *state,
                              const char *line,
                              struct patch *patch)
 {
@@ -1072,7 +1048,7 @@ static int gitdiff_similarity(struct apply_state *state,
        return 0;
 }
 
-static int gitdiff_dissimilarity(struct apply_state *state,
+static int gitdiff_dissimilarity(struct gitdiff_data *state,
                                 const char *line,
                                 struct patch *patch)
 {
@@ -1082,7 +1058,7 @@ static int gitdiff_dissimilarity(struct apply_state *state,
        return 0;
 }
 
-static int gitdiff_index(struct apply_state *state,
+static int gitdiff_index(struct gitdiff_data *state,
                         const char *line,
                         struct patch *patch)
 {
@@ -1122,7 +1098,7 @@ static int gitdiff_index(struct apply_state *state,
  * This is normal for a diff that doesn't change anything: we'll fall through
  * into the next diff. Tell the parser to break out.
  */
-static int gitdiff_unrecognized(struct apply_state *state,
+static int gitdiff_unrecognized(struct gitdiff_data *state,
                                const char *line,
                                struct patch *patch)
 {
@@ -1133,17 +1109,17 @@ static int gitdiff_unrecognized(struct apply_state *state,
  * Skip p_value leading components from "line"; as we do not accept
  * absolute paths, return NULL in that case.
  */
-static const char *skip_tree_prefix(struct apply_state *state,
+static const char *skip_tree_prefix(int p_value,
                                    const char *line,
                                    int llen)
 {
        int nslash;
        int i;
 
-       if (!state->p_value)
+       if (!p_value)
                return (llen && line[0] == '/') ? NULL : line;
 
-       nslash = state->p_value;
+       nslash = p_value;
        for (i = 0; i < llen; i++) {
                int ch = line[i];
                if (ch == '/' && --nslash <= 0)
@@ -1160,7 +1136,7 @@ static const char *skip_tree_prefix(struct apply_state *state,
  * creation or deletion of an empty file.  In any of these cases,
  * both sides are the same name under a/ and b/ respectively.
  */
-static char *git_header_name(struct apply_state *state,
+static char *git_header_name(int p_value,
                             const char *line,
                             int llen)
 {
@@ -1180,7 +1156,7 @@ static char *git_header_name(struct apply_state *state,
                        goto free_and_fail1;
 
                /* strip the a/b prefix including trailing slash */
-               cp = skip_tree_prefix(state, first.buf, first.len);
+               cp = skip_tree_prefix(p_value, first.buf, first.len);
                if (!cp)
                        goto free_and_fail1;
                strbuf_remove(&first, 0, cp - first.buf);
@@ -1197,7 +1173,7 @@ static char *git_header_name(struct apply_state *state,
                if (*second == '"') {
                        if (unquote_c_style(&sp, second, NULL))
                                goto free_and_fail1;
-                       cp = skip_tree_prefix(state, sp.buf, sp.len);
+                       cp = skip_tree_prefix(p_value, sp.buf, sp.len);
                        if (!cp)
                                goto free_and_fail1;
                        /* They must match, otherwise ignore */
@@ -1208,7 +1184,7 @@ static char *git_header_name(struct apply_state *state,
                }
 
                /* unquoted second */
-               cp = skip_tree_prefix(state, second, line + llen - second);
+               cp = skip_tree_prefix(p_value, second, line + llen - second);
                if (!cp)
                        goto free_and_fail1;
                if (line + llen - cp != first.len ||
@@ -1223,7 +1199,7 @@ static char *git_header_name(struct apply_state *state,
        }
 
        /* unquoted first name */
-       name = skip_tree_prefix(state, line, llen);
+       name = skip_tree_prefix(p_value, line, llen);
        if (!name)
                return NULL;
 
@@ -1239,7 +1215,7 @@ static char *git_header_name(struct apply_state *state,
                        if (unquote_c_style(&sp, second, NULL))
                                goto free_and_fail2;
 
-                       np = skip_tree_prefix(state, sp.buf, sp.len);
+                       np = skip_tree_prefix(p_value, sp.buf, sp.len);
                        if (!np)
                                goto free_and_fail2;
 
@@ -1283,7 +1259,7 @@ static char *git_header_name(struct apply_state *state,
                         */
                        if (!name[len + 1])
                                return NULL; /* no postimage name */
-                       second = skip_tree_prefix(state, name + len + 1,
+                       second = skip_tree_prefix(p_value, name + len + 1,
                                                  line_len - (len + 1));
                        if (!second)
                                return NULL;
@@ -1298,26 +1274,28 @@ static char *git_header_name(struct apply_state *state,
        }
 }
 
-static int check_header_line(struct apply_state *state, struct patch *patch)
+static int check_header_line(int linenr, struct patch *patch)
 {
        int extensions = (patch->is_delete == 1) + (patch->is_new == 1) +
                         (patch->is_rename == 1) + (patch->is_copy == 1);
        if (extensions > 1)
                return error(_("inconsistent header lines %d and %d"),
-                            patch->extension_linenr, state->linenr);
+                            patch->extension_linenr, linenr);
        if (extensions && !patch->extension_linenr)
-               patch->extension_linenr = state->linenr;
+               patch->extension_linenr = linenr;
        return 0;
 }
 
-/* Verify that we recognize the lines following a git header */
-static int parse_git_header(struct apply_state *state,
-                           const char *line,
-                           int len,
-                           unsigned int size,
-                           struct patch *patch)
+int parse_git_diff_header(struct strbuf *root,
+                         int *linenr,
+                         int p_value,
+                         const char *line,
+                         int len,
+                         unsigned int size,
+                         struct patch *patch)
 {
        unsigned long offset;
+       struct gitdiff_data parse_hdr_state;
 
        /* A git diff has explicit new/delete information, so we don't guess */
        patch->is_new = 0;
@@ -1329,20 +1307,24 @@ static int parse_git_header(struct apply_state *state,
         * or removing or adding empty files), so we get
         * the default name from the header.
         */
-       patch->def_name = git_header_name(state, line, len);
-       if (patch->def_name && state->root.len) {
-               char *s = xstrfmt("%s%s", state->root.buf, patch->def_name);
+       patch->def_name = git_header_name(p_value, line, len);
+       if (patch->def_name && root->len) {
+               char *s = xstrfmt("%s%s", root->buf, patch->def_name);
                free(patch->def_name);
                patch->def_name = s;
        }
 
        line += len;
        size -= len;
-       state->linenr++;
-       for (offset = len ; size > 0 ; offset += len, size -= len, line += len, state->linenr++) {
+       (*linenr)++;
+       parse_hdr_state.root = root;
+       parse_hdr_state.linenr = *linenr;
+       parse_hdr_state.p_value = p_value;
+
+       for (offset = len ; size > 0 ; offset += len, size -= len, line += len, (*linenr)++) {
                static const struct opentry {
                        const char *str;
-                       int (*fn)(struct apply_state *, const char *, struct patch *);
+                       int (*fn)(struct gitdiff_data *, const char *, struct patch *);
                } optable[] = {
                        { "@@ -", gitdiff_hdrend },
                        { "--- ", gitdiff_oldname },
@@ -1373,17 +1355,38 @@ static int parse_git_header(struct apply_state *state,
                        int res;
                        if (len < oplen || memcmp(p->str, line, oplen))
                                continue;
-                       res = p->fn(state, line + oplen, patch);
+                       res = p->fn(&parse_hdr_state, line + oplen, patch);
                        if (res < 0)
                                return -1;
-                       if (check_header_line(state, patch))
+                       if (check_header_line(*linenr, patch))
                                return -1;
                        if (res > 0)
-                               return offset;
+                               goto done;
                        break;
                }
        }
 
+done:
+       if (!patch->old_name && !patch->new_name) {
+               if (!patch->def_name) {
+                       error(Q_("git diff header lacks filename information when removing "
+                                "%d leading pathname component (line %d)",
+                                "git diff header lacks filename information when removing "
+                                "%d leading pathname components (line %d)",
+                                parse_hdr_state.p_value),
+                             parse_hdr_state.p_value, *linenr);
+                       return -128;
+               }
+               patch->old_name = xstrdup(patch->def_name);
+               patch->new_name = xstrdup(patch->def_name);
+       }
+       if ((!patch->new_name && !patch->is_delete) ||
+           (!patch->old_name && !patch->is_new)) {
+               error(_("git diff header lacks filename information "
+                       "(line %d)"), *linenr);
+               return -128;
+       }
+       patch->is_toplevel_relative = 1;
        return offset;
 }
 
@@ -1557,31 +1560,13 @@ static int find_header(struct apply_state *state,
                 * or mode change, so we handle that specially
                 */
                if (!memcmp("diff --git ", line, 11)) {
-                       int git_hdr_len = parse_git_header(state, line, len, size, patch);
+                       int git_hdr_len = parse_git_diff_header(&state->root, &state->linenr,
+                                                               state->p_value, line, len,
+                                                               size, patch);
                        if (git_hdr_len < 0)
                                return -128;
                        if (git_hdr_len <= len)
                                continue;
-                       if (!patch->old_name && !patch->new_name) {
-                               if (!patch->def_name) {
-                                       error(Q_("git diff header lacks filename information when removing "
-                                                       "%d leading pathname component (line %d)",
-                                                       "git diff header lacks filename information when removing "
-                                                       "%d leading pathname components (line %d)",
-                                                       state->p_value),
-                                                    state->p_value, state->linenr);
-                                       return -128;
-                               }
-                               patch->old_name = xstrdup(patch->def_name);
-                               patch->new_name = xstrdup(patch->def_name);
-                       }
-                       if ((!patch->new_name && !patch->is_delete) ||
-                           (!patch->old_name && !patch->is_new)) {
-                               error(_("git diff header lacks filename information "
-                                            "(line %d)"), state->linenr);
-                               return -128;
-                       }
-                       patch->is_toplevel_relative = 1;
                        *hdrsize = git_hdr_len;
                        return offset;
                }
@@ -2676,6 +2661,16 @@ static int find_pos(struct apply_state *state,
        unsigned long backwards, forwards, current;
        int backwards_lno, forwards_lno, current_lno;
 
+       /*
+        * When running with --allow-overlap, it is possible that a hunk is
+        * seen that pretends to start at the beginning (but no longer does),
+        * and that *still* needs to match the end. So trust `match_end` more
+        * than `match_beginning`.
+        */
+       if (state->allow_overlap && match_beginning && match_end &&
+           img->nr - preimage->nr != 0)
+               match_beginning = 0;
+
        /*
         * If match_beginning or match_end is specified, there is no
         * point starting from a wrong line that will never match and
@@ -3162,7 +3157,8 @@ static int apply_binary(struct apply_state *state,
                 * See if the old one matches what the patch
                 * applies to.
                 */
-               hash_object_file(img->buf, img->len, blob_type, &oid);
+               hash_object_file(the_hash_algo, img->buf, img->len, blob_type,
+                                &oid);
                if (strcmp(oid_to_hex(&oid), patch->old_oid_prefix))
                        return error(_("the patch applies to '%s' (%s), "
                                       "which does not match the "
@@ -3207,7 +3203,8 @@ static int apply_binary(struct apply_state *state,
                                     name);
 
                /* verify that the result matches */
-               hash_object_file(img->buf, img->len, blob_type, &oid);
+               hash_object_file(the_hash_algo, img->buf, img->len, blob_type,
+                                &oid);
                if (strcmp(oid_to_hex(&oid), patch->new_oid_prefix))
                        return error(_("binary patch to '%s' creates incorrect result (expecting %s, got %s)"),
                                name, patch->new_oid_prefix, oid_to_hex(&oid));
@@ -4198,8 +4195,8 @@ static void show_rename_copy(struct patch *p)
                old_name = slash_old + 1;
                new_name = slash_new + 1;
        }
-       /* p->old_name thru old_name is the common prefix, and old_name and new_name
-        * through the end of names are renames
+       /* p->old_name through old_name is the common prefix, and old_name and
+        * new_name through the end of names are renames
         */
        if (old_name != p->old_name)
                printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy,
@@ -4306,7 +4303,7 @@ static int add_index_file(struct apply_state *state,
                                                     "created file '%s'"),
                                                   path);
                        }
-                       fill_stat_cache_info(ce, &st);
+                       fill_stat_cache_info(state->repo->index, ce, &st);
                }
                if (write_object_file(buf, size, blob_type, &ce->oid) < 0) {
                        discard_cache_entry(ce);
@@ -4352,7 +4349,7 @@ static int try_create_file(struct apply_state *state, const char *path,
        if (fd < 0)
                return 1;
 
-       if (convert_to_working_tree(state->repo->index, path, buf, size, &nbuf)) {
+       if (convert_to_working_tree(state->repo->index, path, buf, size, &nbuf, NULL)) {
                size = nbuf.len;
                buf  = nbuf.buf;
        }
@@ -4659,6 +4656,7 @@ static int apply_patch(struct apply_state *state,
        struct patch *list = NULL, **listp = &list;
        int skipped_patch = 0;
        int res = 0;
+       int flush_attributes = 0;
 
        state->patch_input_file = filename;
        if (read_patch_file(&buf, fd) < 0)
@@ -4686,6 +4684,14 @@ static int apply_patch(struct apply_state *state,
                        patch_stats(state, patch);
                        *listp = patch;
                        listp = &patch->next;
+
+                       if ((patch->new_name &&
+                            ends_with_path_components(patch->new_name,
+                                                      GITATTRIBUTES_FILE)) ||
+                           (patch->old_name &&
+                            ends_with_path_components(patch->old_name,
+                                                      GITATTRIBUTES_FILE)))
+                               flush_attributes = 1;
                }
                else {
                        if (state->apply_verbosity > verbosity_normal)
@@ -4762,6 +4768,8 @@ static int apply_patch(struct apply_state *state,
        if (state->summary && state->apply_verbosity > verbosity_silent)
                summary_patch_list(list);
 
+       if (flush_attributes)
+               reset_parsed_attributes();
 end:
        free_patch_list(list);
        strbuf_release(&buf);