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 <pyokagan@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Paul Tan 2015-08-04 21:51:41 +08:00 committed by Junio C Hamano
parent d23a5117f8
commit 84f3de28ba

View File

@ -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,25 +774,141 @@ 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))
return -1;
/* 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")),