]> git.ipfire.org Git - thirdparty/git.git/blobdiff - apply.c
t4034: abstract away SHA-1-specific constants
[thirdparty/git.git] / apply.c
diff --git a/apply.c b/apply.c
index 4992eca416c9c2b5d21de42460cafa839df25f4e..57a61f2881bed383862044413210b42abca18434 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);
@@ -201,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) {
@@ -469,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)
 {
@@ -478,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://public-inbox.org/git/7vll0wvb2a.fsf@assigned-by-dhcp.cox.net/
         */
        if (unquote_c_style(&name, line, NULL)) {
                strbuf_release(&name);
@@ -495,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));
 }
 
@@ -659,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,
@@ -702,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)
@@ -734,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;
        }
@@ -742,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);
 }
 
 /*
@@ -759,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, '/');
@@ -883,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;
@@ -914,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)
 {
@@ -933,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;
        }
 
@@ -949,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) ?
@@ -965,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)
 {
@@ -974,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)
 {
@@ -992,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)
 {
@@ -1016,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)
 {
@@ -1026,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)
 {
@@ -1076,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)
 {
@@ -1086,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)
 {
@@ -1126,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)
 {
@@ -1137,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)
@@ -1164,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)
 {
@@ -1184,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);
@@ -1201,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 */
@@ -1212,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 ||
@@ -1227,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;
 
@@ -1243,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;
 
@@ -1287,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;
@@ -1302,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;
@@ -1333,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 },
@@ -1377,10 +1355,10 @@ 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;
@@ -1561,7 +1539,9 @@ 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)
@@ -4663,6 +4643,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)
@@ -4690,6 +4671,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)
@@ -4766,6 +4755,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);