builtin-am: implement --3way
Sinced1c5f2a
(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. Since5d86861
(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:
parent
d23a5117f8
commit
84f3de28ba
154
builtin/am.c
154
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,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")),
|
||||
|
Loading…
Reference in New Issue
Block a user