Skip to content

Commit 84f3de2

Browse files
pyokagangitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent d23a511 commit 84f3de2

File tree

1 file changed

+150
-4
lines changed

1 file changed

+150
-4
lines changed

builtin/am.c

Lines changed: 150 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#include "unpack-trees.h"
2020
#include "branch.h"
2121
#include "sequencer.h"
22+
#include "revision.h"
23+
#include "merge-recursive.h"
2224

2325
/**
2426
* Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -83,6 +85,7 @@ struct am_state {
8385
int prec;
8486

8587
/* various operating modes and command line options */
88+
int threeway;
8689
int quiet;
8790
int signoff;
8891
const char *resolvemsg;
@@ -352,6 +355,9 @@ static void am_load(struct am_state *state)
352355

353356
read_commit_msg(state);
354357

358+
read_state_file(&sb, state, "threeway", 1);
359+
state->threeway = !strcmp(sb.buf, "t");
360+
355361
read_state_file(&sb, state, "quiet", 1);
356362
state->quiet = !strcmp(sb.buf, "t");
357363

@@ -536,6 +542,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
536542
die(_("Failed to split patches."));
537543
}
538544

545+
write_file(am_path(state, "threeway"), 1, state->threeway ? "t" : "f");
546+
539547
write_file(am_path(state, "quiet"), 1, state->quiet ? "t" : "f");
540548

541549
write_file(am_path(state, "sign"), 1, state->signoff ? "t" : "f");
@@ -766,25 +774,141 @@ static int parse_mail(struct am_state *state, const char *mail)
766774
}
767775

768776
/**
769-
* Applies current patch with git-apply. Returns 0 on success, -1 otherwise.
777+
* Applies current patch with git-apply. Returns 0 on success, -1 otherwise. If
778+
* `index_file` is not NULL, the patch will be applied to that index.
770779
*/
771-
static int run_apply(const struct am_state *state)
780+
static int run_apply(const struct am_state *state, const char *index_file)
772781
{
773782
struct child_process cp = CHILD_PROCESS_INIT;
774783

775784
cp.git_cmd = 1;
776785

786+
if (index_file)
787+
argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", index_file);
788+
789+
/*
790+
* If we are allowed to fall back on 3-way merge, don't give false
791+
* errors during the initial attempt.
792+
*/
793+
if (state->threeway && !index_file) {
794+
cp.no_stdout = 1;
795+
cp.no_stderr = 1;
796+
}
797+
777798
argv_array_push(&cp.args, "apply");
778-
argv_array_push(&cp.args, "--index");
799+
800+
if (index_file)
801+
argv_array_push(&cp.args, "--cached");
802+
else
803+
argv_array_push(&cp.args, "--index");
804+
779805
argv_array_push(&cp.args, am_path(state, "patch"));
780806

781807
if (run_command(&cp))
782808
return -1;
783809

784810
/* Reload index as git-apply will have modified it. */
811+
discard_cache();
812+
read_cache_from(index_file ? index_file : get_index_file());
813+
814+
return 0;
815+
}
816+
817+
/**
818+
* Builds an index that contains just the blobs needed for a 3way merge.
819+
*/
820+
static int build_fake_ancestor(const struct am_state *state, const char *index_file)
821+
{
822+
struct child_process cp = CHILD_PROCESS_INIT;
823+
824+
cp.git_cmd = 1;
825+
argv_array_push(&cp.args, "apply");
826+
argv_array_pushf(&cp.args, "--build-fake-ancestor=%s", index_file);
827+
argv_array_push(&cp.args, am_path(state, "patch"));
828+
829+
if (run_command(&cp))
830+
return -1;
831+
832+
return 0;
833+
}
834+
835+
/**
836+
* Attempt a threeway merge, using index_path as the temporary index.
837+
*/
838+
static int fall_back_threeway(const struct am_state *state, const char *index_path)
839+
{
840+
unsigned char orig_tree[GIT_SHA1_RAWSZ], his_tree[GIT_SHA1_RAWSZ],
841+
our_tree[GIT_SHA1_RAWSZ];
842+
const unsigned char *bases[1] = {orig_tree};
843+
struct merge_options o;
844+
struct commit *result;
845+
char *his_tree_name;
846+
847+
if (get_sha1("HEAD", our_tree) < 0)
848+
hashcpy(our_tree, EMPTY_TREE_SHA1_BIN);
849+
850+
if (build_fake_ancestor(state, index_path))
851+
return error("could not build fake ancestor");
852+
853+
discard_cache();
854+
read_cache_from(index_path);
855+
856+
if (write_index_as_tree(orig_tree, &the_index, index_path, 0, NULL))
857+
return error(_("Repository lacks necessary blobs to fall back on 3-way merge."));
858+
859+
say(state, stdout, _("Using index info to reconstruct a base tree..."));
860+
861+
if (!state->quiet) {
862+
/*
863+
* List paths that needed 3-way fallback, so that the user can
864+
* review them with extra care to spot mismerges.
865+
*/
866+
struct rev_info rev_info;
867+
const char *diff_filter_str = "--diff-filter=AM";
868+
869+
init_revisions(&rev_info, NULL);
870+
rev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS;
871+
diff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1);
872+
add_pending_sha1(&rev_info, "HEAD", our_tree, 0);
873+
diff_setup_done(&rev_info.diffopt);
874+
run_diff_index(&rev_info, 1);
875+
}
876+
877+
if (run_apply(state, index_path))
878+
return error(_("Did you hand edit your patch?\n"
879+
"It does not apply to blobs recorded in its index."));
880+
881+
if (write_index_as_tree(his_tree, &the_index, index_path, 0, NULL))
882+
return error("could not write tree");
883+
884+
say(state, stdout, _("Falling back to patching base and 3-way merge..."));
885+
785886
discard_cache();
786887
read_cache();
787888

889+
/*
890+
* This is not so wrong. Depending on which base we picked, orig_tree
891+
* may be wildly different from ours, but his_tree has the same set of
892+
* wildly different changes in parts the patch did not touch, so
893+
* recursive ends up canceling them, saying that we reverted all those
894+
* changes.
895+
*/
896+
897+
init_merge_options(&o);
898+
899+
o.branch1 = "HEAD";
900+
his_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
901+
o.branch2 = his_tree_name;
902+
903+
if (state->quiet)
904+
o.verbosity = 0;
905+
906+
if (merge_recursive_generic(&o, our_tree, his_tree, 1, bases, &result)) {
907+
free(his_tree_name);
908+
return error(_("Failed to merge in the changes."));
909+
}
910+
911+
free(his_tree_name);
788912
return 0;
789913
}
790914

@@ -872,6 +996,7 @@ static void am_run(struct am_state *state, int resume)
872996

873997
while (state->cur <= state->last) {
874998
const char *mail = am_path(state, msgnum(state));
999+
int apply_status;
8751000

8761001
if (!file_exists(mail))
8771002
goto next;
@@ -889,7 +1014,26 @@ static void am_run(struct am_state *state, int resume)
8891014

8901015
say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
8911016

892-
if (run_apply(state) < 0) {
1017+
apply_status = run_apply(state, NULL);
1018+
1019+
if (apply_status && state->threeway) {
1020+
struct strbuf sb = STRBUF_INIT;
1021+
1022+
strbuf_addstr(&sb, am_path(state, "patch-merge-index"));
1023+
apply_status = fall_back_threeway(state, sb.buf);
1024+
strbuf_release(&sb);
1025+
1026+
/*
1027+
* Applying the patch to an earlier tree and merging
1028+
* the result may have produced the same tree as ours.
1029+
*/
1030+
if (!apply_status && !index_has_changes(NULL)) {
1031+
say(state, stdout, _("No changes -- Patch already applied."));
1032+
goto next;
1033+
}
1034+
}
1035+
1036+
if (apply_status) {
8931037
int advice_amworkdir = 1;
8941038

8951039
printf_ln(_("Patch failed at %s %.*s"), msgnum(state),
@@ -1159,6 +1303,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)
11591303
};
11601304

11611305
struct option options[] = {
1306+
OPT_BOOL('3', "3way", &state.threeway,
1307+
N_("allow fall back on 3way merging if needed")),
11621308
OPT__QUIET(&state.quiet, N_("be quiet")),
11631309
OPT_BOOL('s', "signoff", &state.signoff,
11641310
N_("add a Signed-off-by line to the commit message")),

0 commit comments

Comments
 (0)