From 84f3de28baf6fc6a91b95fad9a4c5468dec3ea8e Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:41 +0800 Subject: [PATCH] builtin-am: implement --3way Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am.sh supported the --3way option, and if set, would attempt to do a 3-way merge if the initial patch application fails. Since 5d86861 (am -3: list the paths that needed 3-way fallback, 2012-03-28), in a 3-way merge git-am.sh would list the paths that needed 3-way fallback, so that the user can review them more carefully to spot mismerges. Re-implement the above in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 150 insertions(+), 4 deletions(-) diff --git a/builtin/am.c b/builtin/am.c index 12952cf11c..a5d5e8c665 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -19,6 +19,8 @@ #include "unpack-trees.h" #include "branch.h" #include "sequencer.h" +#include "revision.h" +#include "merge-recursive.h" /** * Returns 1 if the file is empty or does not exist, 0 otherwise. @@ -83,6 +85,7 @@ struct am_state { int prec; /* various operating modes and command line options */ + int threeway; int quiet; int signoff; const char *resolvemsg; @@ -352,6 +355,9 @@ static void am_load(struct am_state *state) read_commit_msg(state); + read_state_file(&sb, state, "threeway", 1); + state->threeway = !strcmp(sb.buf, "t"); + read_state_file(&sb, state, "quiet", 1); state->quiet = !strcmp(sb.buf, "t"); @@ -536,6 +542,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, die(_("Failed to split patches.")); } + write_file(am_path(state, "threeway"), 1, state->threeway ? "t" : "f"); + write_file(am_path(state, "quiet"), 1, state->quiet ? "t" : "f"); write_file(am_path(state, "sign"), 1, state->signoff ? "t" : "f"); @@ -766,16 +774,34 @@ finish: } /** - * Applies current patch with git-apply. Returns 0 on success, -1 otherwise. + * Applies current patch with git-apply. Returns 0 on success, -1 otherwise. If + * `index_file` is not NULL, the patch will be applied to that index. */ -static int run_apply(const struct am_state *state) +static int run_apply(const struct am_state *state, const char *index_file) { struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; + if (index_file) + argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", index_file); + + /* + * If we are allowed to fall back on 3-way merge, don't give false + * errors during the initial attempt. + */ + if (state->threeway && !index_file) { + cp.no_stdout = 1; + cp.no_stderr = 1; + } + argv_array_push(&cp.args, "apply"); - argv_array_push(&cp.args, "--index"); + + if (index_file) + argv_array_push(&cp.args, "--cached"); + else + argv_array_push(&cp.args, "--index"); + argv_array_push(&cp.args, am_path(state, "patch")); if (run_command(&cp)) @@ -783,8 +809,106 @@ static int run_apply(const struct am_state *state) /* Reload index as git-apply will have modified it. */ discard_cache(); + read_cache_from(index_file ? index_file : get_index_file()); + + return 0; +} + +/** + * Builds an index that contains just the blobs needed for a 3way merge. + */ +static int build_fake_ancestor(const struct am_state *state, const char *index_file) +{ + struct child_process cp = CHILD_PROCESS_INIT; + + cp.git_cmd = 1; + argv_array_push(&cp.args, "apply"); + argv_array_pushf(&cp.args, "--build-fake-ancestor=%s", index_file); + argv_array_push(&cp.args, am_path(state, "patch")); + + if (run_command(&cp)) + return -1; + + return 0; +} + +/** + * Attempt a threeway merge, using index_path as the temporary index. + */ +static int fall_back_threeway(const struct am_state *state, const char *index_path) +{ + unsigned char orig_tree[GIT_SHA1_RAWSZ], his_tree[GIT_SHA1_RAWSZ], + our_tree[GIT_SHA1_RAWSZ]; + const unsigned char *bases[1] = {orig_tree}; + struct merge_options o; + struct commit *result; + char *his_tree_name; + + if (get_sha1("HEAD", our_tree) < 0) + hashcpy(our_tree, EMPTY_TREE_SHA1_BIN); + + if (build_fake_ancestor(state, index_path)) + return error("could not build fake ancestor"); + + discard_cache(); + read_cache_from(index_path); + + if (write_index_as_tree(orig_tree, &the_index, index_path, 0, NULL)) + return error(_("Repository lacks necessary blobs to fall back on 3-way merge.")); + + say(state, stdout, _("Using index info to reconstruct a base tree...")); + + if (!state->quiet) { + /* + * List paths that needed 3-way fallback, so that the user can + * review them with extra care to spot mismerges. + */ + struct rev_info rev_info; + const char *diff_filter_str = "--diff-filter=AM"; + + init_revisions(&rev_info, NULL); + rev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS; + diff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1); + add_pending_sha1(&rev_info, "HEAD", our_tree, 0); + diff_setup_done(&rev_info.diffopt); + run_diff_index(&rev_info, 1); + } + + if (run_apply(state, index_path)) + return error(_("Did you hand edit your patch?\n" + "It does not apply to blobs recorded in its index.")); + + if (write_index_as_tree(his_tree, &the_index, index_path, 0, NULL)) + return error("could not write tree"); + + say(state, stdout, _("Falling back to patching base and 3-way merge...")); + + discard_cache(); read_cache(); + /* + * This is not so wrong. Depending on which base we picked, orig_tree + * may be wildly different from ours, but his_tree has the same set of + * wildly different changes in parts the patch did not touch, so + * recursive ends up canceling them, saying that we reverted all those + * changes. + */ + + init_merge_options(&o); + + o.branch1 = "HEAD"; + his_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg); + o.branch2 = his_tree_name; + + if (state->quiet) + o.verbosity = 0; + + if (merge_recursive_generic(&o, our_tree, his_tree, 1, bases, &result)) { + free(his_tree_name); + return error(_("Failed to merge in the changes.")); + } + + free(his_tree_name); return 0; } @@ -872,6 +996,7 @@ static void am_run(struct am_state *state, int resume) while (state->cur <= state->last) { const char *mail = am_path(state, msgnum(state)); + int apply_status; if (!file_exists(mail)) goto next; @@ -889,7 +1014,26 @@ static void am_run(struct am_state *state, int resume) say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg); - if (run_apply(state) < 0) { + apply_status = run_apply(state, NULL); + + if (apply_status && state->threeway) { + struct strbuf sb = STRBUF_INIT; + + strbuf_addstr(&sb, am_path(state, "patch-merge-index")); + apply_status = fall_back_threeway(state, sb.buf); + strbuf_release(&sb); + + /* + * Applying the patch to an earlier tree and merging + * the result may have produced the same tree as ours. + */ + if (!apply_status && !index_has_changes(NULL)) { + say(state, stdout, _("No changes -- Patch already applied.")); + goto next; + } + } + + if (apply_status) { int advice_amworkdir = 1; printf_ln(_("Patch failed at %s %.*s"), msgnum(state), @@ -1159,6 +1303,8 @@ int cmd_am(int argc, const char **argv, const char *prefix) }; struct option options[] = { + OPT_BOOL('3', "3way", &state.threeway, + N_("allow fall back on 3way merging if needed")), OPT__QUIET(&state.quiet, N_("be quiet")), OPT_BOOL('s', "signoff", &state.signoff, N_("add a Signed-off-by line to the commit message")), -- 2.11.4.GIT