All of lore.kernel.org
 help / color / mirror / Atom feed
From: Paul Tan <pyokagan@gmail.com>
To: git@vger.kernel.org
Cc: Stefan Beller <sbeller@google.com>,
	Johannes Schindelin <johannes.schindelin@gmx.de>,
	Paul Tan <pyokagan@gmail.com>
Subject: [PATCH/WIP v2 19/19] am: implement 3-way merge
Date: Thu, 11 Jun 2015 18:22:05 +0800	[thread overview]
Message-ID: <1434018125-31804-20-git-send-email-pyokagan@gmail.com> (raw)
In-Reply-To: <1434018125-31804-1-git-send-email-pyokagan@gmail.com>

Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am
supported the --3way option, and if set, would attempt to do a 3-way
merge if the initial patch application fails. Re-implement this feature
through the fall_back_threeway() function.

Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 builtin/am.c | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 129 insertions(+), 4 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index 71fda16..8566d22 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -19,6 +19,7 @@
 #include "unpack-trees.h"
 #include "branch.h"
 #include "sequencer.h"
+#include "merge-recursive.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -76,6 +77,8 @@ struct am_state {
 	const char *resolvemsg;
 
 	int sign;
+
+	int threeway;
 };
 
 /**
@@ -659,16 +662,32 @@ static void NORETURN die_user_resolve(const struct am_state *state)
 }
 
 /*
- * 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))
@@ -676,8 +695,92 @@ 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 a 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[20], his_tree[20], our_tree[20];
+	const unsigned char *bases[1] = {orig_tree};
+	struct merge_options o;
+	struct commit *result;
+
+	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) {
+		struct child_process cp = CHILD_PROCESS_INIT;
+		cp.git_cmd = 1;
+		argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", index_path);
+		argv_array_pushl(&cp.args, "diff-index", "--cached",
+				"--diff-filter=AM", "--name-status", "HEAD", NULL);
+		run_command(&cp);
+	}
+
+	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";
+	o.branch2 = firstline(state->msg.buf);
+	if (state->quiet)
+		o.verbosity = 0;
+
+	if (merge_recursive_generic(&o, our_tree, his_tree, 1, bases, &result))
+		return error(_("Failed to merge in the changes."));
+
 	return 0;
 }
 
@@ -743,6 +846,7 @@ static void am_run(struct am_state *state)
 
 	while (state->cur <= state->last) {
 		const char *patch = am_path(state, msgnum(state));
+		int apply_status;
 
 		if (!file_exists(patch))
 			goto next;
@@ -755,7 +859,26 @@ static void am_run(struct am_state *state)
 
 		say(state, stdout, _("Applying: %s"), firstline(state->msg.buf));
 
-		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 value;
 
 			printf_ln(_("Patch failed at %s %s"), msgnum(state),
@@ -1007,6 +1130,8 @@ static const char * const am_usage[] = {
 };
 
 static struct option am_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.sign,
 		N_("add a Signed-off-by line to the commit message")),
-- 
2.1.4

      parent reply	other threads:[~2015-06-11 10:23 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-06-11 10:21 [PATCH/WIP v2 00/19] Make git-am a builtin Paul Tan
2015-06-11 10:21 ` [PATCH/WIP v2 01/19] wrapper: implement xopen() Paul Tan
2015-06-11 10:21 ` [PATCH/WIP v2 02/19] wrapper: implement xfopen() Paul Tan
2015-06-11 10:21 ` [PATCH/WIP v2 03/19] am: implement skeletal builtin am Paul Tan
2015-06-14 22:08   ` Junio C Hamano
2015-06-15  9:49     ` Paul Tan
2015-06-15 17:14       ` Junio C Hamano
2015-06-15 17:20         ` Paul Tan
2015-06-15 17:54           ` Junio C Hamano
2015-06-18  8:44             ` Paul Tan
2015-06-11 10:21 ` [PATCH/WIP v2 04/19] am: implement patch queue mechanism Paul Tan
2015-06-11 17:39   ` Stefan Beller
2015-06-15 10:46     ` Paul Tan
2015-06-11 10:21 ` [PATCH/WIP v2 05/19] am: split out mbox/maildir patches with git-mailsplit Paul Tan
2015-06-11 17:45   ` Stefan Beller
2015-06-15 10:08     ` Paul Tan
2015-06-11 10:21 ` [PATCH/WIP v2 06/19] am: detect mbox patches Paul Tan
2015-06-11 10:21 ` [PATCH/WIP v2 07/19] am: extract patch, message and authorship with git-mailinfo Paul Tan
2015-06-14 22:10   ` Junio C Hamano
2015-06-11 10:21 ` [PATCH/WIP v2 08/19] am: apply patch with git-apply Paul Tan
2015-06-11 10:21 ` [PATCH/WIP v2 09/19] am: commit applied patch Paul Tan
2015-06-11 10:21 ` [PATCH/WIP v2 10/19] am: refresh the index at start Paul Tan
2015-06-11 10:21 ` [PATCH/WIP v2 11/19] am: refuse to apply patches if index is dirty Paul Tan
2015-06-11 10:21 ` [PATCH/WIP v2 12/19] am: implement --resolved/--continue Paul Tan
2015-06-11 10:21 ` [PATCH/WIP v2 13/19] am: implement --skip Paul Tan
2015-06-11 10:22 ` [PATCH/WIP v2 14/19] am: implement --abort Paul Tan
2015-06-11 10:22 ` [PATCH/WIP v2 15/19] am: implement quiet option Paul Tan
2015-06-11 10:22 ` [PATCH/WIP v2 16/19] am: exit with user friendly message on patch failure Paul Tan
2015-06-11 10:22 ` [PATCH/WIP v2 17/19] am: implement am --signoff Paul Tan
2015-06-11 10:22 ` [PATCH/WIP v2 18/19] cache-tree: introduce write_index_as_tree() Paul Tan
2015-06-11 10:22 ` Paul Tan [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1434018125-31804-20-git-send-email-pyokagan@gmail.com \
    --to=pyokagan@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=johannes.schindelin@gmx.de \
    --cc=sbeller@google.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.