diff options
Diffstat (limited to 'apply.c')
-rw-r--r-- | apply.c | 262 |
1 files changed, 133 insertions, 129 deletions
@@ -22,11 +22,17 @@ #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) @@ -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://lore.kernel.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,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; } @@ -1561,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; } @@ -2681,6 +2662,16 @@ static int find_pos(struct apply_state *state, 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 * wander around and wait for a match at the specified end. @@ -3166,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 " @@ -3211,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)); @@ -4202,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, @@ -4663,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) @@ -4690,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) @@ -4766,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); |