git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 00/15] Build in merge
@ 2008-06-27 16:21 Miklos Vajna
  2008-06-27 16:21 ` [PATCH 01/15] Move split_cmdline() to alias.c Miklos Vajna
  0 siblings, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-06-27 16:21 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

Hello,

Dscho gave me a detailed review on builtin-merge, so I'm sending an
updated series.

It's based on 49646c5 in git.git, so hopefully it includes all the work
Junio did recently in 'pu'. (PARSE_OPT_ARGV0_IS_AN_OPTION removal, etc.)

Changes:

* "git-fmt-merge-msg: make it usable from other builtins"
  - Small cleanup.

* "Build in merge"
  - When a custom message was given for a merge, the result was not
    identical to the one git-merge.sh had. I added a testcase for this
    and fixed up builtin-merge to pass the test.
  - Memleak fix in restore_state()
  - Cleanup in finish_up_to_date() and squash_message()
  - run_hook() now first checks if the executable bit is set and returns
    immediately if not.
  - Make merge_name() a bit more readable, like *(ptr+1) -> ptr[1]. Also
    fixed a missing initialization in this function.
  - split_merge_strategies(): while (1) -> for (;;)
  - Simplified path_list_append_strategy() by removing unnecessary
    parameters.
  - cmd_merge(): Added more comments to make the code more readable,
    fixed a memory leak, optimized the case when --no-commit is used.
  - merge_one_remote(): Fixed a bug which for example caused a segfault
    when building with -fno-inline.
  - Fixed up color handling when showing the diff after a merge, as
    noticed by Olivier Marin.
  - Fixed up the "Updating foo..bar" message which was like "Updating
    bar..bar", as pointed out by Olivier Marin.

* Dscho's two patches: These introduces strbuf_initf() which can be used
  instead of strbuf_init() and strbuf_addf(). Modified builtin-merge.c
  to use strbuf_initf() where possible.

* Extended t7601-merge-pull-config.sh to make sure git-merge picks up
  the best strategy when no strategy can handle the merge without
  conflicts.

The "interdiff" is available via git diff 49646c5..d1c62b2 in
git://repo.or.cz/git/vmiklos.git.

I'm sending the whole series to avoid complexity, but in fact I only
changed the following patches:

* "git-fmt-merge-msg: make it usable from other builtins"
* "Add new test case to ensure git-merge prepends the custom merge
  message"
* "Build in merge"

Johannes Schindelin (2):
  Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf()
  strbuf_vaddf(): support %*s, too

Junio C Hamano (2):
  Introduce get_merge_bases_many()
  Introduce reduce_heads()

Miklos Vajna (11):
  Move split_cmdline() to alias.c
  Move commit_list_count() to commit.c
  Move parse-options's skip_prefix() to git-compat-util.h
  Add new test to ensure git-merge handles pull.twohead and
    pull.octopus
  Move read_cache_unmerged() to read-cache.c
  git-fmt-merge-msg: make it usable from other builtins
  Introduce get_octopus_merge_bases() in commit.c
  Add new test to ensure git-merge handles more than 25 refs.
  Add new test case to ensure git-merge reduces octopus parents when
    possible
  Add new test case to ensure git-merge prepends the custom merge
    message
  Build in merge

 .gitignore                                    |    1 +
 Makefile                                      |    6 +-
 alias.c                                       |   54 ++
 builtin-fmt-merge-msg.c                       |  155 ++--
 builtin-merge-recursive.c                     |    8 -
 builtin-merge.c                               | 1143 +++++++++++++++++++++++++
 builtin-read-tree.c                           |   24 -
 builtin-remote.c                              |   39 +-
 builtin.h                                     |    4 +
 cache.h                                       |    3 +
 commit.c                                      |  136 +++-
 commit.h                                      |    4 +
 git-merge.sh => contrib/examples/git-merge.sh |    0 
 git-compat-util.h                             |    6 +
 git.c                                         |   54 +--
 parse-options.c                               |    6 -
 read-cache.c                                  |   31 +
 strbuf.c                                      |  140 +++-
 strbuf.h                                      |    3 +
 t/t0000-basic.sh                              |    8 +
 t/t7601-merge-pull-config.sh                  |  129 +++
 t/t7602-merge-octopus-many.sh                 |   52 ++
 t/t7603-merge-reduce-heads.sh                 |   63 ++
 t/t7604-merge-custom-message.sh               |   37 +
 test-strbuf.c                                 |   17 +
 25 files changed, 1917 insertions(+), 206 deletions(-)
 create mode 100644 builtin-merge.c
 rename git-merge.sh => contrib/examples/git-merge.sh (100%)
 create mode 100755 t/t7601-merge-pull-config.sh
 create mode 100755 t/t7602-merge-octopus-many.sh
 create mode 100755 t/t7603-merge-reduce-heads.sh
 create mode 100755 t/t7604-merge-custom-message.sh
 create mode 100644 test-strbuf.c

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH 01/15] Move split_cmdline() to alias.c
  2008-06-27 16:21 [PATCH 00/15] Build in merge Miklos Vajna
@ 2008-06-27 16:21 ` Miklos Vajna
  2008-06-27 16:21   ` [PATCH 02/15] Move commit_list_count() to commit.c Miklos Vajna
  2008-06-29 14:05   ` [PATCH 01/15] Move split_cmdline() to alias.c Olivier Marin
  0 siblings, 2 replies; 81+ messages in thread
From: Miklos Vajna @ 2008-06-27 16:21 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin, Junio C Hamano

split_cmdline() is currently used for aliases only, but later it can be
useful for other builtins as well. Move it to alias.c for now,
indicating that originally it's for aliases, but we'll have it in libgit
this way.

Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 alias.c |   54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 cache.h |    1 +
 git.c   |   53 -----------------------------------------------------
 3 files changed, 55 insertions(+), 53 deletions(-)

diff --git a/alias.c b/alias.c
index 995f3e6..ccb1108 100644
--- a/alias.c
+++ b/alias.c
@@ -21,3 +21,57 @@ char *alias_lookup(const char *alias)
 	git_config(alias_lookup_cb, NULL);
 	return alias_val;
 }
+
+int split_cmdline(char *cmdline, const char ***argv)
+{
+	int src, dst, count = 0, size = 16;
+	char quoted = 0;
+
+	*argv = xmalloc(sizeof(char*) * size);
+
+	/* split alias_string */
+	(*argv)[count++] = cmdline;
+	for (src = dst = 0; cmdline[src];) {
+		char c = cmdline[src];
+		if (!quoted && isspace(c)) {
+			cmdline[dst++] = 0;
+			while (cmdline[++src]
+					&& isspace(cmdline[src]))
+				; /* skip */
+			if (count >= size) {
+				size += 16;
+				*argv = xrealloc(*argv, sizeof(char*) * size);
+			}
+			(*argv)[count++] = cmdline + dst;
+		} else if (!quoted && (c == '\'' || c == '"')) {
+			quoted = c;
+			src++;
+		} else if (c == quoted) {
+			quoted = 0;
+			src++;
+		} else {
+			if (c == '\\' && quoted != '\'') {
+				src++;
+				c = cmdline[src];
+				if (!c) {
+					free(*argv);
+					*argv = NULL;
+					return error("cmdline ends with \\");
+				}
+			}
+			cmdline[dst++] = c;
+			src++;
+		}
+	}
+
+	cmdline[dst] = 0;
+
+	if (quoted) {
+		free(*argv);
+		*argv = NULL;
+		return error("unclosed quote");
+	}
+
+	return count;
+}
+
diff --git a/cache.h b/cache.h
index c8954ef..d5463d8 100644
--- a/cache.h
+++ b/cache.h
@@ -830,5 +830,6 @@ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_
 void overlay_tree_on_cache(const char *tree_name, const char *prefix);
 
 char *alias_lookup(const char *alias);
+int split_cmdline(char *cmdline, const char ***argv);
 
 #endif /* CACHE_H */
diff --git a/git.c b/git.c
index 59f0fcc..2fbe96b 100644
--- a/git.c
+++ b/git.c
@@ -90,59 +90,6 @@ static int handle_options(const char*** argv, int* argc, int* envchanged)
 	return handled;
 }
 
-static int split_cmdline(char *cmdline, const char ***argv)
-{
-	int src, dst, count = 0, size = 16;
-	char quoted = 0;
-
-	*argv = xmalloc(sizeof(char*) * size);
-
-	/* split alias_string */
-	(*argv)[count++] = cmdline;
-	for (src = dst = 0; cmdline[src];) {
-		char c = cmdline[src];
-		if (!quoted && isspace(c)) {
-			cmdline[dst++] = 0;
-			while (cmdline[++src]
-					&& isspace(cmdline[src]))
-				; /* skip */
-			if (count >= size) {
-				size += 16;
-				*argv = xrealloc(*argv, sizeof(char*) * size);
-			}
-			(*argv)[count++] = cmdline + dst;
-		} else if(!quoted && (c == '\'' || c == '"')) {
-			quoted = c;
-			src++;
-		} else if (c == quoted) {
-			quoted = 0;
-			src++;
-		} else {
-			if (c == '\\' && quoted != '\'') {
-				src++;
-				c = cmdline[src];
-				if (!c) {
-					free(*argv);
-					*argv = NULL;
-					return error("cmdline ends with \\");
-				}
-			}
-			cmdline[dst++] = c;
-			src++;
-		}
-	}
-
-	cmdline[dst] = 0;
-
-	if (quoted) {
-		free(*argv);
-		*argv = NULL;
-		return error("unclosed quote");
-	}
-
-	return count;
-}
-
 static int handle_alias(int *argcp, const char ***argv)
 {
 	int envchanged = 0, ret = 0, saved_errno = errno;
-- 
1.5.6

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH 02/15] Move commit_list_count() to commit.c
  2008-06-27 16:21 ` [PATCH 01/15] Move split_cmdline() to alias.c Miklos Vajna
@ 2008-06-27 16:21   ` Miklos Vajna
  2008-06-27 16:21     ` [PATCH 03/15] Move parse-options's skip_prefix() to git-compat-util.h Miklos Vajna
  2008-06-29 14:05   ` [PATCH 01/15] Move split_cmdline() to alias.c Olivier Marin
  1 sibling, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-06-27 16:21 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin, Junio C Hamano

This function is useful outside builtin-merge-recursive, for example in
builtin-merge.

Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin-merge-recursive.c |    8 --------
 commit.c                  |    8 ++++++++
 commit.h                  |    1 +
 3 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c
index 43bf6aa..3731853 100644
--- a/builtin-merge-recursive.c
+++ b/builtin-merge-recursive.c
@@ -42,14 +42,6 @@ static struct tree *shift_tree_object(struct tree *one, struct tree *two)
  * - *(int *)commit->object.sha1 set to the virtual id.
  */
 
-static unsigned commit_list_count(const struct commit_list *l)
-{
-	unsigned c = 0;
-	for (; l; l = l->next )
-		c++;
-	return c;
-}
-
 static struct commit *make_virtual_commit(struct tree *tree, const char *comment)
 {
 	struct commit *commit = xcalloc(1, sizeof(struct commit));
diff --git a/commit.c b/commit.c
index e2d8624..bbf9c75 100644
--- a/commit.c
+++ b/commit.c
@@ -325,6 +325,14 @@ struct commit_list *commit_list_insert(struct commit *item, struct commit_list *
 	return new_list;
 }
 
+unsigned commit_list_count(const struct commit_list *l)
+{
+	unsigned c = 0;
+	for (; l; l = l->next )
+		c++;
+	return c;
+}
+
 void free_commit_list(struct commit_list *list)
 {
 	while (list) {
diff --git a/commit.h b/commit.h
index 2d94d41..7f8c5ee 100644
--- a/commit.h
+++ b/commit.h
@@ -41,6 +41,7 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size);
 int parse_commit(struct commit *item);
 
 struct commit_list * commit_list_insert(struct commit *item, struct commit_list **list_p);
+unsigned commit_list_count(const struct commit_list *l);
 struct commit_list * insert_by_date(struct commit *item, struct commit_list **list);
 
 void free_commit_list(struct commit_list *list);
-- 
1.5.6

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH 03/15] Move parse-options's skip_prefix() to git-compat-util.h
  2008-06-27 16:21   ` [PATCH 02/15] Move commit_list_count() to commit.c Miklos Vajna
@ 2008-06-27 16:21     ` Miklos Vajna
  2008-06-27 16:21       ` [PATCH 04/15] Add new test to ensure git-merge handles pull.twohead and pull.octopus Miklos Vajna
  0 siblings, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-06-27 16:21 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin, Junio C Hamano

builtin-remote.c and parse-options.c both have a skip_prefix() function,
for the same purpose. Move parse-options's one to git-compat-util.h and
let builtin-remote use it as well.

Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin-remote.c  |   39 ++++++++++++++++++++++++++-------------
 git-compat-util.h |    6 ++++++
 parse-options.c   |    6 ------
 3 files changed, 32 insertions(+), 19 deletions(-)

diff --git a/builtin-remote.c b/builtin-remote.c
index 145dd85..1491354 100644
--- a/builtin-remote.c
+++ b/builtin-remote.c
@@ -29,12 +29,6 @@ static inline int postfixcmp(const char *string, const char *postfix)
 	return strcmp(string + len1 - len2, postfix);
 }
 
-static inline const char *skip_prefix(const char *name, const char *prefix)
-{
-	return !name ? "" :
-		prefixcmp(name, prefix) ?  name : name + strlen(prefix);
-}
-
 static int opt_parse_track(const struct option *opt, const char *arg, int not)
 {
 	struct path_list *list = opt->value;
@@ -182,12 +176,18 @@ static int config_read_branches(const char *key, const char *value, void *cb)
 			info->remote = xstrdup(value);
 		} else {
 			char *space = strchr(value, ' ');
-			value = skip_prefix(value, "refs/heads/");
+			const char *ptr = skip_prefix(value, "refs/heads/");
+			if (ptr)
+				value = ptr;
 			while (space) {
 				char *merge;
 				merge = xstrndup(value, space - value);
 				path_list_append(merge, &info->merge);
-				value = skip_prefix(space + 1, "refs/heads/");
+				ptr = skip_prefix(space + 1, "refs/heads/");
+				if (ptr)
+					value = ptr;
+				else
+					value = space + 1;
 				space = strchr(value, ' ');
 			}
 			path_list_append(xstrdup(value), &info->merge);
@@ -219,7 +219,12 @@ static int handle_one_branch(const char *refname,
 	refspec.dst = (char *)refname;
 	if (!remote_find_tracking(states->remote, &refspec)) {
 		struct path_list_item *item;
-		const char *name = skip_prefix(refspec.src, "refs/heads/");
+		const char *name, *ptr;
+		ptr = skip_prefix(refspec.src, "refs/heads/");
+		if (ptr)
+			name = ptr;
+		else
+			name = refspec.src;
 		/* symbolic refs pointing nowhere were handled already */
 		if ((flags & REF_ISSYMREF) ||
 				unsorted_path_list_has_path(&states->tracked,
@@ -248,6 +253,7 @@ static int get_ref_states(const struct ref *ref, struct ref_states *states)
 		struct path_list *target = &states->tracked;
 		unsigned char sha1[20];
 		void *util = NULL;
+		const char *ptr;
 
 		if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1))
 			target = &states->new;
@@ -256,8 +262,10 @@ static int get_ref_states(const struct ref *ref, struct ref_states *states)
 			if (hashcmp(sha1, ref->new_sha1))
 				util = &states;
 		}
-		path_list_append(skip_prefix(ref->name, "refs/heads/"),
-				target)->util = util;
+		ptr = skip_prefix(ref->name, "refs/heads/");
+		if (!ptr)
+			ptr = ref->name;
+		path_list_append(ptr, target)->util = util;
 	}
 	free_refs(fetch_map);
 
@@ -522,10 +530,15 @@ static int show(int argc, const char **argv)
 					"es" : "");
 			for (i = 0; i < states.remote->push_refspec_nr; i++) {
 				struct refspec *spec = states.remote->push + i;
+				const char *p = "", *q = "";
+				if (spec->src)
+					p = skip_prefix(spec->src, "refs/heads/");
+				if (spec->dst)
+					q = skip_prefix(spec->dst, "refs/heads/");
 				printf(" %s%s%s%s", spec->force ? "+" : "",
-					skip_prefix(spec->src, "refs/heads/"),
+					p ? p : spec->src,
 					spec->dst ? ":" : "",
-					skip_prefix(spec->dst, "refs/heads/"));
+					q ? q : spec->dst);
 			}
 			printf("\n");
 		}
diff --git a/git-compat-util.h b/git-compat-util.h
index 6f94a81..31edc98 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -127,6 +127,12 @@ extern void set_warn_routine(void (*routine)(const char *warn, va_list params));
 
 extern int prefixcmp(const char *str, const char *prefix);
 
+static inline const char *skip_prefix(const char *str, const char *prefix)
+{
+	size_t len = strlen(prefix);
+	return strncmp(str, prefix, len) ? NULL : str + len;
+}
+
 #ifdef NO_MMAP
 
 #ifndef PROT_READ
diff --git a/parse-options.c b/parse-options.c
index b8bde2b..bbc3ca4 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -22,12 +22,6 @@ static inline const char *get_arg(struct optparse_t *p)
 	return *++p->argv;
 }
 
-static inline const char *skip_prefix(const char *str, const char *prefix)
-{
-	size_t len = strlen(prefix);
-	return strncmp(str, prefix, len) ? NULL : str + len;
-}
-
 static int opterror(const struct option *opt, const char *reason, int flags)
 {
 	if (flags & OPT_SHORT)
-- 
1.5.6

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH 04/15] Add new test to ensure git-merge handles pull.twohead and pull.octopus
  2008-06-27 16:21     ` [PATCH 03/15] Move parse-options's skip_prefix() to git-compat-util.h Miklos Vajna
@ 2008-06-27 16:21       ` Miklos Vajna
  2008-06-27 16:21         ` [PATCH 05/15] Move read_cache_unmerged() to read-cache.c Miklos Vajna
                           ` (2 more replies)
  0 siblings, 3 replies; 81+ messages in thread
From: Miklos Vajna @ 2008-06-27 16:21 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

Test if the given strategies are used and test the case when multiple
strategies are configured using a space separated list.

Also test if the best strategy is picked if none is specified.  This is
done by adding a simple test case where recursive detects a rename, but
resolve does not, and verify that finally merge will pick up the
previous.

Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---
 t/t7601-merge-pull-config.sh |  129 ++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 129 insertions(+), 0 deletions(-)
 create mode 100755 t/t7601-merge-pull-config.sh

diff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh
new file mode 100755
index 0000000..be622f3
--- /dev/null
+++ b/t/t7601-merge-pull-config.sh
@@ -0,0 +1,129 @@
+#!/bin/sh
+
+test_description='git-merge
+
+Testing pull.* configuration parsing.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	echo c0 >c0.c &&
+	git add c0.c &&
+	git commit -m c0 &&
+	git tag c0 &&
+	echo c1 >c1.c &&
+	git add c1.c &&
+	git commit -m c1 &&
+	git tag c1 &&
+	git reset --hard c0 &&
+	echo c2 >c2.c &&
+	git add c2.c &&
+	git commit -m c2 &&
+	git tag c2
+	git reset --hard c0 &&
+	echo c3 >c3.c &&
+	git add c3.c &&
+	git commit -m c3 &&
+	git tag c3
+'
+
+test_expect_success 'merge c1 with c2' '
+	git reset --hard c1 &&
+	test -f c0.c &&
+	test -f c1.c &&
+	test ! -f c2.c &&
+	test ! -f c3.c &&
+	git merge c2 &&
+	test -f c1.c &&
+	test -f c2.c
+'
+
+test_expect_success 'merge c1 with c2 (ours in pull.twohead)' '
+	git reset --hard c1 &&
+	git config pull.twohead ours &&
+	git merge c2 &&
+	test -f c1.c &&
+	! test -f c2.c
+'
+
+test_expect_success 'merge c1 with c2 and c3 (recursive in pull.octopus)' '
+	git reset --hard c1 &&
+	git config pull.octopus "recursive" &&
+	test_must_fail git merge c2 c3 &&
+	test "$(git rev-parse c1)" = "$(git rev-parse HEAD)"
+'
+
+test_expect_success 'merge c1 with c2 and c3 (recursive and octopus in pull.octopus)' '
+	git reset --hard c1 &&
+	git config pull.octopus "recursive octopus" &&
+	git merge c2 c3 &&
+	test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
+	test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
+	test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
+	test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)"
+	test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
+	git diff --exit-code &&
+	test -f c0.c &&
+	test -f c1.c &&
+	test -f c2.c &&
+	test -f c3.c
+'
+
+conflict_count()
+{
+	eval $1=`{
+		git diff-files --name-only
+		git ls-files --unmerged
+	} | wc -l`
+}
+
+# c4 - c5
+#    \ c6
+#
+# There are two conflicts here:
+#
+# 1) Because foo.c is renamed to bar.c, recursive will handle this,
+# resolve won't.
+#
+# 2) One in conflict.c and that will always fail.
+
+test_expect_success 'setup conflicted merge' '
+	git reset --hard c0 &&
+	echo A >conflict.c &&
+	git add conflict.c &&
+	echo contents >foo.c &&
+	git add foo.c &&
+	git commit -m c4 &&
+	git tag c4 &&
+	echo B >conflict.c &&
+	git add conflict.c &&
+	git mv foo.c bar.c &&
+	git commit -m c5 &&
+	git tag c5 &&
+	git reset --hard c4 &&
+	echo C >conflict.c &&
+	git add conflict.c &&
+	echo secondline >> foo.c &&
+	git add foo.c &&
+	git commit -m c6 &&
+	git tag c6
+'
+
+# First do the merge with resolve and recursive then verify that
+# recusive is choosen.
+
+test_expect_success 'merge picks up the best result' '
+	git config pull.twohead "recursive resolve" &&
+	git reset --hard c5 &&
+	git merge -s resolve c6
+	conflict_count resolve_count &&
+	git reset --hard c5 &&
+	git merge -s recursive c6
+	conflict_count recursive_count &&
+	git reset --hard c5 &&
+	git merge c6
+	conflict_count auto_count &&
+	test "$auto_count" = "$recursive_count"
+'
+
+test_done
-- 
1.5.6

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH 05/15] Move read_cache_unmerged() to read-cache.c
  2008-06-27 16:21       ` [PATCH 04/15] Add new test to ensure git-merge handles pull.twohead and pull.octopus Miklos Vajna
@ 2008-06-27 16:21         ` Miklos Vajna
  2008-06-27 16:21           ` [PATCH 06/15] git-fmt-merge-msg: make it usable from other builtins Miklos Vajna
  2008-06-29 13:30         ` [PATCH 04/15] Add new test to ensure git-merge handles pull.twohead and pull.octopus Olivier Marin
  2008-07-04 16:34         ` [PATCH 04/15] " Mike Ralphson
  2 siblings, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-06-27 16:21 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin, Junio C Hamano

builtin-read-tree has a read_cache_unmerged() which is useful for other
builtins, for example builtin-merge uses it as well. Move it to
read-cache.c to avoid code duplication.

Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin-read-tree.c |   24 ------------------------
 cache.h             |    2 ++
 read-cache.c        |   31 +++++++++++++++++++++++++++++++
 3 files changed, 33 insertions(+), 24 deletions(-)

diff --git a/builtin-read-tree.c b/builtin-read-tree.c
index 5a09e17..72a6de3 100644
--- a/builtin-read-tree.c
+++ b/builtin-read-tree.c
@@ -29,30 +29,6 @@ static int list_tree(unsigned char *sha1)
 	return 0;
 }
 
-static int read_cache_unmerged(void)
-{
-	int i;
-	struct cache_entry **dst;
-	struct cache_entry *last = NULL;
-
-	read_cache();
-	dst = active_cache;
-	for (i = 0; i < active_nr; i++) {
-		struct cache_entry *ce = active_cache[i];
-		if (ce_stage(ce)) {
-			remove_name_hash(ce);
-			if (last && !strcmp(ce->name, last->name))
-				continue;
-			cache_tree_invalidate_path(active_cache_tree, ce->name);
-			last = ce;
-			continue;
-		}
-		*dst++ = ce;
-	}
-	active_nr = dst - active_cache;
-	return !!last;
-}
-
 static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree)
 {
 	struct tree_desc desc;
diff --git a/cache.h b/cache.h
index d5463d8..7fb6c70 100644
--- a/cache.h
+++ b/cache.h
@@ -254,6 +254,7 @@ static inline void remove_name_hash(struct cache_entry *ce)
 
 #define read_cache() read_index(&the_index)
 #define read_cache_from(path) read_index_from(&the_index, (path))
+#define read_cache_unmerged() read_index_unmerged(&the_index)
 #define write_cache(newfd, cache, entries) write_index(&the_index, (newfd))
 #define discard_cache() discard_index(&the_index)
 #define unmerged_cache() unmerged_index(&the_index)
@@ -356,6 +357,7 @@ extern int init_db(const char *template_dir, unsigned int flags);
 /* Initialize and use the cache information */
 extern int read_index(struct index_state *);
 extern int read_index_from(struct index_state *, const char *path);
+extern int read_index_unmerged(struct index_state *);
 extern int write_index(const struct index_state *, int newfd);
 extern int discard_index(struct index_state *);
 extern int unmerged_index(const struct index_state *);
diff --git a/read-cache.c b/read-cache.c
index f83de8c..d801f9d 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -1410,3 +1410,34 @@ int write_index(const struct index_state *istate, int newfd)
 	}
 	return ce_flush(&c, newfd);
 }
+
+/*
+ * Read the index file that is potentially unmerged into given
+ * index_state, dropping any unmerged entries.  Returns true is
+ * the index is unmerged.  Callers who want to refuse to work
+ * from an unmerged state can call this and check its return value,
+ * instead of calling read_cache().
+ */
+int read_index_unmerged(struct index_state *istate)
+{
+	int i;
+	struct cache_entry **dst;
+	struct cache_entry *last = NULL;
+
+	read_index(istate);
+	dst = istate->cache;
+	for (i = 0; i < istate->cache_nr; i++) {
+		struct cache_entry *ce = istate->cache[i];
+		if (ce_stage(ce)) {
+			remove_name_hash(ce);
+			if (last && !strcmp(ce->name, last->name))
+				continue;
+			cache_tree_invalidate_path(istate->cache_tree, ce->name);
+			last = ce;
+			continue;
+		}
+		*dst++ = ce;
+	}
+	istate->cache_nr = dst - istate->cache;
+	return !!last;
+}
-- 
1.5.6

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH 06/15] git-fmt-merge-msg: make it usable from other builtins
  2008-06-27 16:21         ` [PATCH 05/15] Move read_cache_unmerged() to read-cache.c Miklos Vajna
@ 2008-06-27 16:21           ` Miklos Vajna
  2008-06-27 16:22             ` [PATCH 07/15] Introduce get_octopus_merge_bases() in commit.c Miklos Vajna
  0 siblings, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-06-27 16:21 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

Move all functionality (except config and option parsing) from
cmd_fmt_merge_msg() to fmt_merge_msg(), so that other builtins can use
it without a child process.

All functions have been changed to use strbufs, and now only
cmd_fmt_merge_msg() reads directly from a file / writes anything to
stdout.

Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---
 builtin-fmt-merge-msg.c |  155 +++++++++++++++++++++++++++--------------------
 builtin.h               |    3 +
 2 files changed, 92 insertions(+), 66 deletions(-)

diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c
index b892621..dbd7d2d 100644
--- a/builtin-fmt-merge-msg.c
+++ b/builtin-fmt-merge-msg.c
@@ -159,23 +159,24 @@ static int handle_line(char *line)
 }
 
 static void print_joined(const char *singular, const char *plural,
-		struct list *list)
+		struct list *list, struct strbuf *out)
 {
 	if (list->nr == 0)
 		return;
 	if (list->nr == 1) {
-		printf("%s%s", singular, list->list[0]);
+		strbuf_addf(out, "%s%s", singular, list->list[0]);
 	} else {
 		int i;
-		printf("%s", plural);
+		strbuf_addstr(out, plural);
 		for (i = 0; i < list->nr - 1; i++)
-			printf("%s%s", i > 0 ? ", " : "", list->list[i]);
-		printf(" and %s", list->list[list->nr - 1]);
+			strbuf_addf(out, "%s%s", i > 0 ? ", " : "", list->list[i]);
+		strbuf_addf(out, " and %s", list->list[list->nr - 1]);
 	}
 }
 
 static void shortlog(const char *name, unsigned char *sha1,
-		struct commit *head, struct rev_info *rev, int limit)
+		struct commit *head, struct rev_info *rev, int limit,
+		struct strbuf *out)
 {
 	int i, count = 0;
 	struct commit *commit;
@@ -232,15 +233,15 @@ static void shortlog(const char *name, unsigned char *sha1,
 	}
 
 	if (count > limit)
-		printf("\n* %s: (%d commits)\n", name, count);
+		strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
 	else
-		printf("\n* %s:\n", name);
+		strbuf_addf(out, "\n* %s:\n", name);
 
 	for (i = 0; i < subjects.nr; i++)
 		if (i >= limit)
-			printf("  ...\n");
+			strbuf_addf(out, "  ...\n");
 		else
-			printf("  %s\n", subjects.list[i]);
+			strbuf_addf(out, "  %s\n", subjects.list[i]);
 
 	clear_commit_marks((struct commit *)branch, flags);
 	clear_commit_marks(head, flags);
@@ -251,43 +252,13 @@ static void shortlog(const char *name, unsigned char *sha1,
 	free_list(&subjects);
 }
 
-int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
-{
-	int limit = 20, i = 0;
+int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) {
+	int limit = 20, i = 0, pos = 0;
 	char line[1024];
-	FILE *in = stdin;
-	const char *sep = "";
+	char *p = line, *sep = "";
 	unsigned char head_sha1[20];
 	const char *current_branch;
 
-	git_config(fmt_merge_msg_config, NULL);
-
-	while (argc > 1) {
-		if (!strcmp(argv[1], "--log") || !strcmp(argv[1], "--summary"))
-			merge_summary = 1;
-		else if (!strcmp(argv[1], "--no-log")
-				|| !strcmp(argv[1], "--no-summary"))
-			merge_summary = 0;
-		else if (!strcmp(argv[1], "-F") || !strcmp(argv[1], "--file")) {
-			if (argc < 3)
-				die ("Which file?");
-			if (!strcmp(argv[2], "-"))
-				in = stdin;
-			else {
-				fclose(in);
-				in = fopen(argv[2], "r");
-				if (!in)
-					die("cannot open %s", argv[2]);
-			}
-			argc--; argv++;
-		} else
-			break;
-		argc--; argv++;
-	}
-
-	if (argc > 1)
-		usage(fmt_merge_msg_usage);
-
 	/* get current branch */
 	current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
 	if (!current_branch)
@@ -295,75 +266,127 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
 	if (!prefixcmp(current_branch, "refs/heads/"))
 		current_branch += 11;
 
-	while (fgets(line, sizeof(line), in)) {
+	/* get a line */
+	while (pos < in->len) {
+		int len;
+		char *newline;
+
+		p = in->buf + pos;
+		newline = strchr(p, '\n');
+		len = newline ? newline - p : strlen(p);
+		pos += len + !!newline;
 		i++;
-		if (line[0] == 0)
-			continue;
-		if (handle_line(line))
-			die ("Error in line %d: %s", i, line);
+		p[len] = 0;
+		if (handle_line(p))
+			die ("Error in line %d: %.*s", i, len, p);
 	}
 
-	printf("Merge ");
+	strbuf_addstr(out, "Merge ");
 	for (i = 0; i < srcs.nr; i++) {
 		struct src_data *src_data = srcs.payload[i];
 		const char *subsep = "";
 
-		printf(sep);
+		strbuf_addstr(out, sep);
 		sep = "; ";
 
 		if (src_data->head_status == 1) {
-			printf(srcs.list[i]);
+			strbuf_addstr(out, srcs.list[i]);
 			continue;
 		}
 		if (src_data->head_status == 3) {
 			subsep = ", ";
-			printf("HEAD");
+			strbuf_addstr(out, "HEAD");
 		}
 		if (src_data->branch.nr) {
-			printf(subsep);
+			strbuf_addstr(out, subsep);
 			subsep = ", ";
-			print_joined("branch ", "branches ", &src_data->branch);
+			print_joined("branch ", "branches ", &src_data->branch,
+					out);
 		}
 		if (src_data->r_branch.nr) {
-			printf(subsep);
+			strbuf_addstr(out, subsep);
 			subsep = ", ";
 			print_joined("remote branch ", "remote branches ",
-					&src_data->r_branch);
+					&src_data->r_branch, out);
 		}
 		if (src_data->tag.nr) {
-			printf(subsep);
+			strbuf_addstr(out, subsep);
 			subsep = ", ";
-			print_joined("tag ", "tags ", &src_data->tag);
+			print_joined("tag ", "tags ", &src_data->tag, out);
 		}
 		if (src_data->generic.nr) {
-			printf(subsep);
-			print_joined("commit ", "commits ", &src_data->generic);
+			strbuf_addstr(out, subsep);
+			print_joined("commit ", "commits ", &src_data->generic,
+					out);
 		}
 		if (strcmp(".", srcs.list[i]))
-			printf(" of %s", srcs.list[i]);
+			strbuf_addf(out, " of %s", srcs.list[i]);
 	}
 
 	if (!strcmp("master", current_branch))
-		putchar('\n');
+		strbuf_addch(out, '\n');
 	else
-		printf(" into %s\n", current_branch);
+		strbuf_addf(out, " into %s\n", current_branch);
 
 	if (merge_summary) {
 		struct commit *head;
 		struct rev_info rev;
 
 		head = lookup_commit(head_sha1);
-		init_revisions(&rev, prefix);
+		init_revisions(&rev, NULL);
 		rev.commit_format = CMIT_FMT_ONELINE;
 		rev.ignore_merges = 1;
 		rev.limited = 1;
 
 		for (i = 0; i < origins.nr; i++)
 			shortlog(origins.list[i], origins.payload[i],
-					head, &rev, limit);
+					head, &rev, limit, out);
 	}
+	return 0;
+}
+
+int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
+{
+	FILE *in = stdin;
+	struct strbuf input, output;
+	int ret;
+
+	git_config(fmt_merge_msg_config, NULL);
+
+	while (argc > 1) {
+		if (!strcmp(argv[1], "--log") || !strcmp(argv[1], "--summary"))
+			merge_summary = 1;
+		else if (!strcmp(argv[1], "--no-log")
+				|| !strcmp(argv[1], "--no-summary"))
+			merge_summary = 0;
+		else if (!strcmp(argv[1], "-F") || !strcmp(argv[1], "--file")) {
+			if (argc < 3)
+				die ("Which file?");
+			if (!strcmp(argv[2], "-"))
+				in = stdin;
+			else {
+				fclose(in);
+				in = fopen(argv[2], "r");
+				if (!in)
+					die("cannot open %s", argv[2]);
+			}
+			argc--; argv++;
+		} else
+			break;
+		argc--; argv++;
+	}
+
+	if (argc > 1)
+		usage(fmt_merge_msg_usage);
 
-	/* No cleanup yet; is standalone anyway */
+	strbuf_init(&input, 0);
+	if (strbuf_read(&input, fileno(in), 0) < 0)
+		die("could not read input file %s", strerror(errno));
+	strbuf_init(&output, 0);
 
+	ret = fmt_merge_msg(merge_summary, &input, &output);
+	if (ret)
+		return ret;
+	printf("%s", output.buf);
 	return 0;
 }
diff --git a/builtin.h b/builtin.h
index b460b2d..2b01fea 100644
--- a/builtin.h
+++ b/builtin.h
@@ -2,6 +2,7 @@
 #define BUILTIN_H
 
 #include "git-compat-util.h"
+#include "strbuf.h"
 
 extern const char git_version_string[];
 extern const char git_usage_string[];
@@ -11,6 +12,8 @@ extern void list_common_cmds_help(void);
 extern void help_unknown_cmd(const char *cmd);
 extern void prune_packed_objects(int);
 extern int read_line_with_nul(char *buf, int size, FILE *file);
+extern int fmt_merge_msg(int merge_summary, struct strbuf *in,
+	struct strbuf *out);
 
 extern int cmd_add(int argc, const char **argv, const char *prefix);
 extern int cmd_annotate(int argc, const char **argv, const char *prefix);
-- 
1.5.6

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH 07/15] Introduce get_octopus_merge_bases() in commit.c
  2008-06-27 16:21           ` [PATCH 06/15] git-fmt-merge-msg: make it usable from other builtins Miklos Vajna
@ 2008-06-27 16:22             ` Miklos Vajna
  2008-06-27 16:22               ` [PATCH 08/15] Add new test to ensure git-merge handles more than 25 refs Miklos Vajna
  0 siblings, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-06-27 16:22 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin, Junio C Hamano

This is like get_merge_bases() but it works for multiple heads, like
show-branch --merge-base.

Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 commit.c |   27 +++++++++++++++++++++++++++
 commit.h |    1 +
 2 files changed, 28 insertions(+), 0 deletions(-)

diff --git a/commit.c b/commit.c
index bbf9c75..6052ca3 100644
--- a/commit.c
+++ b/commit.c
@@ -600,6 +600,33 @@ static struct commit_list *merge_bases(struct commit *one, struct commit *two)
 	return result;
 }
 
+struct commit_list *get_octopus_merge_bases(struct commit_list *in)
+{
+	struct commit_list *i, *j, *k, *ret = NULL;
+	struct commit_list **pptr = &ret;
+
+	for (i = in; i; i = i->next) {
+		if (!ret)
+			pptr = &commit_list_insert(i->item, pptr)->next;
+		else {
+			struct commit_list *new = NULL, *end = NULL;
+
+			for (j = ret; j; j = j->next) {
+				struct commit_list *bases;
+				bases = get_merge_bases(i->item, j->item, 1);
+				if (!new)
+					new = bases;
+				else
+					end->next = bases;
+				for (k = bases; k; k = k->next)
+					end = k;
+			}
+			ret = new;
+		}
+	}
+	return ret;
+}
+
 struct commit_list *get_merge_bases(struct commit *one,
 					struct commit *two, int cleanup)
 {
diff --git a/commit.h b/commit.h
index 7f8c5ee..dcec7fb 100644
--- a/commit.h
+++ b/commit.h
@@ -121,6 +121,7 @@ int read_graft_file(const char *graft_file);
 struct commit_graft *lookup_commit_graft(const unsigned char *sha1);
 
 extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup);
+extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);
 
 extern int register_shallow(const unsigned char *sha1);
 extern int unregister_shallow(const unsigned char *sha1);
-- 
1.5.6

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH 08/15] Add new test to ensure git-merge handles more than 25 refs.
  2008-06-27 16:22             ` [PATCH 07/15] Introduce get_octopus_merge_bases() in commit.c Miklos Vajna
@ 2008-06-27 16:22               ` Miklos Vajna
  2008-06-27 16:22                 ` [PATCH 09/15] Introduce get_merge_bases_many() Miklos Vajna
  2008-06-27 17:06                 ` [PATCH 08/15] Add new test to ensure git-merge handles more than 25 refs Miklos Vajna
  0 siblings, 2 replies; 81+ messages in thread
From: Miklos Vajna @ 2008-06-27 16:22 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin, Junio C Hamano

The old shell version handled only 25 refs but we no longer have this
limitation. Add a test to make sure this limitation will not be
introduced again in the future.

Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 t/t7602-merge-octopus-many.sh |   52 +++++++++++++++++++++++++++++++++++++++++
 1 files changed, 52 insertions(+), 0 deletions(-)
 create mode 100755 t/t7602-merge-octopus-many.sh

diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh
new file mode 100755
index 0000000..f3a4bb2
--- /dev/null
+++ b/t/t7602-merge-octopus-many.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+test_description='git-merge
+
+Testing octopus merge with more than 25 refs.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	echo c0 > c0.c &&
+	git add c0.c &&
+	git commit -m c0 &&
+	git tag c0 &&
+	i=1 &&
+	while test $i -le 30
+	do
+		git reset --hard c0 &&
+		echo c$i > c$i.c &&
+		git add c$i.c &&
+		git commit -m c$i &&
+		git tag c$i &&
+		i=`expr $i + 1` || return 1
+	done
+'
+
+test_expect_failure 'merge c1 with c2, c3, c4, ... c29' '
+	git reset --hard c1 &&
+	i=2 &&
+	refs="" &&
+	while test $i -le 30
+	do
+		refs="$refs c$i"
+		i=`expr $i + 1`
+	done
+	git merge $refs &&
+	test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
+	i=1 &&
+	while test $i -le 30
+	do
+		test "$(git rev-parse c$i)" = "$(git rev-parse HEAD^$i)" &&
+		i=`expr $i + 1` || return 1
+	done &&
+	git diff --exit-code &&
+	i=1 &&
+	while test $i -le 30
+	do
+		test -f c$i.c &&
+		i=`expr $i + 1` || return 1
+	done
+'
+
+test_done
-- 
1.5.6

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH 09/15] Introduce get_merge_bases_many()
  2008-06-27 16:22               ` [PATCH 08/15] Add new test to ensure git-merge handles more than 25 refs Miklos Vajna
@ 2008-06-27 16:22                 ` Miklos Vajna
  2008-06-27 16:22                   ` [PATCH 10/15] Introduce reduce_heads() Miklos Vajna
  2008-06-27 17:06                 ` [PATCH 08/15] Add new test to ensure git-merge handles more than 25 refs Miklos Vajna
  1 sibling, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-06-27 16:22 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin, Junio C Hamano

From: Junio C Hamano <gitster@pobox.com>

This introduces a new function get_merge_bases_many() which is a natural
extension of two commit merge base computation.  It is given one commit
(one) and a set of other commits (twos), and computes the merge base of
one and a merge across other commits.

This is mostly useful to figure out the common ancestor when iterating
over heads during an octopus merge.  When making an octopus between
commits A, B, C and D, we first merge tree of A and B, and then try to
merge C with it.  If we were making pairwise merge, we would be recording
the tree resulting from the merge between A and B as a commit, say M, and
then the next round we will be computing the merge base between M and C.

         o---C...*
        /       .
       o---B...M
      /       .
     o---o---A

But during an octopus merge, we actually do not create a commit M.  In
order to figure out that the common ancestor to use for this merge,
instead of computing the merge base between C and M, we can call
merge_bases_many() with one set to C and twos containing A and B.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 commit.c |   56 ++++++++++++++++++++++++++++++++++++++------------------
 1 files changed, 38 insertions(+), 18 deletions(-)

diff --git a/commit.c b/commit.c
index 6052ca3..cafed26 100644
--- a/commit.c
+++ b/commit.c
@@ -533,26 +533,34 @@ static struct commit *interesting(struct commit_list *list)
 	return NULL;
 }
 
-static struct commit_list *merge_bases(struct commit *one, struct commit *two)
+static struct commit_list *merge_bases_many(struct commit *one, int n, struct commit **twos)
 {
 	struct commit_list *list = NULL;
 	struct commit_list *result = NULL;
+	int i;
 
-	if (one == two)
-		/* We do not mark this even with RESULT so we do not
-		 * have to clean it up.
-		 */
-		return commit_list_insert(one, &result);
+	for (i = 0; i < n; i++) {
+		if (one == twos[i])
+			/*
+			 * We do not mark this even with RESULT so we do not
+			 * have to clean it up.
+			 */
+			return commit_list_insert(one, &result);
+	}
 
 	if (parse_commit(one))
 		return NULL;
-	if (parse_commit(two))
-		return NULL;
+	for (i = 0; i < n; i++) {
+		if (parse_commit(twos[i]))
+			return NULL;
+	}
 
 	one->object.flags |= PARENT1;
-	two->object.flags |= PARENT2;
 	insert_by_date(one, &list);
-	insert_by_date(two, &list);
+	for (i = 0; i < n; i++) {
+		twos[i]->object.flags |= PARENT2;
+		insert_by_date(twos[i], &list);
+	}
 
 	while (interesting(list)) {
 		struct commit *commit;
@@ -627,21 +635,26 @@ struct commit_list *get_octopus_merge_bases(struct commit_list *in)
 	return ret;
 }
 
-struct commit_list *get_merge_bases(struct commit *one,
-					struct commit *two, int cleanup)
+struct commit_list *get_merge_bases_many(struct commit *one,
+					 int n,
+					 struct commit **twos,
+					 int cleanup)
 {
 	struct commit_list *list;
 	struct commit **rslt;
 	struct commit_list *result;
 	int cnt, i, j;
 
-	result = merge_bases(one, two);
-	if (one == two)
-		return result;
+	result = merge_bases_many(one, n, twos);
+	for (i = 0; i < n; i++) {
+		if (one == twos[i])
+			return result;
+	}
 	if (!result || !result->next) {
 		if (cleanup) {
 			clear_commit_marks(one, all_flags);
-			clear_commit_marks(two, all_flags);
+			for (i = 0; i < n; i++)
+				clear_commit_marks(twos[i], all_flags);
 		}
 		return result;
 	}
@@ -659,12 +672,13 @@ struct commit_list *get_merge_bases(struct commit *one,
 	free_commit_list(result);
 
 	clear_commit_marks(one, all_flags);
-	clear_commit_marks(two, all_flags);
+	for (i = 0; i < n; i++)
+		clear_commit_marks(twos[i], all_flags);
 	for (i = 0; i < cnt - 1; i++) {
 		for (j = i+1; j < cnt; j++) {
 			if (!rslt[i] || !rslt[j])
 				continue;
-			result = merge_bases(rslt[i], rslt[j]);
+			result = merge_bases_many(rslt[i], 1, &rslt[j]);
 			clear_commit_marks(rslt[i], all_flags);
 			clear_commit_marks(rslt[j], all_flags);
 			for (list = result; list; list = list->next) {
@@ -686,6 +700,12 @@ struct commit_list *get_merge_bases(struct commit *one,
 	return result;
 }
 
+struct commit_list *get_merge_bases(struct commit *one, struct commit *two,
+				    int cleanup)
+{
+	return get_merge_bases_many(one, 1, &two, cleanup);
+}
+
 int in_merge_bases(struct commit *commit, struct commit **reference, int num)
 {
 	struct commit_list *bases, *b;
-- 
1.5.6

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH 10/15] Introduce reduce_heads()
  2008-06-27 16:22                 ` [PATCH 09/15] Introduce get_merge_bases_many() Miklos Vajna
@ 2008-06-27 16:22                   ` Miklos Vajna
  2008-06-27 16:22                     ` [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf() Miklos Vajna
  0 siblings, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-06-27 16:22 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin, Junio C Hamano

From: Junio C Hamano <gitster@pobox.com>

The new function reduce_heads() is given a list of commits, and removes
ones that can be reached from other commits in the list.  It is useful for
reducing the commits randomly thrown at the git-merge command and remove
redundant commits that the user shouldn't have given to it.

The implementation uses the get_merge_bases_many() introduced in the
previous commit.  If the merge base between one commit taken from the list
and the remaining commits is the commit itself, that means the commit is
reachable from some of the other commits.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 commit.c |   45 +++++++++++++++++++++++++++++++++++++++++++++
 commit.h |    2 ++
 2 files changed, 47 insertions(+), 0 deletions(-)

diff --git a/commit.c b/commit.c
index cafed26..d20b14e 100644
--- a/commit.c
+++ b/commit.c
@@ -725,3 +725,48 @@ int in_merge_bases(struct commit *commit, struct commit **reference, int num)
 	free_commit_list(bases);
 	return ret;
 }
+
+struct commit_list *reduce_heads(struct commit_list *heads)
+{
+	struct commit_list *p;
+	struct commit_list *result = NULL, **tail = &result;
+	struct commit **other;
+	size_t num_head, num_other;
+
+	if (!heads)
+		return NULL;
+
+	/* Avoid unnecessary reallocations */
+	for (p = heads, num_head = 0; p; p = p->next)
+		num_head++;
+	other = xcalloc(sizeof(*other), num_head);
+
+	/* For each commit, see if it can be reached by others */
+	for (p = heads; p; p = p->next) {
+		struct commit_list *q, *base;
+
+		num_other = 0;
+		for (q = heads; q; q = q->next) {
+			if (p == q)
+				continue;
+			other[num_other++] = q->item;
+		}
+		if (num_other) {
+			base = get_merge_bases_many(p->item, num_other, other, 1);
+		} else
+			base = NULL;
+		/*
+		 * If p->item does not have anything common with other
+		 * commits, there won't be any merge base.  If it is
+		 * reachable from some of the others, p->item will be
+		 * the merge base.  If its history is connected with
+		 * others, but p->item is not reachable by others, we
+		 * will get something other than p->item back.
+		 */
+		if (!base || (base->item != p->item))
+			tail = &(commit_list_insert(p->item, tail)->next);
+		free_commit_list(base);
+	}
+	free(other);
+	return result;
+}
diff --git a/commit.h b/commit.h
index dcec7fb..2acfc79 100644
--- a/commit.h
+++ b/commit.h
@@ -140,4 +140,6 @@ static inline int single_parent(struct commit *commit)
 	return commit->parents && !commit->parents->next;
 }
 
+struct commit_list *reduce_heads(struct commit_list *heads);
+
 #endif /* COMMIT_H */
-- 
1.5.6

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf()
  2008-06-27 16:22                   ` [PATCH 10/15] Introduce reduce_heads() Miklos Vajna
@ 2008-06-27 16:22                     ` Miklos Vajna
  2008-06-27 16:22                       ` [PATCH 12/15] strbuf_vaddf(): support %*s, too Miklos Vajna
  2008-06-28  2:00                       ` [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf() Junio C Hamano
  0 siblings, 2 replies; 81+ messages in thread
From: Miklos Vajna @ 2008-06-27 16:22 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Johannes Schindelin, Olivier Marin, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

The most common use of addf() was to init a strbuf and addf() right away.
Since it is so common, it makes sense to have a function strbuf_initf() to
wrap both calls into one.

To do that, we implement a (really minimal) vaddf() lookalike to
vsprintf().

At the moment, it only handles %u, %i, %d, %l, %o, %x and %X with size
indicators '<number>', ' <number>' and '0<number>', as well as %c and %s,
the latter with size indicators '.*' in addition to the same size
indicators as for numbers.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .gitignore       |    1 +
 Makefile         |    4 +-
 strbuf.c         |  134 +++++++++++++++++++++++++++++++++++++++++++++++------
 strbuf.h         |    3 +
 t/t0000-basic.sh |    8 +++
 test-strbuf.c    |   17 +++++++
 6 files changed, 150 insertions(+), 17 deletions(-)
 create mode 100644 test-strbuf.c

diff --git a/.gitignore b/.gitignore
index 4ff2fec..9281395 100644
--- a/.gitignore
+++ b/.gitignore
@@ -151,6 +151,7 @@ test-genrandom
 test-match-trees
 test-parse-options
 test-sha1
+test-strbuf
 common-cmds.h
 *.tar.gz
 *.dsc
diff --git a/Makefile b/Makefile
index 314339d..1b43171 100644
--- a/Makefile
+++ b/Makefile
@@ -1230,7 +1230,7 @@ endif
 
 ### Testing rules
 
-TEST_PROGRAMS = test-chmtime$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X test-absolute-path$X test-parse-options$X
+TEST_PROGRAMS = test-chmtime$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X test-absolute-path$X test-parse-options$X test-strbuf$X
 
 all:: $(TEST_PROGRAMS)
 
@@ -1249,6 +1249,8 @@ test-delta$X: diff-delta.o patch-delta.o
 
 test-parse-options$X: parse-options.o
 
+test-strbuf$X: strbuf.o
+
 .PRECIOUS: $(patsubst test-%$X,test-%.o,$(TEST_PROGRAMS))
 
 test-%$X: test-%.o $(GITLIBS)
diff --git a/strbuf.c b/strbuf.c
index 4aed752..72b3585 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -122,28 +122,130 @@ void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len)
 	strbuf_setlen(sb, sb->len + len);
 }
 
+static int number_length(unsigned long number, long base)
+{
+	int length = 1;
+	while (number >= base) {
+		number /= base;
+		length++;
+	}
+	return length;
+}
+
+/*
+ * Only supports %u, %i, %d, %l, %o, %x and %X with size indicators
+ *   '<number>', '0<number>', and ' <number>',
+ * as well as %c,
+ * and %s with size indicators '<number>', ' <number>' and '.*'.
+ */
+void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap)
+{
+	while (*fmt) {
+		char fill = '\0';
+		int size = -1, max_size = -1;
+		char *p = (char *)fmt;
+
+		if (*p != '%' || *(++p) == '%') {
+			strbuf_addch(sb, *p++);
+			fmt = p;
+			continue;
+		}
+		if (*p == ' ' || *p == '0')
+			fill = *p++;
+		if (isdigit(*p))
+			size = (int)strtol(p, &p, 10);
+		else if (!prefixcmp(p, ".*")) {
+			max_size = va_arg(ap, int);
+			p += 2;
+		}
+		switch (*p) {
+		case 's': {
+			const char *s = va_arg(ap, const char *);
+			if (fill) {
+				int len = size - strlen(s);
+				while (len-- > 0)
+					strbuf_addch(sb, fill);
+			}
+			while (*s && max_size--)
+				strbuf_addch(sb, *s++);
+			break;
+		}
+		case 'c':
+			strbuf_addch(sb, va_arg(ap, int));
+			break;
+		case 'u':
+		case 'i':
+		case 'l':
+		case 'd':
+		case 'o':
+		case 'x':
+		case 'X': {
+			int base = *p == 'x' || *p == 'X' ? 16 :
+				*p == 'o' ? 8 : 10;
+			int negative = 0, len;
+			unsigned long number, power;
+
+			if (*p == 'u')
+				number = va_arg(ap, unsigned int);
+			else {
+				long signed_number;
+				if (*p == 'l')
+					signed_number = va_arg(ap, long);
+				else
+					signed_number = va_arg(ap, int);
+				if (signed_number < 0) {
+					negative = 1;
+					number = -signed_number;
+				} else
+					number = signed_number;
+			}
+
+			/* pad */
+			len = number_length(number, base);
+			while (size-- > len + negative)
+				strbuf_addch(sb, fill ? fill : ' ');
+			if (negative)
+				strbuf_addch(sb, '-');
+
+			/* output number */
+			power = 1;
+			while (len-- > 1)
+				power *= base;
+			while (power) {
+				int digit = number / power;
+				strbuf_addch(sb, digit < 10 ? '0' + digit
+					: *p + 'A' - 'X' + digit - 10);
+				number -= digit * power;
+				power /= base;
+			}
+
+			break;
+		}
+		default:
+			/* unknown / invalid format: copy verbatim */
+			strbuf_insert(sb, sb->len, fmt, p - fmt + 1);
+		}
+		fmt = p + (*p != '\0');
+	}
+}
+
 void strbuf_addf(struct strbuf *sb, const char *fmt, ...)
 {
-	int len;
 	va_list ap;
 
-	if (!strbuf_avail(sb))
-		strbuf_grow(sb, 64);
 	va_start(ap, fmt);
-	len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap);
+	strbuf_vaddf(sb, fmt, ap);
+	va_end(ap);
+}
+
+void strbuf_initf(struct strbuf *sb, const char *fmt, ...)
+{
+	va_list ap;
+
+	strbuf_init(sb, strlen(fmt) + 64);
+	va_start(ap, fmt);
+	strbuf_vaddf(sb, fmt, ap);
 	va_end(ap);
-	if (len < 0)
-		die("your vsnprintf is broken");
-	if (len > strbuf_avail(sb)) {
-		strbuf_grow(sb, len);
-		va_start(ap, fmt);
-		len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap);
-		va_end(ap);
-		if (len > strbuf_avail(sb)) {
-			die("this should not happen, your snprintf is broken");
-		}
-	}
-	strbuf_setlen(sb, sb->len + len);
 }
 
 void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn,
diff --git a/strbuf.h b/strbuf.h
index faec229..d7c7aaf 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -106,8 +106,11 @@ extern void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len);
 typedef size_t (*expand_fn_t) (struct strbuf *sb, const char *placeholder, void *context);
 extern void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn, void *context);
 
+extern void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list args);
 __attribute__((format(printf,2,3)))
 extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
+__attribute__((format(printf,2,3)))
+extern void strbuf_initf(struct strbuf *sb, const char *fmt, ...);
 
 extern size_t strbuf_fread(struct strbuf *, size_t, FILE *);
 /* XXX: if read fails, any partial read is undone */
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
index 690f80a..7c7ca4c 100755
--- a/t/t0000-basic.sh
+++ b/t/t0000-basic.sh
@@ -311,6 +311,14 @@ test_expect_success 'absolute path works as expected' '
 	test "$sym" = "$(test-absolute-path "$dir2/syml")"
 '
 
+test_expect_success 'strbuf_initf() works as expected' '
+
+	eval $(test-strbuf) &&
+	test ! -z "$result" &&
+	test "$result" = "$expect"
+
+'
+
 test_expect_success 'very long name in the index handled sanely' '
 
 	a=a && # 1
diff --git a/test-strbuf.c b/test-strbuf.c
new file mode 100644
index 0000000..479fa08
--- /dev/null
+++ b/test-strbuf.c
@@ -0,0 +1,17 @@
+#include "cache.h"
+#include "strbuf.h"
+
+int main(int argc, char **argv)
+{
+	struct strbuf buf;
+#define TEST_FORMAT \
+	"'%%%.*s,%x,%05X,%u,%i,% 4d,%3d,%c,%3d'", \
+	5, "Hello, World!", 27, 27, -1, -1, 1, 5, ':', 1234
+
+	strbuf_initf(&buf, TEST_FORMAT);
+	printf("result=%s\n", buf.buf);
+	printf("expect=" TEST_FORMAT);
+	strbuf_release(&buf);
+
+	return 0;
+}
-- 
1.5.6

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH 12/15] strbuf_vaddf(): support %*s, too
  2008-06-27 16:22                     ` [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf() Miklos Vajna
@ 2008-06-27 16:22                       ` Miklos Vajna
  2008-06-27 16:22                         ` [PATCH 13/15] Add new test case to ensure git-merge reduces octopus parents when possible Miklos Vajna
  2008-06-28  2:00                       ` [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf() Junio C Hamano
  1 sibling, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-06-27 16:22 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Johannes Schindelin, Olivier Marin, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

The recent addition of graph.c broke things with our custom implementation
of strbuf_vaddf(), because "%*s" was not yet supported (because it did not
have any users before).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 strbuf.c |    6 ++++++
 1 files changed, 6 insertions(+), 0 deletions(-)

diff --git a/strbuf.c b/strbuf.c
index 72b3585..bd5dc80 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -154,6 +154,12 @@ void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap)
 			fill = *p++;
 		if (isdigit(*p))
 			size = (int)strtol(p, &p, 10);
+		else if (*p == '*') {
+			size = va_arg(ap, int);
+			if (!fill)
+				fill = ' ';
+			p++;
+		}
 		else if (!prefixcmp(p, ".*")) {
 			max_size = va_arg(ap, int);
 			p += 2;
-- 
1.5.6

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH 13/15] Add new test case to ensure git-merge reduces octopus parents when possible
  2008-06-27 16:22                       ` [PATCH 12/15] strbuf_vaddf(): support %*s, too Miklos Vajna
@ 2008-06-27 16:22                         ` Miklos Vajna
  2008-06-27 16:22                           ` [PATCH 14/15] Add new test case to ensure git-merge prepends the custom merge message Miklos Vajna
  0 siblings, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-06-27 16:22 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin, Junio C Hamano

The old shell version used show-branch --independent to filter for the
ones that cannot be reached from any other reference.

The new C version uses reduce_heads() from commit.c for this, so
add test to ensure it works as expected.

Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 t/t7603-merge-reduce-heads.sh |   63 +++++++++++++++++++++++++++++++++++++++++
 1 files changed, 63 insertions(+), 0 deletions(-)
 create mode 100755 t/t7603-merge-reduce-heads.sh

diff --git a/t/t7603-merge-reduce-heads.sh b/t/t7603-merge-reduce-heads.sh
new file mode 100755
index 0000000..17b19dc
--- /dev/null
+++ b/t/t7603-merge-reduce-heads.sh
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+test_description='git-merge
+
+Testing octopus merge when reducing parents to independent branches.'
+
+. ./test-lib.sh
+
+# 0 - 1
+#   \ 2
+#   \ 3
+#   \ 4 - 5
+#
+# So 1, 2, 3 and 5 should be kept, 4 should be avoided.
+
+test_expect_success 'setup' '
+	echo c0 > c0.c &&
+	git add c0.c &&
+	git commit -m c0 &&
+	git tag c0 &&
+	echo c1 > c1.c &&
+	git add c1.c &&
+	git commit -m c1 &&
+	git tag c1 &&
+	git reset --hard c0 &&
+	echo c2 > c2.c &&
+	git add c2.c &&
+	git commit -m c2 &&
+	git tag c2 &&
+	git reset --hard c0 &&
+	echo c3 > c3.c &&
+	git add c3.c &&
+	git commit -m c3 &&
+	git tag c3 &&
+	git reset --hard c0 &&
+	echo c4 > c4.c &&
+	git add c4.c &&
+	git commit -m c4 &&
+	git tag c4 &&
+	echo c5 > c5.c &&
+	git add c5.c &&
+	git commit -m c5 &&
+	git tag c5
+'
+
+test_expect_success 'merge c1 with c2, c3, c4, c5' '
+	git reset --hard c1 &&
+	git merge c2 c3 c4 c5 &&
+	test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
+	test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
+	test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
+	test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
+	test "$(git rev-parse c5)" = "$(git rev-parse HEAD^4)" &&
+	git diff --exit-code &&
+	test -f c0.c &&
+	test -f c1.c &&
+	test -f c2.c &&
+	test -f c3.c &&
+	test -f c4.c &&
+	test -f c5.c
+'
+
+test_done
-- 
1.5.6

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH 14/15] Add new test case to ensure git-merge prepends the custom merge message
  2008-06-27 16:22                         ` [PATCH 13/15] Add new test case to ensure git-merge reduces octopus parents when possible Miklos Vajna
@ 2008-06-27 16:22                           ` Miklos Vajna
  2008-06-27 16:22                             ` [PATCH 15/15] Build in merge Miklos Vajna
  0 siblings, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-06-27 16:22 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

There was no test for this before, so the testsuite passed, even in case
the merge summary was missing from the merge commit message.

Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---
 t/t7604-merge-custom-message.sh |   37 +++++++++++++++++++++++++++++++++++++
 1 files changed, 37 insertions(+), 0 deletions(-)
 create mode 100755 t/t7604-merge-custom-message.sh

diff --git a/t/t7604-merge-custom-message.sh b/t/t7604-merge-custom-message.sh
new file mode 100755
index 0000000..6081677
--- /dev/null
+++ b/t/t7604-merge-custom-message.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+test_description='git-merge
+
+Testing merge when using a custom message for the merge commit.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	echo c0 > c0.c &&
+	git add c0.c &&
+	git commit -m c0 &&
+	git tag c0 &&
+	echo c1 > c1.c &&
+	git add c1.c &&
+	git commit -m c1 &&
+	git tag c1 &&
+	git reset --hard c0 &&
+	echo c2 > c2.c &&
+	git add c2.c &&
+	git commit -m c2 &&
+	git tag c2
+'
+
+cat >expected <<\EOF
+custom message
+
+Merge commit 'c2'
+EOF
+test_expect_success 'merge c2 with a custom message' '
+	git reset --hard c1 &&
+	git merge -m "custom message" c2 &&
+	git cat-file commit HEAD | sed -e "1,/^$/d" > actual &&
+	test_cmp expected actual
+'
+
+test_done
-- 
1.5.6

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH 15/15] Build in merge
  2008-06-27 16:22                           ` [PATCH 14/15] Add new test case to ensure git-merge prepends the custom merge message Miklos Vajna
@ 2008-06-27 16:22                             ` Miklos Vajna
  2008-06-27 17:09                               ` Miklos Vajna
  0 siblings, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-06-27 16:22 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---
 Makefile                                      |    2 +-
 builtin-merge.c                               | 1143 +++++++++++++++++++++++++
 builtin.h                                     |    1 +
 git-merge.sh => contrib/examples/git-merge.sh |    0 
 git.c                                         |    1 +
 t/t7602-merge-octopus-many.sh                 |    2 +-
 6 files changed, 1147 insertions(+), 2 deletions(-)
 create mode 100644 builtin-merge.c
 rename git-merge.sh => contrib/examples/git-merge.sh (100%)

diff --git a/Makefile b/Makefile
index 1b43171..38135cd 100644
--- a/Makefile
+++ b/Makefile
@@ -240,7 +240,6 @@ SCRIPT_SH += git-lost-found.sh
 SCRIPT_SH += git-merge-octopus.sh
 SCRIPT_SH += git-merge-one-file.sh
 SCRIPT_SH += git-merge-resolve.sh
-SCRIPT_SH += git-merge.sh
 SCRIPT_SH += git-merge-stupid.sh
 SCRIPT_SH += git-mergetool.sh
 SCRIPT_SH += git-parse-remote.sh
@@ -512,6 +511,7 @@ BUILTIN_OBJS += builtin-ls-remote.o
 BUILTIN_OBJS += builtin-ls-tree.o
 BUILTIN_OBJS += builtin-mailinfo.o
 BUILTIN_OBJS += builtin-mailsplit.o
+BUILTIN_OBJS += builtin-merge.o
 BUILTIN_OBJS += builtin-merge-base.o
 BUILTIN_OBJS += builtin-merge-file.o
 BUILTIN_OBJS += builtin-merge-ours.o
diff --git a/builtin-merge.c b/builtin-merge.c
new file mode 100644
index 0000000..0d56dca
--- /dev/null
+++ b/builtin-merge.c
@@ -0,0 +1,1143 @@
+/*
+ * Builtin "git merge"
+ *
+ * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
+ *
+ * Based on git-merge.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "parse-options.h"
+#include "builtin.h"
+#include "run-command.h"
+#include "path-list.h"
+#include "diff.h"
+#include "refs.h"
+#include "commit.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
+#include "dir.h"
+#include "utf8.h"
+#include "log-tree.h"
+#include "color.h"
+
+enum strategy {
+	DEFAULT_TWOHEAD = 1,
+	DEFAULT_OCTOPUS = 2,
+	NO_FAST_FORWARD = 4,
+	NO_TRIVIAL = 8
+};
+
+static const char * const builtin_merge_usage[] = {
+	"git-merge [options] <remote>...",
+	"git-merge [options] <msg> HEAD <remote>",
+	NULL
+};
+
+static int show_diffstat = 1, option_log, squash;
+static int option_commit = 1, allow_fast_forward = 1;
+static int allow_trivial = 1, have_message;
+static struct strbuf merge_msg;
+static struct commit_list *remoteheads;
+static unsigned char head[20];
+static struct path_list use_strategies;
+static const char *branch;
+
+static struct path_list_item strategy_items[] = {
+	{ "recur",      (void *)NO_TRIVIAL },
+	{ "recursive",  (void *)(DEFAULT_TWOHEAD | NO_TRIVIAL) },
+	{ "octopus",    (void *)DEFAULT_OCTOPUS },
+	{ "resolve",    (void *)0 },
+	{ "stupid",     (void *)0 },
+	{ "ours",       (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
+	{ "subtree",    (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
+};
+static struct path_list strategies = { strategy_items,
+	ARRAY_SIZE(strategy_items), 0, 0 };
+
+static const char *pull_twohead, *pull_octopus;
+
+static int option_parse_message(const struct option *opt,
+	const char *arg, int unset)
+{
+	struct strbuf *buf = opt->value;
+
+	if (unset)
+		strbuf_setlen(buf, 0);
+	else {
+		strbuf_addf(buf, "%s\n\n", arg);
+		have_message = 1;
+	}
+	return 0;
+}
+
+static struct path_list_item *unsorted_path_list_lookup(const char *path,
+	struct path_list *list)
+{
+	int i;
+
+	if (!path)
+		return NULL;
+
+	for (i = 0; i < list->nr; i++)
+		if (!strcmp(path, list->items[i].path))
+			return &list->items[i];
+	return NULL;
+}
+
+static inline void path_list_append_strategy(
+		struct path_list_item *item)
+{
+	path_list_append(item->path, &use_strategies)->util = item->util;
+}
+
+static int option_parse_strategy(const struct option *opt,
+	const char *arg, int unset)
+{
+	int i;
+	struct path_list_item *item =
+		unsorted_path_list_lookup(arg, &strategies);
+
+	if (unset)
+		return 0;
+
+	if (item)
+		path_list_append_strategy(item);
+	else {
+		struct strbuf err;
+		strbuf_init(&err, 0);
+		for (i = 0; i < strategies.nr; i++)
+			strbuf_addf(&err, " %s", strategies.items[i].path);
+		fprintf(stderr, "Could not find merge strategy '%s'.\n", arg);
+		fprintf(stderr, "Available strategies are:%s.\n", err.buf);
+		exit(1);
+	}
+	return 0;
+}
+
+static int option_parse_n(const struct option *opt,
+		const char *arg, int unset)
+{
+	show_diffstat = unset;
+	return 0;
+}
+
+static struct option builtin_merge_options[] = {
+	{ OPTION_CALLBACK, 'n', NULL, NULL, NULL,
+		"do not show a diffstat at the end of the merge",
+		PARSE_OPT_NOARG, option_parse_n },
+	OPT_BOOLEAN(0, "stat", &show_diffstat,
+		"show a diffstat at the end of the merge"),
+	OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
+	OPT_BOOLEAN(0, "log", &option_log,
+		"add list of one-line log to merge commit message"),
+	OPT_BOOLEAN(0, "squash", &squash,
+		"create a single commit instead of doing a merge"),
+	OPT_BOOLEAN(0, "commit", &option_commit,
+		"perform a commit if the merge succeeds (default)"),
+	OPT_BOOLEAN(0, "ff", &allow_fast_forward,
+		"allow fast forward (default)"),
+	OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
+		"merge strategy to use", option_parse_strategy),
+	OPT_CALLBACK('m', "message", &merge_msg, "message",
+		"message to be used for the merge commit (if any)",
+		option_parse_message),
+	OPT_END()
+};
+
+/* Cleans up metadata that is uninteresting after a succeeded merge. */
+static void dropsave(void)
+{
+	unlink(git_path("MERGE_HEAD"));
+	unlink(git_path("MERGE_MSG"));
+	unlink(git_path("MERGE_STASH"));
+}
+
+static void save_state(void)
+{
+	int fd;
+	struct child_process stash;
+	const char *argv[] = {"stash", "create", NULL};
+
+	fd = open(git_path("MERGE_STASH"), O_WRONLY | O_CREAT, 0666);
+	if (fd < 0)
+		die("Could not write to %s", git_path("MERGE_STASH"));
+	memset(&stash, 0, sizeof(stash));
+	stash.argv = argv;
+	stash.out = fd;
+	stash.git_cmd = 1;
+	run_command(&stash);
+}
+
+static void reset_hard(unsigned const char *sha1, int verbose)
+{
+	struct tree *tree;
+	struct unpack_trees_options opts;
+	struct tree_desc t;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.head_idx = -1;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.reset = 1;
+	if (verbose)
+		opts.verbose_update = 1;
+
+	tree = parse_tree_indirect(sha1);
+	if (!tree)
+		die("failed to unpack %s tree object", sha1_to_hex(sha1));
+	parse_tree(tree);
+	init_tree_desc(&t, tree->buffer, tree->size);
+	if (unpack_trees(1, &t, &opts))
+		exit(128); /* We've already reported the error, finish dying */
+}
+
+static void restore_state(void)
+{
+	struct strbuf sb;
+	const char *args[] = { "stash", "apply", NULL, NULL };
+
+	if (access(git_path("MERGE_STASH"), R_OK) < 0)
+		return;
+
+	reset_hard(head, 1);
+
+	strbuf_init(&sb, 0);
+	if (strbuf_read_file(&sb, git_path("MERGE_STASH"), 0) < 0)
+		die("could not read MERGE_STASH: %s", strerror(errno));
+	args[2] = sb.buf;
+
+	/*
+	 * It is OK to ignore error here, for example when there was
+	 * nothing to restore.
+	 */
+	run_command_v_opt(args, RUN_GIT_CMD);
+
+	strbuf_release(&sb);
+	refresh_cache(REFRESH_QUIET);
+}
+
+/* This is called when no merge was necessary. */
+static void finish_up_to_date(const char *msg)
+{
+	printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
+	dropsave();
+}
+
+static void squash_message(void)
+{
+	struct rev_info rev;
+	struct commit *commit;
+	struct strbuf out;
+	struct commit_list *j;
+	int fd;
+
+	printf("Squash commit -- not updating HEAD\n");
+	fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+	if (fd < 0)
+		die("Could not write to %s", git_path("SQUASH_MSG"));
+
+	init_revisions(&rev, NULL);
+	rev.ignore_merges = 1;
+	rev.commit_format = CMIT_FMT_MEDIUM;
+
+	commit = lookup_commit(head);
+	commit->object.flags |= UNINTERESTING;
+	add_pending_object(&rev, &commit->object, NULL);
+
+	for (j = remoteheads; j; j = j->next)
+		add_pending_object(&rev, &j->item->object, NULL);
+
+	setup_revisions(0, NULL, &rev, NULL);
+	if (prepare_revision_walk(&rev))
+		die("revision walk setup failed");
+
+	strbuf_initf(&out, "Squashed commit of the following:\n");
+	while ((commit = get_revision(&rev)) != NULL) {
+		strbuf_addch(&out, '\n');
+		strbuf_addf(&out, "commit %s\n",
+			sha1_to_hex(commit->object.sha1));
+		pretty_print_commit(rev.commit_format, commit, &out, rev.abbrev,
+			NULL, NULL, rev.date_mode, 0);
+	}
+	write(fd, out.buf, out.len);
+	close(fd);
+	strbuf_release(&out);
+}
+
+static int run_hook(const char *name)
+{
+	struct child_process hook;
+	const char *argv[3], *env[2];
+	char index[PATH_MAX];
+
+	argv[0] = git_path("hooks/%s", name);
+	if (access(argv[0], X_OK) < 0)
+		return 0;
+
+	snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", get_index_file());
+	env[0] = index;
+	env[1] = NULL;
+
+	if (squash)
+		argv[1] = "1";
+	else
+		argv[1] = "0";
+	argv[2] = NULL;
+
+	memset(&hook, 0, sizeof(hook));
+	hook.argv = argv;
+	hook.no_stdin = 1;
+	hook.stdout_to_stderr = 1;
+	hook.env = env;
+
+	return run_command(&hook);
+}
+
+static void finish(const unsigned char *new_head, const char *msg)
+{
+	struct strbuf reflog_message;
+
+	if (!msg)
+		strbuf_initf(&reflog_message, "%s",
+			getenv("GIT_REFLOG_ACTION"));
+	else {
+		printf("%s\n", msg);
+		strbuf_initf(&reflog_message, "%s: %s",
+			getenv("GIT_REFLOG_ACTION"), msg);
+	}
+	if (squash) {
+		squash_message();
+	} else {
+		if (!merge_msg.len)
+			printf("No merge message -- not updating HEAD\n");
+		else {
+			const char *argv_gc_auto[] = { "gc", "--auto", NULL };
+			update_ref(reflog_message.buf, "HEAD",
+				new_head, head, 0,
+				DIE_ON_ERR);
+			/*
+			 * We ignore errors in 'gc --auto', since the
+			 * user should see them.
+			 */
+			run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+		}
+	}
+	if (new_head && show_diffstat) {
+		struct diff_options opts;
+		diff_setup(&opts);
+		opts.output_format |=
+			DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+		opts.detect_rename = DIFF_DETECT_RENAME;
+		if (diff_use_color_default > 0)
+			DIFF_OPT_SET(&opts, COLOR_DIFF);
+		diff_tree_sha1(head, new_head, "", &opts);
+		diffcore_std(&opts);
+		diff_flush(&opts);
+	}
+
+	/* Run a post-merge hook */
+	run_hook("post-merge");
+
+	strbuf_release(&reflog_message);
+}
+
+/* Get the name for the merge commit's message. */
+static void merge_name(const char *remote, struct strbuf *msg)
+{
+	struct object *remote_head;
+	unsigned char branch_head[20], buf_sha[20];
+	struct strbuf buf;
+	char *ptr;
+	int len = 0;
+
+	memset(branch_head, 0, sizeof(branch_head));
+	remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+	if (!remote_head)
+		return;
+
+	strbuf_initf(&buf, "refs/heads/%s", remote);
+	get_sha1(buf.buf, branch_head);
+
+	if (!hashcmp(remote_head->sha1, branch_head)) {
+		strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
+			sha1_to_hex(branch_head), remote);
+		return;
+	}
+	/* See if remote matches <name>~<number>, or <name>^ */
+	ptr = strrchr(remote, '^');
+	if (ptr && ptr[1] == '\0')
+		len = ptr-remote;
+	else {
+		ptr = strrchr(remote, '~');
+		if (ptr && ptr[1] != '0' && isdigit(ptr[1])) {
+			len = ptr-remote;
+			ptr++;
+			for (ptr++; *ptr; ptr++)
+				if (!isdigit(*ptr)) {
+					len = 0;
+					break;
+				}
+		}
+	}
+	if (len) {
+		struct strbuf truname = STRBUF_INIT;
+		strbuf_addstr(&truname, remote);
+		strbuf_setlen(&truname, len);
+		if (!get_sha1(truname.buf, buf_sha)) {
+			strbuf_addf(msg,
+				"%s\t\tbranch '%s' (early part) of .\n",
+				sha1_to_hex(remote_head->sha1), truname.buf);
+			return;
+		}
+	}
+
+	if (!strcmp(remote, "FETCH_HEAD") &&
+			!access(git_path("FETCH_HEAD"), R_OK)) {
+		FILE *fp;
+		struct strbuf line;
+		char *ptr;
+
+		strbuf_init(&line, 0);
+		fp = fopen(git_path("FETCH_HEAD"), "r");
+		if (!fp)
+			die("could not open %s for reading: %s",
+				git_path("FETCH_HEAD"), strerror(errno));
+		strbuf_getline(&line, fp, '\n');
+		fclose(fp);
+		ptr = strstr(line.buf, "\tnot-for-merge\t");
+		if (ptr)
+			strbuf_remove(&line, ptr-line.buf+1, 13);
+		strbuf_addbuf(msg, &line);
+		strbuf_release(&line);
+		return;
+	}
+	strbuf_addf(msg, "%s\t\tcommit '%s'\n",
+		sha1_to_hex(remote_head->sha1), remote);
+}
+
+int git_merge_config(const char *k, const char *v, void *cb)
+{
+	if (branch && !prefixcmp(k, "branch.") &&
+		!prefixcmp(k + 7, branch) &&
+		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+		const char **argv;
+		int argc;
+		char *buf;
+
+		buf = xstrdup(v);
+		argc = split_cmdline(buf, &argv);
+		argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+		memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+		argc++;
+		parse_options(argc, argv, builtin_merge_options,
+			      builtin_merge_usage, 0);
+		free(buf);
+	}
+
+	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+		show_diffstat = git_config_bool(k, v);
+	else if (!strcmp(k, "pull.twohead"))
+		return git_config_string(&pull_twohead, k, v);
+	else if (!strcmp(k, "pull.octopus"))
+		return git_config_string(&pull_octopus, k, v);
+	return 0;
+}
+
+static int read_tree_trivial(unsigned char *common, unsigned char *head,
+	unsigned char *one)
+{
+	int i, nr_trees = 0;
+	struct tree *trees[MAX_UNPACK_TREES];
+	struct tree_desc t[MAX_UNPACK_TREES];
+	struct unpack_trees_options opts;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.head_idx = -1;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.verbose_update = 1;
+	opts.trivial_merges_only = 1;
+	opts.merge = 1;
+	trees[nr_trees] = parse_tree_indirect(common);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(head);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(one);
+	if (!trees[nr_trees++])
+		return -1;
+	opts.fn = threeway_merge;
+	cache_tree_free(&active_cache_tree);
+	opts.head_idx = 2;
+	for (i = 0; i < nr_trees; i++) {
+		parse_tree(trees[i]);
+		init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+	}
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+	return 0;
+}
+
+static int commit_tree_trivial(const char *msg, unsigned const char *tree,
+		struct commit_list *parents, unsigned char *ret)
+{
+	struct commit_list *i;
+	struct strbuf buf;
+	int encoding_is_utf8;
+
+	/* Not having i18n.commitencoding is the same as having utf-8 */
+	encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
+
+	strbuf_init(&buf, 8192); /* should avoid reallocs for the headers */
+	strbuf_addf(&buf, "tree %s\n", sha1_to_hex(tree));
+
+	for (i = parents; i; i = i->next)
+		strbuf_addf(&buf, "parent %s\n",
+			sha1_to_hex(i->item->object.sha1));
+
+	/* Person/date information */
+	strbuf_addf(&buf, "author %s\n",
+		git_author_info(IDENT_ERROR_ON_NO_NAME));
+	strbuf_addf(&buf, "committer %s\n",
+		git_committer_info(IDENT_ERROR_ON_NO_NAME));
+	if (!encoding_is_utf8)
+		strbuf_addf(&buf, "encoding %s\n", git_commit_encoding);
+	strbuf_addch(&buf, '\n');
+
+	/* And add the comment */
+	strbuf_addstr(&buf, msg);
+
+	write_sha1_file(buf.buf, buf.len, commit_type, ret);
+	strbuf_release(&buf);
+	return *ret;
+}
+
+static void write_tree_trivial(unsigned char *sha1)
+{
+	if (write_cache_as_tree(sha1, 0, NULL))
+		die("git write-tree failed to write a tree");
+}
+
+static int try_merge_strategy(char *strategy, struct commit_list *common,
+	struct strbuf *head_arg)
+{
+	const char **args;
+	int i = 0, ret;
+	struct commit_list *j;
+	struct strbuf buf;
+
+	args = xmalloc((4 + commit_list_count(common) +
+			commit_list_count(remoteheads)) * sizeof(char *));
+	strbuf_initf(&buf, "merge-%s", strategy);
+	args[i++] = buf.buf;
+	for (j = common; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i++] = "--";
+	args[i++] = head_arg->buf;
+	for (j = remoteheads; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i] = NULL;
+	ret = run_command_v_opt(args, RUN_GIT_CMD);
+	strbuf_release(&buf);
+	i = 1;
+	for (j = common; j; j = j->next)
+		free((void *)args[i++]);
+	i += 2;
+	for (j = remoteheads; j; j = j->next)
+		free((void *)args[i++]);
+	free(args);
+	return -ret;
+}
+
+static void count_diff_files(struct diff_queue_struct *q,
+		struct diff_options *opt, void *data)
+{
+	int *count = data;
+
+	(*count) += q->nr;
+}
+
+static int count_unmerged_entries(void)
+{
+	const struct index_state *state = &the_index;
+	int i, ret = 0;
+
+	for (i = 0; i < state->cache_nr; i++)
+		if (ce_stage(state->cache[i]))
+			ret++;
+
+	return ret;
+}
+
+static int merge_one_remote(unsigned char *head, unsigned char *remote)
+{
+	struct tree *trees[MAX_UNPACK_TREES];
+	struct unpack_trees_options opts;
+	struct tree_desc t[MAX_UNPACK_TREES];
+	int i, fd, nr_trees = 0;
+	struct dir_struct dir;
+	struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+	if (read_cache_unmerged())
+		die("you need to resolve your current index first");
+
+	fd = hold_locked_index(lock_file, 1);
+
+	memset(&trees, 0, sizeof(trees));
+	memset(&opts, 0, sizeof(opts));
+	memset(&t, 0, sizeof(t));
+	dir.show_ignored = 1;
+	dir.exclude_per_dir = ".gitignore";
+	opts.dir = &dir;
+
+	opts.head_idx = 1;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.verbose_update = 1;
+	opts.merge = 1;
+	opts.fn = twoway_merge;
+
+	trees[nr_trees] = parse_tree_indirect(head);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(remote);
+	if (!trees[nr_trees++])
+		return -1;
+	for (i = 0; i < nr_trees; i++) {
+		parse_tree(trees[i]);
+		init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+	}
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+	if (write_cache(fd, active_cache, active_nr) ||
+		commit_locked_index(lock_file))
+		die("unable to write new index file");
+	return 0;
+}
+
+static void split_merge_strategies(const char *string, struct path_list *list)
+{
+	char *p, *q, *buf;
+
+	if (!string)
+		return;
+
+	list->strdup_paths = 1;
+	buf = xstrdup(string);
+	q = buf;
+	for (;;) {
+		p = strchr(q, ' ');
+		if (!p) {
+			path_list_append(q, list);
+			free(buf);
+			return;
+		} else {
+			*p = '\0';
+			path_list_append(q, list);
+			q = ++p;
+		}
+	}
+}
+
+static void add_strategies(const char *string, enum strategy strategy)
+{
+	struct path_list list;
+	int i;
+
+	memset(&list, 0, sizeof(list));
+	split_merge_strategies(string, &list);
+	if (list.nr) {
+		for (i = 0; i < list.nr; i++) {
+			struct path_list_item *item;
+
+			item = unsorted_path_list_lookup(list.items[i].path,
+				&strategies);
+			if (item)
+				path_list_append_strategy(item);
+		}
+		return;
+	}
+	for (i = 0; i < strategies.nr; i++)
+		if ((enum strategy)strategies.items[i].util & strategy)
+			path_list_append_strategy(&strategies.items[i]);
+}
+
+int cmd_merge(int argc, const char **argv, const char *prefix)
+{
+	unsigned char sha1[20], result_tree[20];
+	struct object *second_token = NULL;
+	struct strbuf buf, head_arg;
+	int flag, head_invalid, i, single_strategy;
+	int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
+	struct commit_list *common = NULL;
+	struct path_list_item *best_strategy = NULL, *wt_strategy = NULL;
+	struct commit_list **remotes = &remoteheads;
+
+	setup_work_tree();
+	if (unmerged_cache())
+		die("You are in the middle of a conflicted merge.");
+
+	/*
+	 * Check if we are _not_ on a detached HEAD, i.e. if there is a
+	 * current branch.
+	 */
+	branch = resolve_ref("HEAD", sha1, 0, &flag);
+	if (branch && flag & REF_ISSYMREF) {
+		const char *ptr = skip_prefix(branch, "refs/heads/");
+		if (ptr)
+			branch = ptr;
+	}
+
+	git_config(git_merge_config, NULL);
+
+	/* for color.diff and diff.color */
+	git_config(git_diff_ui_config, NULL);
+
+	/* for color.ui */
+	if (diff_use_color_default == -1)
+		diff_use_color_default = git_use_color_default;
+
+	argc = parse_options(argc, argv, builtin_merge_options,
+			builtin_merge_usage, 0);
+
+	if (squash) {
+		if (!allow_fast_forward)
+			die("You cannot combine --squash with --no-ff.");
+		option_commit = 0;
+	}
+
+	if (!argc)
+		usage_with_options(builtin_merge_usage,
+			builtin_merge_options);
+
+	/*
+	 * This could be traditional "merge <msg> HEAD <commit>..."  and
+	 * the way we can tell it is to see if the second token is HEAD,
+	 * but some people might have misused the interface and used a
+	 * committish that is the same as HEAD there instead.
+	 * Traditional format never would have "-m" so it is an
+	 * additional safety measure to check for it.
+	 */
+	strbuf_init(&buf, 0);
+	strbuf_init(&head_arg, 0);
+	if (argc > 1)
+		second_token = peel_to_type(argv[1], 0, NULL, OBJ_COMMIT);
+	head_invalid = get_sha1("HEAD", head);
+
+	if (!have_message && second_token &&
+		!hashcmp(second_token->sha1, head)) {
+		strbuf_addstr(&merge_msg, argv[0]);
+		strbuf_addstr(&head_arg, argv[1]);
+		argv += 2;
+		argc -= 2;
+	} else if (head_invalid) {
+		struct object *remote_head;
+		/*
+		 * If the merged head is a valid one there is no reason
+		 * to forbid "git merge" into a branch yet to be born.
+		 * We do the same for "git pull".
+		 */
+		if (argc != 1)
+			die("Can merge only exactly one commit into "
+				"empty head");
+		remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+		if (!remote_head)
+			die("%s - not something we can merge", argv[0]);
+		update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
+				DIE_ON_ERR);
+		reset_hard(remote_head->sha1, 0);
+		return 0;
+	} else {
+		/* We are invoked directly as the first-class UI. */
+		strbuf_addstr(&head_arg, "HEAD");
+		/*
+		 * All the rest are the commits being merged;
+		 * prepare the standard merge summary message to
+		 * be appended to the given message.  If remote
+		 * is invalid we will die later in the common
+		 * codepath so we discard the error in this
+		 * loop.
+		 */
+		struct strbuf msg;
+
+		strbuf_init(&msg, 0);
+		for (i = 0; i < argc; i++)
+			merge_name(argv[i], &msg);
+		fmt_merge_msg(option_log, &msg, &merge_msg);
+		if (merge_msg.len)
+			strbuf_setlen(&merge_msg, merge_msg.len-1);
+	}
+
+	if (head_invalid || !argc)
+		usage_with_options(builtin_merge_usage,
+			builtin_merge_options);
+
+	strbuf_addstr(&buf, "merge");
+	for (i = 0; i < argc; i++)
+		strbuf_addf(&buf, " %s", argv[i]);
+	setenv("GIT_REFLOG_ACTION", buf.buf, 0);
+	strbuf_reset(&buf);
+
+	for (i = 0; i < argc; i++) {
+		struct object *o;
+
+		o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
+		if (!o)
+			die("%s - not something we can merge", argv[i]);
+		remotes = &commit_list_insert(lookup_commit(o->sha1),
+			remotes)->next;
+
+		strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
+		setenv(buf.buf, argv[i], 1);
+		strbuf_reset(&buf);
+	}
+
+	if (!use_strategies.nr) {
+		if (!remoteheads->next)
+			add_strategies(pull_twohead, DEFAULT_TWOHEAD);
+		else
+			add_strategies(pull_octopus, DEFAULT_OCTOPUS);
+	}
+
+	for (i = 0; i < use_strategies.nr; i++) {
+		if ((unsigned int)use_strategies.items[i].util &
+			NO_FAST_FORWARD)
+			allow_fast_forward = 0;
+		if ((unsigned int)use_strategies.items[i].util & NO_TRIVIAL)
+			allow_trivial = 0;
+	}
+
+	if (!remoteheads->next)
+		common = get_merge_bases(lookup_commit(head),
+				remoteheads->item, 1);
+	else {
+		struct commit_list *list = remoteheads;
+		commit_list_insert(lookup_commit(head), &list);
+		common = get_octopus_merge_bases(list);
+		free(list);
+	}
+
+	update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
+		DIE_ON_ERR);
+
+	if (!common)
+		; /* No common ancestors found. We need a real merge. */
+	else if (!remoteheads->next &&
+		!hashcmp(common->item->object.sha1,
+		remoteheads->item->object.sha1)) {
+		/*
+		 * If head can reach all the remote heads then we are up
+		 * to date.
+		 */
+		finish_up_to_date("Already up-to-date.");
+		return 0;
+	} else if (allow_fast_forward && !remoteheads->next &&
+		!hashcmp(common->item->object.sha1, head)) {
+		/* Again the most common case of merging one remote. */
+		struct strbuf msg;
+		struct object *o;
+		char hex[41];
+
+		strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+
+		printf("Updating %s..%s\n",
+			hex,
+			find_unique_abbrev(remoteheads->item->object.sha1,
+			DEFAULT_ABBREV));
+		refresh_cache(REFRESH_QUIET);
+		strbuf_initf(&msg, "%s", "Fast forward");
+		if (have_message)
+			strbuf_addstr(&msg,
+				" (no commit created; -m option ignored)");
+		o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
+			0, NULL, OBJ_COMMIT);
+		if (!o)
+			return 0;
+
+		if (merge_one_remote(head, remoteheads->item->object.sha1))
+			return 0;
+
+		finish(o->sha1, msg.buf);
+		dropsave();
+		return 0;
+	} else if (!remoteheads->next && common->next)
+		;
+		/*
+		 * We are not doing octopus and not fast forward.  Need
+		 * a real merge.
+		 */
+	else if (!remoteheads->next && option_commit) {
+		/*
+		 * We are not doing octopus, not fast forward, and have
+		 * only one common.
+		 */
+		refresh_cache(REFRESH_QUIET);
+		if (allow_trivial) {
+			/* See if it is really trivial. */
+			git_committer_info(IDENT_ERROR_ON_NO_NAME);
+			printf("Trying really trivial in-index merge...\n");
+			if (!read_tree_trivial(common->item->object.sha1,
+					head, remoteheads->item->object.sha1)) {
+				unsigned char result_tree[20],
+					result_commit[20];
+				struct commit_list parent;
+
+				write_tree_trivial(result_tree);
+				printf("Wonderful.\n");
+				parent.item = remoteheads->item;
+				parent.next = NULL;
+				commit_tree_trivial(merge_msg.buf,
+					result_tree, &parent,
+					result_commit);
+				finish(result_commit, "In-index merge");
+				dropsave();
+				return 0;
+			}
+			printf("Nope.\n");
+		}
+	} else {
+		/*
+		 * An octopus.  If we can reach all the remote we are up
+		 * to date.
+		 */
+		int up_to_date = 1;
+		struct commit_list *j;
+
+		for (j = remoteheads; j; j = j->next) {
+			struct commit_list *common_one;
+
+			/*
+			 * Here we *have* to calculate the individual
+			 * merge_bases again, otherwise "git merge HEAD^
+			 * HEAD^^" would be missed.
+			 */
+			common_one = get_merge_bases(lookup_commit(head),
+				j->item, 1);
+			if (hashcmp(common_one->item->object.sha1,
+				j->item->object.sha1)) {
+				up_to_date = 0;
+				break;
+			}
+		}
+		if (up_to_date) {
+			finish_up_to_date("Already up-to-date. Yeeah!");
+			return 0;
+		}
+	}
+
+	/* We are going to make a new commit. */
+	git_committer_info(IDENT_ERROR_ON_NO_NAME);
+
+	/*
+	 * At this point, we need a real merge.  No matter what strategy
+	 * we use, it would operate on the index, possibly affecting the
+	 * working tree, and when resolved cleanly, have the desired
+	 * tree in the index -- this means that the index must be in
+	 * sync with the head commit.  The strategies are responsible
+	 * to ensure this.
+	 */
+	if (use_strategies.nr != 1) {
+		/*
+		 * Stash away the local changes so that we can try more
+		 * than one.
+		 */
+		save_state();
+		single_strategy = 0;
+	} else {
+		unlink(git_path("MERGE_STASH"));
+		single_strategy = 1;
+	}
+
+	for (i = 0; i < use_strategies.nr; i++) {
+		int ret;
+		if (i) {
+			printf("Rewinding the tree to pristine...\n");
+			restore_state();
+		}
+		if (!single_strategy)
+			printf("Trying merge strategy %s...\n",
+				use_strategies.items[i].path);
+		/*
+		 * Remember which strategy left the state in the working
+		 * tree.
+		 */
+		wt_strategy = &use_strategies.items[i];
+
+		ret = try_merge_strategy(use_strategies.items[i].path,
+			common, &head_arg);
+		if (!option_commit && !ret) {
+			merge_was_ok = 1;
+			/*
+			 * This is necessary here just to avoid writing
+			 * the tree, but later we will *not* exit with
+			 * status code 1 because merge_was_ok is set.
+			 */
+			ret = 1;
+		}
+
+		if (ret) {
+			/*
+			 * The backend exits with 1 when conflicts are
+			 * left to be resolved, with 2 when it does not
+			 * handle the given merge at all.
+			 */
+			if (ret == 1) {
+				int cnt = 0;
+				struct rev_info rev;
+
+				if (read_cache() < 0)
+					die("failed to read the cache");
+
+				/* Check how many files differ. */
+				init_revisions(&rev, "");
+				setup_revisions(0, NULL, &rev, NULL);
+				rev.diffopt.output_format |=
+					DIFF_FORMAT_CALLBACK;
+				rev.diffopt.format_callback = count_diff_files;
+				rev.diffopt.format_callback_data = &cnt;
+				run_diff_files(&rev, 0);
+
+				/*
+				 * Check how many unmerged entries are
+				 * there.
+				 */
+				cnt += count_unmerged_entries();
+
+				if (best_cnt <= 0 || cnt <= best_cnt) {
+					best_strategy =
+						&use_strategies.items[i];
+					best_cnt = cnt;
+				}
+			}
+			if (merge_was_ok)
+				break;
+			else
+				continue;
+		}
+
+		/* Automerge succeeded. */
+		write_tree_trivial(result_tree);
+		automerge_was_ok = 1;
+		break;
+	}
+
+	/*
+	 * If we have a resulting tree, that means the strategy module
+	 * auto resolved the merge cleanly.
+	 */
+	if (automerge_was_ok) {
+		struct commit_list *parents = NULL, *j;
+		unsigned char result_commit[20];
+
+		free_commit_list(common);
+		if (allow_fast_forward) {
+			parents = remoteheads;
+			commit_list_insert(lookup_commit(head), &parents);
+			parents = reduce_heads(parents);
+		} else {
+			struct commit_list **pptr = &parents;
+
+			pptr = &commit_list_insert(lookup_commit(head),
+				pptr)->next;
+			for (j = remoteheads; j; j = j->next)
+				pptr = &commit_list_insert(j->item, pptr)->next;
+		}
+		free_commit_list(remoteheads);
+		strbuf_addch(&merge_msg, '\n');
+		commit_tree_trivial(merge_msg.buf, result_tree, parents,
+			result_commit);
+		free_commit_list(parents);
+		strbuf_addf(&buf, "Merge made by %s.", wt_strategy->path);
+		finish(result_commit, buf.buf);
+		strbuf_release(&buf);
+		dropsave();
+		return 0;
+	}
+
+	/*
+	 * Pick the result from the best strategy and have the user fix
+	 * it up.
+	 */
+	if (!best_strategy) {
+		restore_state();
+		if (use_strategies.nr > 1)
+			fprintf(stderr,
+				"No merge strategy handled the merge.\n");
+		else
+			fprintf(stderr, "Merge with strategy %s failed.\n",
+				use_strategies.items[0].path);
+		return 2;
+	} else if (best_strategy == wt_strategy)
+		; /* We already have its result in the working tree. */
+	else {
+		printf("Rewinding the tree to pristine...\n");
+		restore_state();
+		printf("Using the %s to prepare resolving by hand.\n",
+			best_strategy->path);
+		try_merge_strategy(best_strategy->path, common, &head_arg);
+	}
+
+	if (squash)
+		finish(NULL, NULL);
+	else {
+		int fd;
+		struct commit_list *j;
+
+		for (j = remoteheads; j; j = j->next)
+			strbuf_addf(&buf, "%s\n",
+				sha1_to_hex(j->item->object.sha1));
+		fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
+		if (fd < 0)
+			die("Could open %s for writing",
+				git_path("MERGE_HEAD"));
+		if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+			die("Could not write to %s", git_path("MERGE_HEAD"));
+		close(fd);
+		strbuf_addch(&merge_msg, '\n');
+		fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+		if (fd < 0)
+			die("Could open %s for writing", git_path("MERGE_MSG"));
+		if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
+			merge_msg.len)
+			die("Could not write to %s", git_path("MERGE_MSG"));
+		close(fd);
+	}
+
+	if (merge_was_ok) {
+		fprintf(stderr, "Automatic merge went well; "
+			"stopped before committing as requested\n");
+		return 0;
+	} else {
+		FILE *fp;
+		int pos;
+		const char *argv_rerere[] = { "rerere", NULL };
+
+		fp = fopen(git_path("MERGE_MSG"), "a");
+		if (!fp)
+			die("Could open %s for writing", git_path("MERGE_MSG"));
+		fprintf(fp, "\nConflicts:\n");
+		for (pos = 0; pos < active_nr; pos++) {
+			struct cache_entry *ce = active_cache[pos];
+
+			if (ce_stage(ce)) {
+				fprintf(fp, "\t%s\n", ce->name);
+				while (pos + 1 < active_nr &&
+					!strcmp(ce->name,
+					active_cache[pos + 1]->name))
+					pos++;
+			}
+		}
+		fclose(fp);
+		run_command_v_opt(argv_rerere, RUN_GIT_CMD);
+		printf("Automatic merge failed; "
+			"fix conflicts and then commit the result.\n");
+		return 1;
+	}
+}
diff --git a/builtin.h b/builtin.h
index 2b01fea..8bf5280 100644
--- a/builtin.h
+++ b/builtin.h
@@ -60,6 +60,7 @@ extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_ls_remote(int argc, const char **argv, const char *prefix);
 extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
 extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
+extern int cmd_merge(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
diff --git a/git-merge.sh b/contrib/examples/git-merge.sh
similarity index 100%
rename from git-merge.sh
rename to contrib/examples/git-merge.sh
diff --git a/git.c b/git.c
index 2fbe96b..770aadd 100644
--- a/git.c
+++ b/git.c
@@ -271,6 +271,7 @@ static void handle_internal_command(int argc, const char **argv)
 		{ "ls-remote", cmd_ls_remote },
 		{ "mailinfo", cmd_mailinfo },
 		{ "mailsplit", cmd_mailsplit },
+		{ "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
 		{ "merge-base", cmd_merge_base, RUN_SETUP },
 		{ "merge-file", cmd_merge_file },
 		{ "merge-ours", cmd_merge_ours, RUN_SETUP },
diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh
index f3a4bb2..fcb8285 100755
--- a/t/t7602-merge-octopus-many.sh
+++ b/t/t7602-merge-octopus-many.sh
@@ -23,7 +23,7 @@ test_expect_success 'setup' '
 	done
 '
 
-test_expect_failure 'merge c1 with c2, c3, c4, ... c29' '
+test_expect_success 'merge c1 with c2, c3, c4, ... c29' '
 	git reset --hard c1 &&
 	i=2 &&
 	refs="" &&
-- 
1.5.6

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH 08/15] Add new test to ensure git-merge handles more than 25 refs.
  2008-06-27 16:22               ` [PATCH 08/15] Add new test to ensure git-merge handles more than 25 refs Miklos Vajna
  2008-06-27 16:22                 ` [PATCH 09/15] Introduce get_merge_bases_many() Miklos Vajna
@ 2008-06-27 17:06                 ` Miklos Vajna
  1 sibling, 0 replies; 81+ messages in thread
From: Miklos Vajna @ 2008-06-27 17:06 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

The old shell version handled only 25 refs but we no longer have this
limitation. Add a test to make sure this limitation will not be
introduced again in the future.

Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---

On Fri, Jun 27, 2008 at 06:22:01PM +0200, Miklos Vajna <vmiklos@frugalware.org> wrote:
> +test_expect_failure 'merge c1 with c2, c3, c4, ... c29' '

I just noticed this was wrong here. The tests passed because a later
commit had a hunk to fix this but this fix is unrelated there. So here
is the right version.

 t/t7602-merge-octopus-many.sh |   52 +++++++++++++++++++++++++++++++++++++++++
 1 files changed, 52 insertions(+), 0 deletions(-)
 create mode 100755 t/t7602-merge-octopus-many.sh

diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh
new file mode 100755
index 0000000..fcb8285
--- /dev/null
+++ b/t/t7602-merge-octopus-many.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+test_description='git-merge
+
+Testing octopus merge with more than 25 refs.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	echo c0 > c0.c &&
+	git add c0.c &&
+	git commit -m c0 &&
+	git tag c0 &&
+	i=1 &&
+	while test $i -le 30
+	do
+		git reset --hard c0 &&
+		echo c$i > c$i.c &&
+		git add c$i.c &&
+		git commit -m c$i &&
+		git tag c$i &&
+		i=`expr $i + 1` || return 1
+	done
+'
+
+test_expect_success 'merge c1 with c2, c3, c4, ... c29' '
+	git reset --hard c1 &&
+	i=2 &&
+	refs="" &&
+	while test $i -le 30
+	do
+		refs="$refs c$i"
+		i=`expr $i + 1`
+	done
+	git merge $refs &&
+	test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
+	i=1 &&
+	while test $i -le 30
+	do
+		test "$(git rev-parse c$i)" = "$(git rev-parse HEAD^$i)" &&
+		i=`expr $i + 1` || return 1
+	done &&
+	git diff --exit-code &&
+	i=1 &&
+	while test $i -le 30
+	do
+		test -f c$i.c &&
+		i=`expr $i + 1` || return 1
+	done
+'
+
+test_done
-- 
1.5.6

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH 15/15] Build in merge
  2008-06-27 16:22                             ` [PATCH 15/15] Build in merge Miklos Vajna
@ 2008-06-27 17:09                               ` Miklos Vajna
  0 siblings, 0 replies; 81+ messages in thread
From: Miklos Vajna @ 2008-06-27 17:09 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---

On Fri, Jun 27, 2008 at 06:22:08PM +0200, Miklos Vajna <vmiklos@frugalware.org> wrote:
> diff --git a/t/t7602-merge-octopus-many.sh
> b/t/t7602-merge-octopus-many.sh
> index f3a4bb2..fcb8285 100755
> --- a/t/t7602-merge-octopus-many.sh
> +++ b/t/t7602-merge-octopus-many.sh
> @@ -23,7 +23,7 @@ test_expect_success 'setup' '
>       done
>  '
>
> -test_expect_failure 'merge c1 with c2, c3, c4, ... c29' '
> +test_expect_success 'merge c1 with c2, c3, c4, ... c29' '
>       git reset --hard c1 &&
>       i=2 &&
>       refs="" &&

This is an unrelated hunk, patch below which does not contains this.

 Makefile                                      |    2 +-
 builtin-merge.c                               | 1143 +++++++++++++++++++++++++
 builtin.h                                     |    1 +
 git-merge.sh => contrib/examples/git-merge.sh |    0 
 git.c                                         |    1 +
 5 files changed, 1146 insertions(+), 1 deletions(-)
 create mode 100644 builtin-merge.c
 rename git-merge.sh => contrib/examples/git-merge.sh (100%)

diff --git a/Makefile b/Makefile
index 425d1ca..6c642cd 100644
--- a/Makefile
+++ b/Makefile
@@ -240,7 +240,6 @@ SCRIPT_SH += git-lost-found.sh
 SCRIPT_SH += git-merge-octopus.sh
 SCRIPT_SH += git-merge-one-file.sh
 SCRIPT_SH += git-merge-resolve.sh
-SCRIPT_SH += git-merge.sh
 SCRIPT_SH += git-merge-stupid.sh
 SCRIPT_SH += git-mergetool.sh
 SCRIPT_SH += git-parse-remote.sh
@@ -514,6 +513,7 @@ BUILTIN_OBJS += builtin-ls-remote.o
 BUILTIN_OBJS += builtin-ls-tree.o
 BUILTIN_OBJS += builtin-mailinfo.o
 BUILTIN_OBJS += builtin-mailsplit.o
+BUILTIN_OBJS += builtin-merge.o
 BUILTIN_OBJS += builtin-merge-base.o
 BUILTIN_OBJS += builtin-merge-file.o
 BUILTIN_OBJS += builtin-merge-ours.o
diff --git a/builtin-merge.c b/builtin-merge.c
new file mode 100644
index 0000000..0d56dca
--- /dev/null
+++ b/builtin-merge.c
@@ -0,0 +1,1143 @@
+/*
+ * Builtin "git merge"
+ *
+ * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
+ *
+ * Based on git-merge.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "parse-options.h"
+#include "builtin.h"
+#include "run-command.h"
+#include "path-list.h"
+#include "diff.h"
+#include "refs.h"
+#include "commit.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
+#include "dir.h"
+#include "utf8.h"
+#include "log-tree.h"
+#include "color.h"
+
+enum strategy {
+	DEFAULT_TWOHEAD = 1,
+	DEFAULT_OCTOPUS = 2,
+	NO_FAST_FORWARD = 4,
+	NO_TRIVIAL = 8
+};
+
+static const char * const builtin_merge_usage[] = {
+	"git-merge [options] <remote>...",
+	"git-merge [options] <msg> HEAD <remote>",
+	NULL
+};
+
+static int show_diffstat = 1, option_log, squash;
+static int option_commit = 1, allow_fast_forward = 1;
+static int allow_trivial = 1, have_message;
+static struct strbuf merge_msg;
+static struct commit_list *remoteheads;
+static unsigned char head[20];
+static struct path_list use_strategies;
+static const char *branch;
+
+static struct path_list_item strategy_items[] = {
+	{ "recur",      (void *)NO_TRIVIAL },
+	{ "recursive",  (void *)(DEFAULT_TWOHEAD | NO_TRIVIAL) },
+	{ "octopus",    (void *)DEFAULT_OCTOPUS },
+	{ "resolve",    (void *)0 },
+	{ "stupid",     (void *)0 },
+	{ "ours",       (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
+	{ "subtree",    (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
+};
+static struct path_list strategies = { strategy_items,
+	ARRAY_SIZE(strategy_items), 0, 0 };
+
+static const char *pull_twohead, *pull_octopus;
+
+static int option_parse_message(const struct option *opt,
+	const char *arg, int unset)
+{
+	struct strbuf *buf = opt->value;
+
+	if (unset)
+		strbuf_setlen(buf, 0);
+	else {
+		strbuf_addf(buf, "%s\n\n", arg);
+		have_message = 1;
+	}
+	return 0;
+}
+
+static struct path_list_item *unsorted_path_list_lookup(const char *path,
+	struct path_list *list)
+{
+	int i;
+
+	if (!path)
+		return NULL;
+
+	for (i = 0; i < list->nr; i++)
+		if (!strcmp(path, list->items[i].path))
+			return &list->items[i];
+	return NULL;
+}
+
+static inline void path_list_append_strategy(
+		struct path_list_item *item)
+{
+	path_list_append(item->path, &use_strategies)->util = item->util;
+}
+
+static int option_parse_strategy(const struct option *opt,
+	const char *arg, int unset)
+{
+	int i;
+	struct path_list_item *item =
+		unsorted_path_list_lookup(arg, &strategies);
+
+	if (unset)
+		return 0;
+
+	if (item)
+		path_list_append_strategy(item);
+	else {
+		struct strbuf err;
+		strbuf_init(&err, 0);
+		for (i = 0; i < strategies.nr; i++)
+			strbuf_addf(&err, " %s", strategies.items[i].path);
+		fprintf(stderr, "Could not find merge strategy '%s'.\n", arg);
+		fprintf(stderr, "Available strategies are:%s.\n", err.buf);
+		exit(1);
+	}
+	return 0;
+}
+
+static int option_parse_n(const struct option *opt,
+		const char *arg, int unset)
+{
+	show_diffstat = unset;
+	return 0;
+}
+
+static struct option builtin_merge_options[] = {
+	{ OPTION_CALLBACK, 'n', NULL, NULL, NULL,
+		"do not show a diffstat at the end of the merge",
+		PARSE_OPT_NOARG, option_parse_n },
+	OPT_BOOLEAN(0, "stat", &show_diffstat,
+		"show a diffstat at the end of the merge"),
+	OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
+	OPT_BOOLEAN(0, "log", &option_log,
+		"add list of one-line log to merge commit message"),
+	OPT_BOOLEAN(0, "squash", &squash,
+		"create a single commit instead of doing a merge"),
+	OPT_BOOLEAN(0, "commit", &option_commit,
+		"perform a commit if the merge succeeds (default)"),
+	OPT_BOOLEAN(0, "ff", &allow_fast_forward,
+		"allow fast forward (default)"),
+	OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
+		"merge strategy to use", option_parse_strategy),
+	OPT_CALLBACK('m', "message", &merge_msg, "message",
+		"message to be used for the merge commit (if any)",
+		option_parse_message),
+	OPT_END()
+};
+
+/* Cleans up metadata that is uninteresting after a succeeded merge. */
+static void dropsave(void)
+{
+	unlink(git_path("MERGE_HEAD"));
+	unlink(git_path("MERGE_MSG"));
+	unlink(git_path("MERGE_STASH"));
+}
+
+static void save_state(void)
+{
+	int fd;
+	struct child_process stash;
+	const char *argv[] = {"stash", "create", NULL};
+
+	fd = open(git_path("MERGE_STASH"), O_WRONLY | O_CREAT, 0666);
+	if (fd < 0)
+		die("Could not write to %s", git_path("MERGE_STASH"));
+	memset(&stash, 0, sizeof(stash));
+	stash.argv = argv;
+	stash.out = fd;
+	stash.git_cmd = 1;
+	run_command(&stash);
+}
+
+static void reset_hard(unsigned const char *sha1, int verbose)
+{
+	struct tree *tree;
+	struct unpack_trees_options opts;
+	struct tree_desc t;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.head_idx = -1;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.reset = 1;
+	if (verbose)
+		opts.verbose_update = 1;
+
+	tree = parse_tree_indirect(sha1);
+	if (!tree)
+		die("failed to unpack %s tree object", sha1_to_hex(sha1));
+	parse_tree(tree);
+	init_tree_desc(&t, tree->buffer, tree->size);
+	if (unpack_trees(1, &t, &opts))
+		exit(128); /* We've already reported the error, finish dying */
+}
+
+static void restore_state(void)
+{
+	struct strbuf sb;
+	const char *args[] = { "stash", "apply", NULL, NULL };
+
+	if (access(git_path("MERGE_STASH"), R_OK) < 0)
+		return;
+
+	reset_hard(head, 1);
+
+	strbuf_init(&sb, 0);
+	if (strbuf_read_file(&sb, git_path("MERGE_STASH"), 0) < 0)
+		die("could not read MERGE_STASH: %s", strerror(errno));
+	args[2] = sb.buf;
+
+	/*
+	 * It is OK to ignore error here, for example when there was
+	 * nothing to restore.
+	 */
+	run_command_v_opt(args, RUN_GIT_CMD);
+
+	strbuf_release(&sb);
+	refresh_cache(REFRESH_QUIET);
+}
+
+/* This is called when no merge was necessary. */
+static void finish_up_to_date(const char *msg)
+{
+	printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
+	dropsave();
+}
+
+static void squash_message(void)
+{
+	struct rev_info rev;
+	struct commit *commit;
+	struct strbuf out;
+	struct commit_list *j;
+	int fd;
+
+	printf("Squash commit -- not updating HEAD\n");
+	fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+	if (fd < 0)
+		die("Could not write to %s", git_path("SQUASH_MSG"));
+
+	init_revisions(&rev, NULL);
+	rev.ignore_merges = 1;
+	rev.commit_format = CMIT_FMT_MEDIUM;
+
+	commit = lookup_commit(head);
+	commit->object.flags |= UNINTERESTING;
+	add_pending_object(&rev, &commit->object, NULL);
+
+	for (j = remoteheads; j; j = j->next)
+		add_pending_object(&rev, &j->item->object, NULL);
+
+	setup_revisions(0, NULL, &rev, NULL);
+	if (prepare_revision_walk(&rev))
+		die("revision walk setup failed");
+
+	strbuf_initf(&out, "Squashed commit of the following:\n");
+	while ((commit = get_revision(&rev)) != NULL) {
+		strbuf_addch(&out, '\n');
+		strbuf_addf(&out, "commit %s\n",
+			sha1_to_hex(commit->object.sha1));
+		pretty_print_commit(rev.commit_format, commit, &out, rev.abbrev,
+			NULL, NULL, rev.date_mode, 0);
+	}
+	write(fd, out.buf, out.len);
+	close(fd);
+	strbuf_release(&out);
+}
+
+static int run_hook(const char *name)
+{
+	struct child_process hook;
+	const char *argv[3], *env[2];
+	char index[PATH_MAX];
+
+	argv[0] = git_path("hooks/%s", name);
+	if (access(argv[0], X_OK) < 0)
+		return 0;
+
+	snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", get_index_file());
+	env[0] = index;
+	env[1] = NULL;
+
+	if (squash)
+		argv[1] = "1";
+	else
+		argv[1] = "0";
+	argv[2] = NULL;
+
+	memset(&hook, 0, sizeof(hook));
+	hook.argv = argv;
+	hook.no_stdin = 1;
+	hook.stdout_to_stderr = 1;
+	hook.env = env;
+
+	return run_command(&hook);
+}
+
+static void finish(const unsigned char *new_head, const char *msg)
+{
+	struct strbuf reflog_message;
+
+	if (!msg)
+		strbuf_initf(&reflog_message, "%s",
+			getenv("GIT_REFLOG_ACTION"));
+	else {
+		printf("%s\n", msg);
+		strbuf_initf(&reflog_message, "%s: %s",
+			getenv("GIT_REFLOG_ACTION"), msg);
+	}
+	if (squash) {
+		squash_message();
+	} else {
+		if (!merge_msg.len)
+			printf("No merge message -- not updating HEAD\n");
+		else {
+			const char *argv_gc_auto[] = { "gc", "--auto", NULL };
+			update_ref(reflog_message.buf, "HEAD",
+				new_head, head, 0,
+				DIE_ON_ERR);
+			/*
+			 * We ignore errors in 'gc --auto', since the
+			 * user should see them.
+			 */
+			run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+		}
+	}
+	if (new_head && show_diffstat) {
+		struct diff_options opts;
+		diff_setup(&opts);
+		opts.output_format |=
+			DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+		opts.detect_rename = DIFF_DETECT_RENAME;
+		if (diff_use_color_default > 0)
+			DIFF_OPT_SET(&opts, COLOR_DIFF);
+		diff_tree_sha1(head, new_head, "", &opts);
+		diffcore_std(&opts);
+		diff_flush(&opts);
+	}
+
+	/* Run a post-merge hook */
+	run_hook("post-merge");
+
+	strbuf_release(&reflog_message);
+}
+
+/* Get the name for the merge commit's message. */
+static void merge_name(const char *remote, struct strbuf *msg)
+{
+	struct object *remote_head;
+	unsigned char branch_head[20], buf_sha[20];
+	struct strbuf buf;
+	char *ptr;
+	int len = 0;
+
+	memset(branch_head, 0, sizeof(branch_head));
+	remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+	if (!remote_head)
+		return;
+
+	strbuf_initf(&buf, "refs/heads/%s", remote);
+	get_sha1(buf.buf, branch_head);
+
+	if (!hashcmp(remote_head->sha1, branch_head)) {
+		strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
+			sha1_to_hex(branch_head), remote);
+		return;
+	}
+	/* See if remote matches <name>~<number>, or <name>^ */
+	ptr = strrchr(remote, '^');
+	if (ptr && ptr[1] == '\0')
+		len = ptr-remote;
+	else {
+		ptr = strrchr(remote, '~');
+		if (ptr && ptr[1] != '0' && isdigit(ptr[1])) {
+			len = ptr-remote;
+			ptr++;
+			for (ptr++; *ptr; ptr++)
+				if (!isdigit(*ptr)) {
+					len = 0;
+					break;
+				}
+		}
+	}
+	if (len) {
+		struct strbuf truname = STRBUF_INIT;
+		strbuf_addstr(&truname, remote);
+		strbuf_setlen(&truname, len);
+		if (!get_sha1(truname.buf, buf_sha)) {
+			strbuf_addf(msg,
+				"%s\t\tbranch '%s' (early part) of .\n",
+				sha1_to_hex(remote_head->sha1), truname.buf);
+			return;
+		}
+	}
+
+	if (!strcmp(remote, "FETCH_HEAD") &&
+			!access(git_path("FETCH_HEAD"), R_OK)) {
+		FILE *fp;
+		struct strbuf line;
+		char *ptr;
+
+		strbuf_init(&line, 0);
+		fp = fopen(git_path("FETCH_HEAD"), "r");
+		if (!fp)
+			die("could not open %s for reading: %s",
+				git_path("FETCH_HEAD"), strerror(errno));
+		strbuf_getline(&line, fp, '\n');
+		fclose(fp);
+		ptr = strstr(line.buf, "\tnot-for-merge\t");
+		if (ptr)
+			strbuf_remove(&line, ptr-line.buf+1, 13);
+		strbuf_addbuf(msg, &line);
+		strbuf_release(&line);
+		return;
+	}
+	strbuf_addf(msg, "%s\t\tcommit '%s'\n",
+		sha1_to_hex(remote_head->sha1), remote);
+}
+
+int git_merge_config(const char *k, const char *v, void *cb)
+{
+	if (branch && !prefixcmp(k, "branch.") &&
+		!prefixcmp(k + 7, branch) &&
+		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+		const char **argv;
+		int argc;
+		char *buf;
+
+		buf = xstrdup(v);
+		argc = split_cmdline(buf, &argv);
+		argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+		memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+		argc++;
+		parse_options(argc, argv, builtin_merge_options,
+			      builtin_merge_usage, 0);
+		free(buf);
+	}
+
+	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+		show_diffstat = git_config_bool(k, v);
+	else if (!strcmp(k, "pull.twohead"))
+		return git_config_string(&pull_twohead, k, v);
+	else if (!strcmp(k, "pull.octopus"))
+		return git_config_string(&pull_octopus, k, v);
+	return 0;
+}
+
+static int read_tree_trivial(unsigned char *common, unsigned char *head,
+	unsigned char *one)
+{
+	int i, nr_trees = 0;
+	struct tree *trees[MAX_UNPACK_TREES];
+	struct tree_desc t[MAX_UNPACK_TREES];
+	struct unpack_trees_options opts;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.head_idx = -1;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.verbose_update = 1;
+	opts.trivial_merges_only = 1;
+	opts.merge = 1;
+	trees[nr_trees] = parse_tree_indirect(common);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(head);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(one);
+	if (!trees[nr_trees++])
+		return -1;
+	opts.fn = threeway_merge;
+	cache_tree_free(&active_cache_tree);
+	opts.head_idx = 2;
+	for (i = 0; i < nr_trees; i++) {
+		parse_tree(trees[i]);
+		init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+	}
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+	return 0;
+}
+
+static int commit_tree_trivial(const char *msg, unsigned const char *tree,
+		struct commit_list *parents, unsigned char *ret)
+{
+	struct commit_list *i;
+	struct strbuf buf;
+	int encoding_is_utf8;
+
+	/* Not having i18n.commitencoding is the same as having utf-8 */
+	encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
+
+	strbuf_init(&buf, 8192); /* should avoid reallocs for the headers */
+	strbuf_addf(&buf, "tree %s\n", sha1_to_hex(tree));
+
+	for (i = parents; i; i = i->next)
+		strbuf_addf(&buf, "parent %s\n",
+			sha1_to_hex(i->item->object.sha1));
+
+	/* Person/date information */
+	strbuf_addf(&buf, "author %s\n",
+		git_author_info(IDENT_ERROR_ON_NO_NAME));
+	strbuf_addf(&buf, "committer %s\n",
+		git_committer_info(IDENT_ERROR_ON_NO_NAME));
+	if (!encoding_is_utf8)
+		strbuf_addf(&buf, "encoding %s\n", git_commit_encoding);
+	strbuf_addch(&buf, '\n');
+
+	/* And add the comment */
+	strbuf_addstr(&buf, msg);
+
+	write_sha1_file(buf.buf, buf.len, commit_type, ret);
+	strbuf_release(&buf);
+	return *ret;
+}
+
+static void write_tree_trivial(unsigned char *sha1)
+{
+	if (write_cache_as_tree(sha1, 0, NULL))
+		die("git write-tree failed to write a tree");
+}
+
+static int try_merge_strategy(char *strategy, struct commit_list *common,
+	struct strbuf *head_arg)
+{
+	const char **args;
+	int i = 0, ret;
+	struct commit_list *j;
+	struct strbuf buf;
+
+	args = xmalloc((4 + commit_list_count(common) +
+			commit_list_count(remoteheads)) * sizeof(char *));
+	strbuf_initf(&buf, "merge-%s", strategy);
+	args[i++] = buf.buf;
+	for (j = common; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i++] = "--";
+	args[i++] = head_arg->buf;
+	for (j = remoteheads; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i] = NULL;
+	ret = run_command_v_opt(args, RUN_GIT_CMD);
+	strbuf_release(&buf);
+	i = 1;
+	for (j = common; j; j = j->next)
+		free((void *)args[i++]);
+	i += 2;
+	for (j = remoteheads; j; j = j->next)
+		free((void *)args[i++]);
+	free(args);
+	return -ret;
+}
+
+static void count_diff_files(struct diff_queue_struct *q,
+		struct diff_options *opt, void *data)
+{
+	int *count = data;
+
+	(*count) += q->nr;
+}
+
+static int count_unmerged_entries(void)
+{
+	const struct index_state *state = &the_index;
+	int i, ret = 0;
+
+	for (i = 0; i < state->cache_nr; i++)
+		if (ce_stage(state->cache[i]))
+			ret++;
+
+	return ret;
+}
+
+static int merge_one_remote(unsigned char *head, unsigned char *remote)
+{
+	struct tree *trees[MAX_UNPACK_TREES];
+	struct unpack_trees_options opts;
+	struct tree_desc t[MAX_UNPACK_TREES];
+	int i, fd, nr_trees = 0;
+	struct dir_struct dir;
+	struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+	if (read_cache_unmerged())
+		die("you need to resolve your current index first");
+
+	fd = hold_locked_index(lock_file, 1);
+
+	memset(&trees, 0, sizeof(trees));
+	memset(&opts, 0, sizeof(opts));
+	memset(&t, 0, sizeof(t));
+	dir.show_ignored = 1;
+	dir.exclude_per_dir = ".gitignore";
+	opts.dir = &dir;
+
+	opts.head_idx = 1;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.verbose_update = 1;
+	opts.merge = 1;
+	opts.fn = twoway_merge;
+
+	trees[nr_trees] = parse_tree_indirect(head);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(remote);
+	if (!trees[nr_trees++])
+		return -1;
+	for (i = 0; i < nr_trees; i++) {
+		parse_tree(trees[i]);
+		init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+	}
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+	if (write_cache(fd, active_cache, active_nr) ||
+		commit_locked_index(lock_file))
+		die("unable to write new index file");
+	return 0;
+}
+
+static void split_merge_strategies(const char *string, struct path_list *list)
+{
+	char *p, *q, *buf;
+
+	if (!string)
+		return;
+
+	list->strdup_paths = 1;
+	buf = xstrdup(string);
+	q = buf;
+	for (;;) {
+		p = strchr(q, ' ');
+		if (!p) {
+			path_list_append(q, list);
+			free(buf);
+			return;
+		} else {
+			*p = '\0';
+			path_list_append(q, list);
+			q = ++p;
+		}
+	}
+}
+
+static void add_strategies(const char *string, enum strategy strategy)
+{
+	struct path_list list;
+	int i;
+
+	memset(&list, 0, sizeof(list));
+	split_merge_strategies(string, &list);
+	if (list.nr) {
+		for (i = 0; i < list.nr; i++) {
+			struct path_list_item *item;
+
+			item = unsorted_path_list_lookup(list.items[i].path,
+				&strategies);
+			if (item)
+				path_list_append_strategy(item);
+		}
+		return;
+	}
+	for (i = 0; i < strategies.nr; i++)
+		if ((enum strategy)strategies.items[i].util & strategy)
+			path_list_append_strategy(&strategies.items[i]);
+}
+
+int cmd_merge(int argc, const char **argv, const char *prefix)
+{
+	unsigned char sha1[20], result_tree[20];
+	struct object *second_token = NULL;
+	struct strbuf buf, head_arg;
+	int flag, head_invalid, i, single_strategy;
+	int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
+	struct commit_list *common = NULL;
+	struct path_list_item *best_strategy = NULL, *wt_strategy = NULL;
+	struct commit_list **remotes = &remoteheads;
+
+	setup_work_tree();
+	if (unmerged_cache())
+		die("You are in the middle of a conflicted merge.");
+
+	/*
+	 * Check if we are _not_ on a detached HEAD, i.e. if there is a
+	 * current branch.
+	 */
+	branch = resolve_ref("HEAD", sha1, 0, &flag);
+	if (branch && flag & REF_ISSYMREF) {
+		const char *ptr = skip_prefix(branch, "refs/heads/");
+		if (ptr)
+			branch = ptr;
+	}
+
+	git_config(git_merge_config, NULL);
+
+	/* for color.diff and diff.color */
+	git_config(git_diff_ui_config, NULL);
+
+	/* for color.ui */
+	if (diff_use_color_default == -1)
+		diff_use_color_default = git_use_color_default;
+
+	argc = parse_options(argc, argv, builtin_merge_options,
+			builtin_merge_usage, 0);
+
+	if (squash) {
+		if (!allow_fast_forward)
+			die("You cannot combine --squash with --no-ff.");
+		option_commit = 0;
+	}
+
+	if (!argc)
+		usage_with_options(builtin_merge_usage,
+			builtin_merge_options);
+
+	/*
+	 * This could be traditional "merge <msg> HEAD <commit>..."  and
+	 * the way we can tell it is to see if the second token is HEAD,
+	 * but some people might have misused the interface and used a
+	 * committish that is the same as HEAD there instead.
+	 * Traditional format never would have "-m" so it is an
+	 * additional safety measure to check for it.
+	 */
+	strbuf_init(&buf, 0);
+	strbuf_init(&head_arg, 0);
+	if (argc > 1)
+		second_token = peel_to_type(argv[1], 0, NULL, OBJ_COMMIT);
+	head_invalid = get_sha1("HEAD", head);
+
+	if (!have_message && second_token &&
+		!hashcmp(second_token->sha1, head)) {
+		strbuf_addstr(&merge_msg, argv[0]);
+		strbuf_addstr(&head_arg, argv[1]);
+		argv += 2;
+		argc -= 2;
+	} else if (head_invalid) {
+		struct object *remote_head;
+		/*
+		 * If the merged head is a valid one there is no reason
+		 * to forbid "git merge" into a branch yet to be born.
+		 * We do the same for "git pull".
+		 */
+		if (argc != 1)
+			die("Can merge only exactly one commit into "
+				"empty head");
+		remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+		if (!remote_head)
+			die("%s - not something we can merge", argv[0]);
+		update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
+				DIE_ON_ERR);
+		reset_hard(remote_head->sha1, 0);
+		return 0;
+	} else {
+		/* We are invoked directly as the first-class UI. */
+		strbuf_addstr(&head_arg, "HEAD");
+		/*
+		 * All the rest are the commits being merged;
+		 * prepare the standard merge summary message to
+		 * be appended to the given message.  If remote
+		 * is invalid we will die later in the common
+		 * codepath so we discard the error in this
+		 * loop.
+		 */
+		struct strbuf msg;
+
+		strbuf_init(&msg, 0);
+		for (i = 0; i < argc; i++)
+			merge_name(argv[i], &msg);
+		fmt_merge_msg(option_log, &msg, &merge_msg);
+		if (merge_msg.len)
+			strbuf_setlen(&merge_msg, merge_msg.len-1);
+	}
+
+	if (head_invalid || !argc)
+		usage_with_options(builtin_merge_usage,
+			builtin_merge_options);
+
+	strbuf_addstr(&buf, "merge");
+	for (i = 0; i < argc; i++)
+		strbuf_addf(&buf, " %s", argv[i]);
+	setenv("GIT_REFLOG_ACTION", buf.buf, 0);
+	strbuf_reset(&buf);
+
+	for (i = 0; i < argc; i++) {
+		struct object *o;
+
+		o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
+		if (!o)
+			die("%s - not something we can merge", argv[i]);
+		remotes = &commit_list_insert(lookup_commit(o->sha1),
+			remotes)->next;
+
+		strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
+		setenv(buf.buf, argv[i], 1);
+		strbuf_reset(&buf);
+	}
+
+	if (!use_strategies.nr) {
+		if (!remoteheads->next)
+			add_strategies(pull_twohead, DEFAULT_TWOHEAD);
+		else
+			add_strategies(pull_octopus, DEFAULT_OCTOPUS);
+	}
+
+	for (i = 0; i < use_strategies.nr; i++) {
+		if ((unsigned int)use_strategies.items[i].util &
+			NO_FAST_FORWARD)
+			allow_fast_forward = 0;
+		if ((unsigned int)use_strategies.items[i].util & NO_TRIVIAL)
+			allow_trivial = 0;
+	}
+
+	if (!remoteheads->next)
+		common = get_merge_bases(lookup_commit(head),
+				remoteheads->item, 1);
+	else {
+		struct commit_list *list = remoteheads;
+		commit_list_insert(lookup_commit(head), &list);
+		common = get_octopus_merge_bases(list);
+		free(list);
+	}
+
+	update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
+		DIE_ON_ERR);
+
+	if (!common)
+		; /* No common ancestors found. We need a real merge. */
+	else if (!remoteheads->next &&
+		!hashcmp(common->item->object.sha1,
+		remoteheads->item->object.sha1)) {
+		/*
+		 * If head can reach all the remote heads then we are up
+		 * to date.
+		 */
+		finish_up_to_date("Already up-to-date.");
+		return 0;
+	} else if (allow_fast_forward && !remoteheads->next &&
+		!hashcmp(common->item->object.sha1, head)) {
+		/* Again the most common case of merging one remote. */
+		struct strbuf msg;
+		struct object *o;
+		char hex[41];
+
+		strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+
+		printf("Updating %s..%s\n",
+			hex,
+			find_unique_abbrev(remoteheads->item->object.sha1,
+			DEFAULT_ABBREV));
+		refresh_cache(REFRESH_QUIET);
+		strbuf_initf(&msg, "%s", "Fast forward");
+		if (have_message)
+			strbuf_addstr(&msg,
+				" (no commit created; -m option ignored)");
+		o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
+			0, NULL, OBJ_COMMIT);
+		if (!o)
+			return 0;
+
+		if (merge_one_remote(head, remoteheads->item->object.sha1))
+			return 0;
+
+		finish(o->sha1, msg.buf);
+		dropsave();
+		return 0;
+	} else if (!remoteheads->next && common->next)
+		;
+		/*
+		 * We are not doing octopus and not fast forward.  Need
+		 * a real merge.
+		 */
+	else if (!remoteheads->next && option_commit) {
+		/*
+		 * We are not doing octopus, not fast forward, and have
+		 * only one common.
+		 */
+		refresh_cache(REFRESH_QUIET);
+		if (allow_trivial) {
+			/* See if it is really trivial. */
+			git_committer_info(IDENT_ERROR_ON_NO_NAME);
+			printf("Trying really trivial in-index merge...\n");
+			if (!read_tree_trivial(common->item->object.sha1,
+					head, remoteheads->item->object.sha1)) {
+				unsigned char result_tree[20],
+					result_commit[20];
+				struct commit_list parent;
+
+				write_tree_trivial(result_tree);
+				printf("Wonderful.\n");
+				parent.item = remoteheads->item;
+				parent.next = NULL;
+				commit_tree_trivial(merge_msg.buf,
+					result_tree, &parent,
+					result_commit);
+				finish(result_commit, "In-index merge");
+				dropsave();
+				return 0;
+			}
+			printf("Nope.\n");
+		}
+	} else {
+		/*
+		 * An octopus.  If we can reach all the remote we are up
+		 * to date.
+		 */
+		int up_to_date = 1;
+		struct commit_list *j;
+
+		for (j = remoteheads; j; j = j->next) {
+			struct commit_list *common_one;
+
+			/*
+			 * Here we *have* to calculate the individual
+			 * merge_bases again, otherwise "git merge HEAD^
+			 * HEAD^^" would be missed.
+			 */
+			common_one = get_merge_bases(lookup_commit(head),
+				j->item, 1);
+			if (hashcmp(common_one->item->object.sha1,
+				j->item->object.sha1)) {
+				up_to_date = 0;
+				break;
+			}
+		}
+		if (up_to_date) {
+			finish_up_to_date("Already up-to-date. Yeeah!");
+			return 0;
+		}
+	}
+
+	/* We are going to make a new commit. */
+	git_committer_info(IDENT_ERROR_ON_NO_NAME);
+
+	/*
+	 * At this point, we need a real merge.  No matter what strategy
+	 * we use, it would operate on the index, possibly affecting the
+	 * working tree, and when resolved cleanly, have the desired
+	 * tree in the index -- this means that the index must be in
+	 * sync with the head commit.  The strategies are responsible
+	 * to ensure this.
+	 */
+	if (use_strategies.nr != 1) {
+		/*
+		 * Stash away the local changes so that we can try more
+		 * than one.
+		 */
+		save_state();
+		single_strategy = 0;
+	} else {
+		unlink(git_path("MERGE_STASH"));
+		single_strategy = 1;
+	}
+
+	for (i = 0; i < use_strategies.nr; i++) {
+		int ret;
+		if (i) {
+			printf("Rewinding the tree to pristine...\n");
+			restore_state();
+		}
+		if (!single_strategy)
+			printf("Trying merge strategy %s...\n",
+				use_strategies.items[i].path);
+		/*
+		 * Remember which strategy left the state in the working
+		 * tree.
+		 */
+		wt_strategy = &use_strategies.items[i];
+
+		ret = try_merge_strategy(use_strategies.items[i].path,
+			common, &head_arg);
+		if (!option_commit && !ret) {
+			merge_was_ok = 1;
+			/*
+			 * This is necessary here just to avoid writing
+			 * the tree, but later we will *not* exit with
+			 * status code 1 because merge_was_ok is set.
+			 */
+			ret = 1;
+		}
+
+		if (ret) {
+			/*
+			 * The backend exits with 1 when conflicts are
+			 * left to be resolved, with 2 when it does not
+			 * handle the given merge at all.
+			 */
+			if (ret == 1) {
+				int cnt = 0;
+				struct rev_info rev;
+
+				if (read_cache() < 0)
+					die("failed to read the cache");
+
+				/* Check how many files differ. */
+				init_revisions(&rev, "");
+				setup_revisions(0, NULL, &rev, NULL);
+				rev.diffopt.output_format |=
+					DIFF_FORMAT_CALLBACK;
+				rev.diffopt.format_callback = count_diff_files;
+				rev.diffopt.format_callback_data = &cnt;
+				run_diff_files(&rev, 0);
+
+				/*
+				 * Check how many unmerged entries are
+				 * there.
+				 */
+				cnt += count_unmerged_entries();
+
+				if (best_cnt <= 0 || cnt <= best_cnt) {
+					best_strategy =
+						&use_strategies.items[i];
+					best_cnt = cnt;
+				}
+			}
+			if (merge_was_ok)
+				break;
+			else
+				continue;
+		}
+
+		/* Automerge succeeded. */
+		write_tree_trivial(result_tree);
+		automerge_was_ok = 1;
+		break;
+	}
+
+	/*
+	 * If we have a resulting tree, that means the strategy module
+	 * auto resolved the merge cleanly.
+	 */
+	if (automerge_was_ok) {
+		struct commit_list *parents = NULL, *j;
+		unsigned char result_commit[20];
+
+		free_commit_list(common);
+		if (allow_fast_forward) {
+			parents = remoteheads;
+			commit_list_insert(lookup_commit(head), &parents);
+			parents = reduce_heads(parents);
+		} else {
+			struct commit_list **pptr = &parents;
+
+			pptr = &commit_list_insert(lookup_commit(head),
+				pptr)->next;
+			for (j = remoteheads; j; j = j->next)
+				pptr = &commit_list_insert(j->item, pptr)->next;
+		}
+		free_commit_list(remoteheads);
+		strbuf_addch(&merge_msg, '\n');
+		commit_tree_trivial(merge_msg.buf, result_tree, parents,
+			result_commit);
+		free_commit_list(parents);
+		strbuf_addf(&buf, "Merge made by %s.", wt_strategy->path);
+		finish(result_commit, buf.buf);
+		strbuf_release(&buf);
+		dropsave();
+		return 0;
+	}
+
+	/*
+	 * Pick the result from the best strategy and have the user fix
+	 * it up.
+	 */
+	if (!best_strategy) {
+		restore_state();
+		if (use_strategies.nr > 1)
+			fprintf(stderr,
+				"No merge strategy handled the merge.\n");
+		else
+			fprintf(stderr, "Merge with strategy %s failed.\n",
+				use_strategies.items[0].path);
+		return 2;
+	} else if (best_strategy == wt_strategy)
+		; /* We already have its result in the working tree. */
+	else {
+		printf("Rewinding the tree to pristine...\n");
+		restore_state();
+		printf("Using the %s to prepare resolving by hand.\n",
+			best_strategy->path);
+		try_merge_strategy(best_strategy->path, common, &head_arg);
+	}
+
+	if (squash)
+		finish(NULL, NULL);
+	else {
+		int fd;
+		struct commit_list *j;
+
+		for (j = remoteheads; j; j = j->next)
+			strbuf_addf(&buf, "%s\n",
+				sha1_to_hex(j->item->object.sha1));
+		fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
+		if (fd < 0)
+			die("Could open %s for writing",
+				git_path("MERGE_HEAD"));
+		if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+			die("Could not write to %s", git_path("MERGE_HEAD"));
+		close(fd);
+		strbuf_addch(&merge_msg, '\n');
+		fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+		if (fd < 0)
+			die("Could open %s for writing", git_path("MERGE_MSG"));
+		if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
+			merge_msg.len)
+			die("Could not write to %s", git_path("MERGE_MSG"));
+		close(fd);
+	}
+
+	if (merge_was_ok) {
+		fprintf(stderr, "Automatic merge went well; "
+			"stopped before committing as requested\n");
+		return 0;
+	} else {
+		FILE *fp;
+		int pos;
+		const char *argv_rerere[] = { "rerere", NULL };
+
+		fp = fopen(git_path("MERGE_MSG"), "a");
+		if (!fp)
+			die("Could open %s for writing", git_path("MERGE_MSG"));
+		fprintf(fp, "\nConflicts:\n");
+		for (pos = 0; pos < active_nr; pos++) {
+			struct cache_entry *ce = active_cache[pos];
+
+			if (ce_stage(ce)) {
+				fprintf(fp, "\t%s\n", ce->name);
+				while (pos + 1 < active_nr &&
+					!strcmp(ce->name,
+					active_cache[pos + 1]->name))
+					pos++;
+			}
+		}
+		fclose(fp);
+		run_command_v_opt(argv_rerere, RUN_GIT_CMD);
+		printf("Automatic merge failed; "
+			"fix conflicts and then commit the result.\n");
+		return 1;
+	}
+}
diff --git a/builtin.h b/builtin.h
index 2b01fea..8bf5280 100644
--- a/builtin.h
+++ b/builtin.h
@@ -60,6 +60,7 @@ extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_ls_remote(int argc, const char **argv, const char *prefix);
 extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
 extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
+extern int cmd_merge(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
diff --git a/git-merge.sh b/contrib/examples/git-merge.sh
similarity index 100%
rename from git-merge.sh
rename to contrib/examples/git-merge.sh
diff --git a/git.c b/git.c
index 2fbe96b..770aadd 100644
--- a/git.c
+++ b/git.c
@@ -271,6 +271,7 @@ static void handle_internal_command(int argc, const char **argv)
 		{ "ls-remote", cmd_ls_remote },
 		{ "mailinfo", cmd_mailinfo },
 		{ "mailsplit", cmd_mailsplit },
+		{ "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
 		{ "merge-base", cmd_merge_base, RUN_SETUP },
 		{ "merge-file", cmd_merge_file },
 		{ "merge-ours", cmd_merge_ours, RUN_SETUP },
-- 
1.5.6

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf()
  2008-06-27 16:22                     ` [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf() Miklos Vajna
  2008-06-27 16:22                       ` [PATCH 12/15] strbuf_vaddf(): support %*s, too Miklos Vajna
@ 2008-06-28  2:00                       ` Junio C Hamano
  2008-06-28  2:33                         ` Miklos Vajna
  2008-06-28 17:33                         ` [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf() Johannes Schindelin
  1 sibling, 2 replies; 81+ messages in thread
From: Junio C Hamano @ 2008-06-28  2:00 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: git, Johannes Schindelin, Olivier Marin

Do you really think these two patches belong to the series, I seriously
have to wonder?

The series uses strbuf_initf() from 7 call sites to save a few lines per
each call site, and for that you have a 140-line patch to strbuf.c (in
aggregate between this one and the "Oops, I need to support this, too" one
after this).

This new code that is not essential to the series need to be carefully
vetted to avoid risks of potentially affecting existing 70+ users of
strbuf_addf(), which used to use (hopefully more trustworthy) vsnprintf()
from the system, but now uses the new and unproven strbuf_vaddf() that
has "Only supports %<these>" disclaimer at the top.

I am not saying the strbuf_vaddf() patch is not worth considering.  It
might help platforms without a working vsnprintf().  But its merit needs
to be defended separately, and I do not want "merge in C" series to be
taken hostage by it.

Other than that, I did not see anything obviously wrong in the diff
between the previous round and this series.

Thanks.

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf()
  2008-06-28  2:00                       ` [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf() Junio C Hamano
@ 2008-06-28  2:33                         ` Miklos Vajna
  2008-06-28  2:38                           ` [PATCH 13/13] Build in merge Miklos Vajna
  2008-06-28 17:33                         ` [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf() Johannes Schindelin
  1 sibling, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-06-28  2:33 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

[-- Attachment #1: Type: text/plain, Size: 572 bytes --]

On Fri, Jun 27, 2008 at 07:00:53PM -0700, Junio C Hamano <gitster@pobox.com> wrote:
> Do you really think these two patches belong to the series, I seriously
> have to wonder?

Well, no. I was suggested to include it there, but you have reason about
it should be a different series.

> Other than that, I did not see anything obviously wrong in the diff
> between the previous round and this series.

OK, I'll send the main patch without the strbuf_initf() calls, and then
you can just fetch from git/vmiklos.git or drop Dscho's two patches from
my topic branch.

Thanks.

[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH 13/13] Build in merge
  2008-06-28  2:33                         ` Miklos Vajna
@ 2008-06-28  2:38                           ` Miklos Vajna
  2008-06-29  7:46                             ` Junio C Hamano
  0 siblings, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-06-28  2:38 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---

On Sat, Jun 28, 2008 at 04:33:55AM +0200, Miklos Vajna <vmiklos@frugalware.org> wrote:
> OK, I'll send the main patch without the strbuf_initf() calls

Here it is.

 Makefile                                      |    2 +-
 builtin-merge.c                               | 1148 +++++++++++++++++++++++++
 builtin.h                                     |    1 +
 git-merge.sh => contrib/examples/git-merge.sh |    0 
 git.c                                         |    1 +
 5 files changed, 1151 insertions(+), 1 deletions(-)
 create mode 100644 builtin-merge.c
 rename git-merge.sh => contrib/examples/git-merge.sh (100%)

diff --git a/Makefile b/Makefile
index 3584b8c..dab29b0 100644
--- a/Makefile
+++ b/Makefile
@@ -240,7 +240,6 @@ SCRIPT_SH += git-lost-found.sh
 SCRIPT_SH += git-merge-octopus.sh
 SCRIPT_SH += git-merge-one-file.sh
 SCRIPT_SH += git-merge-resolve.sh
-SCRIPT_SH += git-merge.sh
 SCRIPT_SH += git-merge-stupid.sh
 SCRIPT_SH += git-mergetool.sh
 SCRIPT_SH += git-parse-remote.sh
@@ -514,6 +513,7 @@ BUILTIN_OBJS += builtin-ls-remote.o
 BUILTIN_OBJS += builtin-ls-tree.o
 BUILTIN_OBJS += builtin-mailinfo.o
 BUILTIN_OBJS += builtin-mailsplit.o
+BUILTIN_OBJS += builtin-merge.o
 BUILTIN_OBJS += builtin-merge-base.o
 BUILTIN_OBJS += builtin-merge-file.o
 BUILTIN_OBJS += builtin-merge-ours.o
diff --git a/builtin-merge.c b/builtin-merge.c
new file mode 100644
index 0000000..39bfbd1
--- /dev/null
+++ b/builtin-merge.c
@@ -0,0 +1,1148 @@
+/*
+ * Builtin "git merge"
+ *
+ * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
+ *
+ * Based on git-merge.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "parse-options.h"
+#include "builtin.h"
+#include "run-command.h"
+#include "path-list.h"
+#include "diff.h"
+#include "refs.h"
+#include "commit.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
+#include "dir.h"
+#include "utf8.h"
+#include "log-tree.h"
+#include "color.h"
+
+enum strategy {
+	DEFAULT_TWOHEAD = 1,
+	DEFAULT_OCTOPUS = 2,
+	NO_FAST_FORWARD = 4,
+	NO_TRIVIAL = 8
+};
+
+static const char * const builtin_merge_usage[] = {
+	"git-merge [options] <remote>...",
+	"git-merge [options] <msg> HEAD <remote>",
+	NULL
+};
+
+static int show_diffstat = 1, option_log, squash;
+static int option_commit = 1, allow_fast_forward = 1;
+static int allow_trivial = 1, have_message;
+static struct strbuf merge_msg;
+static struct commit_list *remoteheads;
+static unsigned char head[20];
+static struct path_list use_strategies;
+static const char *branch;
+
+static struct path_list_item strategy_items[] = {
+	{ "recur",      (void *)NO_TRIVIAL },
+	{ "recursive",  (void *)(DEFAULT_TWOHEAD | NO_TRIVIAL) },
+	{ "octopus",    (void *)DEFAULT_OCTOPUS },
+	{ "resolve",    (void *)0 },
+	{ "stupid",     (void *)0 },
+	{ "ours",       (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
+	{ "subtree",    (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
+};
+static struct path_list strategies = { strategy_items,
+	ARRAY_SIZE(strategy_items), 0, 0 };
+
+static const char *pull_twohead, *pull_octopus;
+
+static int option_parse_message(const struct option *opt,
+	const char *arg, int unset)
+{
+	struct strbuf *buf = opt->value;
+
+	if (unset)
+		strbuf_setlen(buf, 0);
+	else {
+		strbuf_addf(buf, "%s\n\n", arg);
+		have_message = 1;
+	}
+	return 0;
+}
+
+static struct path_list_item *unsorted_path_list_lookup(const char *path,
+	struct path_list *list)
+{
+	int i;
+
+	if (!path)
+		return NULL;
+
+	for (i = 0; i < list->nr; i++)
+		if (!strcmp(path, list->items[i].path))
+			return &list->items[i];
+	return NULL;
+}
+
+static inline void path_list_append_strategy(
+		struct path_list_item *item)
+{
+	path_list_append(item->path, &use_strategies)->util = item->util;
+}
+
+static int option_parse_strategy(const struct option *opt,
+	const char *arg, int unset)
+{
+	int i;
+	struct path_list_item *item =
+		unsorted_path_list_lookup(arg, &strategies);
+
+	if (unset)
+		return 0;
+
+	if (item)
+		path_list_append_strategy(item);
+	else {
+		struct strbuf err;
+		strbuf_init(&err, 0);
+		for (i = 0; i < strategies.nr; i++)
+			strbuf_addf(&err, " %s", strategies.items[i].path);
+		fprintf(stderr, "Could not find merge strategy '%s'.\n", arg);
+		fprintf(stderr, "Available strategies are:%s.\n", err.buf);
+		exit(1);
+	}
+	return 0;
+}
+
+static int option_parse_n(const struct option *opt,
+		const char *arg, int unset)
+{
+	show_diffstat = unset;
+	return 0;
+}
+
+static struct option builtin_merge_options[] = {
+	{ OPTION_CALLBACK, 'n', NULL, NULL, NULL,
+		"do not show a diffstat at the end of the merge",
+		PARSE_OPT_NOARG, option_parse_n },
+	OPT_BOOLEAN(0, "stat", &show_diffstat,
+		"show a diffstat at the end of the merge"),
+	OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
+	OPT_BOOLEAN(0, "log", &option_log,
+		"add list of one-line log to merge commit message"),
+	OPT_BOOLEAN(0, "squash", &squash,
+		"create a single commit instead of doing a merge"),
+	OPT_BOOLEAN(0, "commit", &option_commit,
+		"perform a commit if the merge succeeds (default)"),
+	OPT_BOOLEAN(0, "ff", &allow_fast_forward,
+		"allow fast forward (default)"),
+	OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
+		"merge strategy to use", option_parse_strategy),
+	OPT_CALLBACK('m', "message", &merge_msg, "message",
+		"message to be used for the merge commit (if any)",
+		option_parse_message),
+	OPT_END()
+};
+
+/* Cleans up metadata that is uninteresting after a succeeded merge. */
+static void dropsave(void)
+{
+	unlink(git_path("MERGE_HEAD"));
+	unlink(git_path("MERGE_MSG"));
+	unlink(git_path("MERGE_STASH"));
+}
+
+static void save_state(void)
+{
+	int fd;
+	struct child_process stash;
+	const char *argv[] = {"stash", "create", NULL};
+
+	fd = open(git_path("MERGE_STASH"), O_WRONLY | O_CREAT, 0666);
+	if (fd < 0)
+		die("Could not write to %s", git_path("MERGE_STASH"));
+	memset(&stash, 0, sizeof(stash));
+	stash.argv = argv;
+	stash.out = fd;
+	stash.git_cmd = 1;
+	run_command(&stash);
+}
+
+static void reset_hard(unsigned const char *sha1, int verbose)
+{
+	struct tree *tree;
+	struct unpack_trees_options opts;
+	struct tree_desc t;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.head_idx = -1;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.reset = 1;
+	if (verbose)
+		opts.verbose_update = 1;
+
+	tree = parse_tree_indirect(sha1);
+	if (!tree)
+		die("failed to unpack %s tree object", sha1_to_hex(sha1));
+	parse_tree(tree);
+	init_tree_desc(&t, tree->buffer, tree->size);
+	if (unpack_trees(1, &t, &opts))
+		exit(128); /* We've already reported the error, finish dying */
+}
+
+static void restore_state(void)
+{
+	struct strbuf sb;
+	const char *args[] = { "stash", "apply", NULL, NULL };
+
+	if (access(git_path("MERGE_STASH"), R_OK) < 0)
+		return;
+
+	reset_hard(head, 1);
+
+	strbuf_init(&sb, 0);
+	if (strbuf_read_file(&sb, git_path("MERGE_STASH"), 0) < 0)
+		die("could not read MERGE_STASH: %s", strerror(errno));
+	args[2] = sb.buf;
+
+	/*
+	 * It is OK to ignore error here, for example when there was
+	 * nothing to restore.
+	 */
+	run_command_v_opt(args, RUN_GIT_CMD);
+
+	strbuf_release(&sb);
+	refresh_cache(REFRESH_QUIET);
+}
+
+/* This is called when no merge was necessary. */
+static void finish_up_to_date(const char *msg)
+{
+	printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
+	dropsave();
+}
+
+static void squash_message(void)
+{
+	struct rev_info rev;
+	struct commit *commit;
+	struct strbuf out;
+	struct commit_list *j;
+	int fd;
+
+	printf("Squash commit -- not updating HEAD\n");
+	fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+	if (fd < 0)
+		die("Could not write to %s", git_path("SQUASH_MSG"));
+
+	init_revisions(&rev, NULL);
+	rev.ignore_merges = 1;
+	rev.commit_format = CMIT_FMT_MEDIUM;
+
+	commit = lookup_commit(head);
+	commit->object.flags |= UNINTERESTING;
+	add_pending_object(&rev, &commit->object, NULL);
+
+	for (j = remoteheads; j; j = j->next)
+		add_pending_object(&rev, &j->item->object, NULL);
+
+	setup_revisions(0, NULL, &rev, NULL);
+	if (prepare_revision_walk(&rev))
+		die("revision walk setup failed");
+
+	strbuf_init(&out, 0);
+	strbuf_addstr(&out, "Squashed commit of the following:\n");
+	while ((commit = get_revision(&rev)) != NULL) {
+		strbuf_addch(&out, '\n');
+		strbuf_addf(&out, "commit %s\n",
+			sha1_to_hex(commit->object.sha1));
+		pretty_print_commit(rev.commit_format, commit, &out, rev.abbrev,
+			NULL, NULL, rev.date_mode, 0);
+	}
+	write(fd, out.buf, out.len);
+	close(fd);
+	strbuf_release(&out);
+}
+
+static int run_hook(const char *name)
+{
+	struct child_process hook;
+	const char *argv[3], *env[2];
+	char index[PATH_MAX];
+
+	argv[0] = git_path("hooks/%s", name);
+	if (access(argv[0], X_OK) < 0)
+		return 0;
+
+	snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", get_index_file());
+	env[0] = index;
+	env[1] = NULL;
+
+	if (squash)
+		argv[1] = "1";
+	else
+		argv[1] = "0";
+	argv[2] = NULL;
+
+	memset(&hook, 0, sizeof(hook));
+	hook.argv = argv;
+	hook.no_stdin = 1;
+	hook.stdout_to_stderr = 1;
+	hook.env = env;
+
+	return run_command(&hook);
+}
+
+static void finish(const unsigned char *new_head, const char *msg)
+{
+	struct strbuf reflog_message;
+
+	strbuf_init(&reflog_message, 0);
+	if (!msg)
+		strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
+	else {
+		printf("%s\n", msg);
+		strbuf_addf(&reflog_message, "%s: %s",
+			getenv("GIT_REFLOG_ACTION"), msg);
+	}
+	if (squash) {
+		squash_message();
+	} else {
+		if (!merge_msg.len)
+			printf("No merge message -- not updating HEAD\n");
+		else {
+			const char *argv_gc_auto[] = { "gc", "--auto", NULL };
+			update_ref(reflog_message.buf, "HEAD",
+				new_head, head, 0,
+				DIE_ON_ERR);
+			/*
+			 * We ignore errors in 'gc --auto', since the
+			 * user should see them.
+			 */
+			run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+		}
+	}
+	if (new_head && show_diffstat) {
+		struct diff_options opts;
+		diff_setup(&opts);
+		opts.output_format |=
+			DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+		opts.detect_rename = DIFF_DETECT_RENAME;
+		if (diff_use_color_default > 0)
+			DIFF_OPT_SET(&opts, COLOR_DIFF);
+		diff_tree_sha1(head, new_head, "", &opts);
+		diffcore_std(&opts);
+		diff_flush(&opts);
+	}
+
+	/* Run a post-merge hook */
+	run_hook("post-merge");
+
+	strbuf_release(&reflog_message);
+}
+
+/* Get the name for the merge commit's message. */
+static void merge_name(const char *remote, struct strbuf *msg)
+{
+	struct object *remote_head;
+	unsigned char branch_head[20], buf_sha[20];
+	struct strbuf buf;
+	char *ptr;
+	int len = 0;
+
+	memset(branch_head, 0, sizeof(branch_head));
+	remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+	if (!remote_head)
+		return;
+
+	strbuf_init(&buf, 0);
+	strbuf_addstr(&buf, "refs/heads/");
+	strbuf_addstr(&buf, remote);
+	get_sha1(buf.buf, branch_head);
+
+	if (!hashcmp(remote_head->sha1, branch_head)) {
+		strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
+			sha1_to_hex(branch_head), remote);
+		return;
+	}
+	/* See if remote matches <name>~<number>, or <name>^ */
+	ptr = strrchr(remote, '^');
+	if (ptr && ptr[1] == '\0')
+		len = ptr-remote;
+	else {
+		ptr = strrchr(remote, '~');
+		if (ptr && ptr[1] != '0' && isdigit(ptr[1])) {
+			len = ptr-remote;
+			ptr++;
+			for (ptr++; *ptr; ptr++)
+				if (!isdigit(*ptr)) {
+					len = 0;
+					break;
+				}
+		}
+	}
+	if (len) {
+		struct strbuf truname = STRBUF_INIT;
+		strbuf_addstr(&truname, remote);
+		strbuf_setlen(&truname, len);
+		if (!get_sha1(truname.buf, buf_sha)) {
+			strbuf_addf(msg,
+				"%s\t\tbranch '%s' (early part) of .\n",
+				sha1_to_hex(remote_head->sha1), truname.buf);
+			return;
+		}
+	}
+
+	if (!strcmp(remote, "FETCH_HEAD") &&
+			!access(git_path("FETCH_HEAD"), R_OK)) {
+		FILE *fp;
+		struct strbuf line;
+		char *ptr;
+
+		strbuf_init(&line, 0);
+		fp = fopen(git_path("FETCH_HEAD"), "r");
+		if (!fp)
+			die("could not open %s for reading: %s",
+				git_path("FETCH_HEAD"), strerror(errno));
+		strbuf_getline(&line, fp, '\n');
+		fclose(fp);
+		ptr = strstr(line.buf, "\tnot-for-merge\t");
+		if (ptr)
+			strbuf_remove(&line, ptr-line.buf+1, 13);
+		strbuf_addbuf(msg, &line);
+		strbuf_release(&line);
+		return;
+	}
+	strbuf_addf(msg, "%s\t\tcommit '%s'\n",
+		sha1_to_hex(remote_head->sha1), remote);
+}
+
+int git_merge_config(const char *k, const char *v, void *cb)
+{
+	if (branch && !prefixcmp(k, "branch.") &&
+		!prefixcmp(k + 7, branch) &&
+		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+		const char **argv;
+		int argc;
+		char *buf;
+
+		buf = xstrdup(v);
+		argc = split_cmdline(buf, &argv);
+		argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+		memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+		argc++;
+		parse_options(argc, argv, builtin_merge_options,
+			      builtin_merge_usage, 0);
+		free(buf);
+	}
+
+	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+		show_diffstat = git_config_bool(k, v);
+	else if (!strcmp(k, "pull.twohead"))
+		return git_config_string(&pull_twohead, k, v);
+	else if (!strcmp(k, "pull.octopus"))
+		return git_config_string(&pull_octopus, k, v);
+	return 0;
+}
+
+static int read_tree_trivial(unsigned char *common, unsigned char *head,
+	unsigned char *one)
+{
+	int i, nr_trees = 0;
+	struct tree *trees[MAX_UNPACK_TREES];
+	struct tree_desc t[MAX_UNPACK_TREES];
+	struct unpack_trees_options opts;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.head_idx = -1;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.verbose_update = 1;
+	opts.trivial_merges_only = 1;
+	opts.merge = 1;
+	trees[nr_trees] = parse_tree_indirect(common);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(head);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(one);
+	if (!trees[nr_trees++])
+		return -1;
+	opts.fn = threeway_merge;
+	cache_tree_free(&active_cache_tree);
+	opts.head_idx = 2;
+	for (i = 0; i < nr_trees; i++) {
+		parse_tree(trees[i]);
+		init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+	}
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+	return 0;
+}
+
+static int commit_tree_trivial(const char *msg, unsigned const char *tree,
+		struct commit_list *parents, unsigned char *ret)
+{
+	struct commit_list *i;
+	struct strbuf buf;
+	int encoding_is_utf8;
+
+	/* Not having i18n.commitencoding is the same as having utf-8 */
+	encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
+
+	strbuf_init(&buf, 8192); /* should avoid reallocs for the headers */
+	strbuf_addf(&buf, "tree %s\n", sha1_to_hex(tree));
+
+	for (i = parents; i; i = i->next)
+		strbuf_addf(&buf, "parent %s\n",
+			sha1_to_hex(i->item->object.sha1));
+
+	/* Person/date information */
+	strbuf_addf(&buf, "author %s\n",
+		git_author_info(IDENT_ERROR_ON_NO_NAME));
+	strbuf_addf(&buf, "committer %s\n",
+		git_committer_info(IDENT_ERROR_ON_NO_NAME));
+	if (!encoding_is_utf8)
+		strbuf_addf(&buf, "encoding %s\n", git_commit_encoding);
+	strbuf_addch(&buf, '\n');
+
+	/* And add the comment */
+	strbuf_addstr(&buf, msg);
+
+	write_sha1_file(buf.buf, buf.len, commit_type, ret);
+	strbuf_release(&buf);
+	return *ret;
+}
+
+static void write_tree_trivial(unsigned char *sha1)
+{
+	if (write_cache_as_tree(sha1, 0, NULL))
+		die("git write-tree failed to write a tree");
+}
+
+static int try_merge_strategy(char *strategy, struct commit_list *common,
+	struct strbuf *head_arg)
+{
+	const char **args;
+	int i = 0, ret;
+	struct commit_list *j;
+	struct strbuf buf;
+
+	args = xmalloc((4 + commit_list_count(common) +
+			commit_list_count(remoteheads)) * sizeof(char *));
+	strbuf_init(&buf, 0);
+	strbuf_addf(&buf, "merge-%s", strategy);
+	args[i++] = buf.buf;
+	for (j = common; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i++] = "--";
+	args[i++] = head_arg->buf;
+	for (j = remoteheads; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i] = NULL;
+	ret = run_command_v_opt(args, RUN_GIT_CMD);
+	strbuf_release(&buf);
+	i = 1;
+	for (j = common; j; j = j->next)
+		free((void *)args[i++]);
+	i += 2;
+	for (j = remoteheads; j; j = j->next)
+		free((void *)args[i++]);
+	free(args);
+	return -ret;
+}
+
+static void count_diff_files(struct diff_queue_struct *q,
+		struct diff_options *opt, void *data)
+{
+	int *count = data;
+
+	(*count) += q->nr;
+}
+
+static int count_unmerged_entries(void)
+{
+	const struct index_state *state = &the_index;
+	int i, ret = 0;
+
+	for (i = 0; i < state->cache_nr; i++)
+		if (ce_stage(state->cache[i]))
+			ret++;
+
+	return ret;
+}
+
+static int merge_one_remote(unsigned char *head, unsigned char *remote)
+{
+	struct tree *trees[MAX_UNPACK_TREES];
+	struct unpack_trees_options opts;
+	struct tree_desc t[MAX_UNPACK_TREES];
+	int i, fd, nr_trees = 0;
+	struct dir_struct dir;
+	struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+	if (read_cache_unmerged())
+		die("you need to resolve your current index first");
+
+	fd = hold_locked_index(lock_file, 1);
+
+	memset(&trees, 0, sizeof(trees));
+	memset(&opts, 0, sizeof(opts));
+	memset(&t, 0, sizeof(t));
+	dir.show_ignored = 1;
+	dir.exclude_per_dir = ".gitignore";
+	opts.dir = &dir;
+
+	opts.head_idx = 1;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.verbose_update = 1;
+	opts.merge = 1;
+	opts.fn = twoway_merge;
+
+	trees[nr_trees] = parse_tree_indirect(head);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(remote);
+	if (!trees[nr_trees++])
+		return -1;
+	for (i = 0; i < nr_trees; i++) {
+		parse_tree(trees[i]);
+		init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+	}
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+	if (write_cache(fd, active_cache, active_nr) ||
+		commit_locked_index(lock_file))
+		die("unable to write new index file");
+	return 0;
+}
+
+static void split_merge_strategies(const char *string, struct path_list *list)
+{
+	char *p, *q, *buf;
+
+	if (!string)
+		return;
+
+	list->strdup_paths = 1;
+	buf = xstrdup(string);
+	q = buf;
+	for (;;) {
+		p = strchr(q, ' ');
+		if (!p) {
+			path_list_append(q, list);
+			free(buf);
+			return;
+		} else {
+			*p = '\0';
+			path_list_append(q, list);
+			q = ++p;
+		}
+	}
+}
+
+static void add_strategies(const char *string, enum strategy strategy)
+{
+	struct path_list list;
+	int i;
+
+	memset(&list, 0, sizeof(list));
+	split_merge_strategies(string, &list);
+	if (list.nr) {
+		for (i = 0; i < list.nr; i++) {
+			struct path_list_item *item;
+
+			item = unsorted_path_list_lookup(list.items[i].path,
+				&strategies);
+			if (item)
+				path_list_append_strategy(item);
+		}
+		return;
+	}
+	for (i = 0; i < strategies.nr; i++)
+		if ((enum strategy)strategies.items[i].util & strategy)
+			path_list_append_strategy(&strategies.items[i]);
+}
+
+int cmd_merge(int argc, const char **argv, const char *prefix)
+{
+	unsigned char sha1[20], result_tree[20];
+	struct object *second_token = NULL;
+	struct strbuf buf, head_arg;
+	int flag, head_invalid, i, single_strategy;
+	int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
+	struct commit_list *common = NULL;
+	struct path_list_item *best_strategy = NULL, *wt_strategy = NULL;
+	struct commit_list **remotes = &remoteheads;
+
+	setup_work_tree();
+	if (unmerged_cache())
+		die("You are in the middle of a conflicted merge.");
+
+	/*
+	 * Check if we are _not_ on a detached HEAD, i.e. if there is a
+	 * current branch.
+	 */
+	branch = resolve_ref("HEAD", sha1, 0, &flag);
+	if (branch && flag & REF_ISSYMREF) {
+		const char *ptr = skip_prefix(branch, "refs/heads/");
+		if (ptr)
+			branch = ptr;
+	}
+
+	git_config(git_merge_config, NULL);
+
+	/* for color.diff and diff.color */
+	git_config(git_diff_ui_config, NULL);
+
+	/* for color.ui */
+	if (diff_use_color_default == -1)
+		diff_use_color_default = git_use_color_default;
+
+	argc = parse_options(argc, argv, builtin_merge_options,
+			builtin_merge_usage, 0);
+
+	if (squash) {
+		if (!allow_fast_forward)
+			die("You cannot combine --squash with --no-ff.");
+		option_commit = 0;
+	}
+
+	if (!argc)
+		usage_with_options(builtin_merge_usage,
+			builtin_merge_options);
+
+	/*
+	 * This could be traditional "merge <msg> HEAD <commit>..."  and
+	 * the way we can tell it is to see if the second token is HEAD,
+	 * but some people might have misused the interface and used a
+	 * committish that is the same as HEAD there instead.
+	 * Traditional format never would have "-m" so it is an
+	 * additional safety measure to check for it.
+	 */
+	strbuf_init(&buf, 0);
+	strbuf_init(&head_arg, 0);
+	if (argc > 1)
+		second_token = peel_to_type(argv[1], 0, NULL, OBJ_COMMIT);
+	head_invalid = get_sha1("HEAD", head);
+
+	if (!have_message && second_token &&
+		!hashcmp(second_token->sha1, head)) {
+		strbuf_addstr(&merge_msg, argv[0]);
+		strbuf_addstr(&head_arg, argv[1]);
+		argv += 2;
+		argc -= 2;
+	} else if (head_invalid) {
+		struct object *remote_head;
+		/*
+		 * If the merged head is a valid one there is no reason
+		 * to forbid "git merge" into a branch yet to be born.
+		 * We do the same for "git pull".
+		 */
+		if (argc != 1)
+			die("Can merge only exactly one commit into "
+				"empty head");
+		remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+		if (!remote_head)
+			die("%s - not something we can merge", argv[0]);
+		update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
+				DIE_ON_ERR);
+		reset_hard(remote_head->sha1, 0);
+		return 0;
+	} else {
+		/* We are invoked directly as the first-class UI. */
+		strbuf_addstr(&head_arg, "HEAD");
+		/*
+		 * All the rest are the commits being merged;
+		 * prepare the standard merge summary message to
+		 * be appended to the given message.  If remote
+		 * is invalid we will die later in the common
+		 * codepath so we discard the error in this
+		 * loop.
+		 */
+		struct strbuf msg;
+
+		strbuf_init(&msg, 0);
+		for (i = 0; i < argc; i++)
+			merge_name(argv[i], &msg);
+		fmt_merge_msg(option_log, &msg, &merge_msg);
+		if (merge_msg.len)
+			strbuf_setlen(&merge_msg, merge_msg.len-1);
+	}
+
+	if (head_invalid || !argc)
+		usage_with_options(builtin_merge_usage,
+			builtin_merge_options);
+
+	strbuf_addstr(&buf, "merge");
+	for (i = 0; i < argc; i++)
+		strbuf_addf(&buf, " %s", argv[i]);
+	setenv("GIT_REFLOG_ACTION", buf.buf, 0);
+	strbuf_reset(&buf);
+
+	for (i = 0; i < argc; i++) {
+		struct object *o;
+
+		o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
+		if (!o)
+			die("%s - not something we can merge", argv[i]);
+		remotes = &commit_list_insert(lookup_commit(o->sha1),
+			remotes)->next;
+
+		strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
+		setenv(buf.buf, argv[i], 1);
+		strbuf_reset(&buf);
+	}
+
+	if (!use_strategies.nr) {
+		if (!remoteheads->next)
+			add_strategies(pull_twohead, DEFAULT_TWOHEAD);
+		else
+			add_strategies(pull_octopus, DEFAULT_OCTOPUS);
+	}
+
+	for (i = 0; i < use_strategies.nr; i++) {
+		if ((unsigned int)use_strategies.items[i].util &
+			NO_FAST_FORWARD)
+			allow_fast_forward = 0;
+		if ((unsigned int)use_strategies.items[i].util & NO_TRIVIAL)
+			allow_trivial = 0;
+	}
+
+	if (!remoteheads->next)
+		common = get_merge_bases(lookup_commit(head),
+				remoteheads->item, 1);
+	else {
+		struct commit_list *list = remoteheads;
+		commit_list_insert(lookup_commit(head), &list);
+		common = get_octopus_merge_bases(list);
+		free(list);
+	}
+
+	update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
+		DIE_ON_ERR);
+
+	if (!common)
+		; /* No common ancestors found. We need a real merge. */
+	else if (!remoteheads->next &&
+		!hashcmp(common->item->object.sha1,
+		remoteheads->item->object.sha1)) {
+		/*
+		 * If head can reach all the remote heads then we are up
+		 * to date.
+		 */
+		finish_up_to_date("Already up-to-date.");
+		return 0;
+	} else if (allow_fast_forward && !remoteheads->next &&
+		!hashcmp(common->item->object.sha1, head)) {
+		/* Again the most common case of merging one remote. */
+		struct strbuf msg;
+		struct object *o;
+		char hex[41];
+
+		strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+
+		printf("Updating %s..%s\n",
+			hex,
+			find_unique_abbrev(remoteheads->item->object.sha1,
+			DEFAULT_ABBREV));
+		refresh_cache(REFRESH_QUIET);
+		strbuf_init(&msg, 0);
+		strbuf_addstr(&msg, "Fast forward");
+		if (have_message)
+			strbuf_addstr(&msg,
+				" (no commit created; -m option ignored)");
+		o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
+			0, NULL, OBJ_COMMIT);
+		if (!o)
+			return 0;
+
+		if (merge_one_remote(head, remoteheads->item->object.sha1))
+			return 0;
+
+		finish(o->sha1, msg.buf);
+		dropsave();
+		return 0;
+	} else if (!remoteheads->next && common->next)
+		;
+		/*
+		 * We are not doing octopus and not fast forward.  Need
+		 * a real merge.
+		 */
+	else if (!remoteheads->next && option_commit) {
+		/*
+		 * We are not doing octopus, not fast forward, and have
+		 * only one common.
+		 */
+		refresh_cache(REFRESH_QUIET);
+		if (allow_trivial) {
+			/* See if it is really trivial. */
+			git_committer_info(IDENT_ERROR_ON_NO_NAME);
+			printf("Trying really trivial in-index merge...\n");
+			if (!read_tree_trivial(common->item->object.sha1,
+					head, remoteheads->item->object.sha1)) {
+				unsigned char result_tree[20],
+					result_commit[20];
+				struct commit_list parent;
+
+				write_tree_trivial(result_tree);
+				printf("Wonderful.\n");
+				parent.item = remoteheads->item;
+				parent.next = NULL;
+				commit_tree_trivial(merge_msg.buf,
+					result_tree, &parent,
+					result_commit);
+				finish(result_commit, "In-index merge");
+				dropsave();
+				return 0;
+			}
+			printf("Nope.\n");
+		}
+	} else {
+		/*
+		 * An octopus.  If we can reach all the remote we are up
+		 * to date.
+		 */
+		int up_to_date = 1;
+		struct commit_list *j;
+
+		for (j = remoteheads; j; j = j->next) {
+			struct commit_list *common_one;
+
+			/*
+			 * Here we *have* to calculate the individual
+			 * merge_bases again, otherwise "git merge HEAD^
+			 * HEAD^^" would be missed.
+			 */
+			common_one = get_merge_bases(lookup_commit(head),
+				j->item, 1);
+			if (hashcmp(common_one->item->object.sha1,
+				j->item->object.sha1)) {
+				up_to_date = 0;
+				break;
+			}
+		}
+		if (up_to_date) {
+			finish_up_to_date("Already up-to-date. Yeeah!");
+			return 0;
+		}
+	}
+
+	/* We are going to make a new commit. */
+	git_committer_info(IDENT_ERROR_ON_NO_NAME);
+
+	/*
+	 * At this point, we need a real merge.  No matter what strategy
+	 * we use, it would operate on the index, possibly affecting the
+	 * working tree, and when resolved cleanly, have the desired
+	 * tree in the index -- this means that the index must be in
+	 * sync with the head commit.  The strategies are responsible
+	 * to ensure this.
+	 */
+	if (use_strategies.nr != 1) {
+		/*
+		 * Stash away the local changes so that we can try more
+		 * than one.
+		 */
+		save_state();
+		single_strategy = 0;
+	} else {
+		unlink(git_path("MERGE_STASH"));
+		single_strategy = 1;
+	}
+
+	for (i = 0; i < use_strategies.nr; i++) {
+		int ret;
+		if (i) {
+			printf("Rewinding the tree to pristine...\n");
+			restore_state();
+		}
+		if (!single_strategy)
+			printf("Trying merge strategy %s...\n",
+				use_strategies.items[i].path);
+		/*
+		 * Remember which strategy left the state in the working
+		 * tree.
+		 */
+		wt_strategy = &use_strategies.items[i];
+
+		ret = try_merge_strategy(use_strategies.items[i].path,
+			common, &head_arg);
+		if (!option_commit && !ret) {
+			merge_was_ok = 1;
+			/*
+			 * This is necessary here just to avoid writing
+			 * the tree, but later we will *not* exit with
+			 * status code 1 because merge_was_ok is set.
+			 */
+			ret = 1;
+		}
+
+		if (ret) {
+			/*
+			 * The backend exits with 1 when conflicts are
+			 * left to be resolved, with 2 when it does not
+			 * handle the given merge at all.
+			 */
+			if (ret == 1) {
+				int cnt = 0;
+				struct rev_info rev;
+
+				if (read_cache() < 0)
+					die("failed to read the cache");
+
+				/* Check how many files differ. */
+				init_revisions(&rev, "");
+				setup_revisions(0, NULL, &rev, NULL);
+				rev.diffopt.output_format |=
+					DIFF_FORMAT_CALLBACK;
+				rev.diffopt.format_callback = count_diff_files;
+				rev.diffopt.format_callback_data = &cnt;
+				run_diff_files(&rev, 0);
+
+				/*
+				 * Check how many unmerged entries are
+				 * there.
+				 */
+				cnt += count_unmerged_entries();
+
+				if (best_cnt <= 0 || cnt <= best_cnt) {
+					best_strategy =
+						&use_strategies.items[i];
+					best_cnt = cnt;
+				}
+			}
+			if (merge_was_ok)
+				break;
+			else
+				continue;
+		}
+
+		/* Automerge succeeded. */
+		write_tree_trivial(result_tree);
+		automerge_was_ok = 1;
+		break;
+	}
+
+	/*
+	 * If we have a resulting tree, that means the strategy module
+	 * auto resolved the merge cleanly.
+	 */
+	if (automerge_was_ok) {
+		struct commit_list *parents = NULL, *j;
+		unsigned char result_commit[20];
+
+		free_commit_list(common);
+		if (allow_fast_forward) {
+			parents = remoteheads;
+			commit_list_insert(lookup_commit(head), &parents);
+			parents = reduce_heads(parents);
+		} else {
+			struct commit_list **pptr = &parents;
+
+			pptr = &commit_list_insert(lookup_commit(head),
+				pptr)->next;
+			for (j = remoteheads; j; j = j->next)
+				pptr = &commit_list_insert(j->item, pptr)->next;
+		}
+		free_commit_list(remoteheads);
+		strbuf_addch(&merge_msg, '\n');
+		commit_tree_trivial(merge_msg.buf, result_tree, parents,
+			result_commit);
+		free_commit_list(parents);
+		strbuf_addf(&buf, "Merge made by %s.", wt_strategy->path);
+		finish(result_commit, buf.buf);
+		strbuf_release(&buf);
+		dropsave();
+		return 0;
+	}
+
+	/*
+	 * Pick the result from the best strategy and have the user fix
+	 * it up.
+	 */
+	if (!best_strategy) {
+		restore_state();
+		if (use_strategies.nr > 1)
+			fprintf(stderr,
+				"No merge strategy handled the merge.\n");
+		else
+			fprintf(stderr, "Merge with strategy %s failed.\n",
+				use_strategies.items[0].path);
+		return 2;
+	} else if (best_strategy == wt_strategy)
+		; /* We already have its result in the working tree. */
+	else {
+		printf("Rewinding the tree to pristine...\n");
+		restore_state();
+		printf("Using the %s to prepare resolving by hand.\n",
+			best_strategy->path);
+		try_merge_strategy(best_strategy->path, common, &head_arg);
+	}
+
+	if (squash)
+		finish(NULL, NULL);
+	else {
+		int fd;
+		struct commit_list *j;
+
+		for (j = remoteheads; j; j = j->next)
+			strbuf_addf(&buf, "%s\n",
+				sha1_to_hex(j->item->object.sha1));
+		fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
+		if (fd < 0)
+			die("Could open %s for writing",
+				git_path("MERGE_HEAD"));
+		if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+			die("Could not write to %s", git_path("MERGE_HEAD"));
+		close(fd);
+		strbuf_addch(&merge_msg, '\n');
+		fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+		if (fd < 0)
+			die("Could open %s for writing", git_path("MERGE_MSG"));
+		if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
+			merge_msg.len)
+			die("Could not write to %s", git_path("MERGE_MSG"));
+		close(fd);
+	}
+
+	if (merge_was_ok) {
+		fprintf(stderr, "Automatic merge went well; "
+			"stopped before committing as requested\n");
+		return 0;
+	} else {
+		FILE *fp;
+		int pos;
+		const char *argv_rerere[] = { "rerere", NULL };
+
+		fp = fopen(git_path("MERGE_MSG"), "a");
+		if (!fp)
+			die("Could open %s for writing", git_path("MERGE_MSG"));
+		fprintf(fp, "\nConflicts:\n");
+		for (pos = 0; pos < active_nr; pos++) {
+			struct cache_entry *ce = active_cache[pos];
+
+			if (ce_stage(ce)) {
+				fprintf(fp, "\t%s\n", ce->name);
+				while (pos + 1 < active_nr &&
+					!strcmp(ce->name,
+					active_cache[pos + 1]->name))
+					pos++;
+			}
+		}
+		fclose(fp);
+		run_command_v_opt(argv_rerere, RUN_GIT_CMD);
+		printf("Automatic merge failed; "
+			"fix conflicts and then commit the result.\n");
+		return 1;
+	}
+}
diff --git a/builtin.h b/builtin.h
index 2b01fea..8bf5280 100644
--- a/builtin.h
+++ b/builtin.h
@@ -60,6 +60,7 @@ extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_ls_remote(int argc, const char **argv, const char *prefix);
 extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
 extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
+extern int cmd_merge(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
diff --git a/git-merge.sh b/contrib/examples/git-merge.sh
similarity index 100%
rename from git-merge.sh
rename to contrib/examples/git-merge.sh
diff --git a/git.c b/git.c
index 2fbe96b..770aadd 100644
--- a/git.c
+++ b/git.c
@@ -271,6 +271,7 @@ static void handle_internal_command(int argc, const char **argv)
 		{ "ls-remote", cmd_ls_remote },
 		{ "mailinfo", cmd_mailinfo },
 		{ "mailsplit", cmd_mailsplit },
+		{ "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
 		{ "merge-base", cmd_merge_base, RUN_SETUP },
 		{ "merge-file", cmd_merge_file },
 		{ "merge-ours", cmd_merge_ours, RUN_SETUP },
-- 
1.5.6

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf()
  2008-06-28  2:00                       ` [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf() Junio C Hamano
  2008-06-28  2:33                         ` Miklos Vajna
@ 2008-06-28 17:33                         ` Johannes Schindelin
  2008-06-29  8:07                           ` Junio C Hamano
  1 sibling, 1 reply; 81+ messages in thread
From: Johannes Schindelin @ 2008-06-28 17:33 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Miklos Vajna, git, Olivier Marin

Hi,

On Fri, 27 Jun 2008, Junio C Hamano wrote:

> Do you really think these two patches belong to the series, I seriously 
> have to wonder?

Heh.  I suggested it, as I thought this was a perfect excuse to introduce 
strbuf_initf(), which has been on my wishlist since long.

Anyway, I'm fine with having them only in my personal fork.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 13/13] Build in merge
  2008-06-28  2:38                           ` [PATCH 13/13] Build in merge Miklos Vajna
@ 2008-06-29  7:46                             ` Junio C Hamano
  2008-06-29  8:11                               ` Jakub Narebski
  2008-06-30  1:36                               ` Miklos Vajna
  0 siblings, 2 replies; 81+ messages in thread
From: Junio C Hamano @ 2008-06-29  7:46 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: git, Johannes Schindelin, Olivier Marin

Miklos Vajna <vmiklos@frugalware.org> writes:

> Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
> Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
> ---
>
> On Sat, Jun 28, 2008 at 04:33:55AM +0200, Miklos Vajna <vmiklos@frugalware.org> wrote:
>> OK, I'll send the main patch without the strbuf_initf() calls
>
> Here it is.

Sorry, this file is huge and very time consuming to review.  Here are some
pieces.  The parts I did not comment I haven't looked at.

> +/* Get the name for the merge commit's message. */
> +static void merge_name(const char *remote, struct strbuf *msg)
> +{
> +	struct object *remote_head;
> +	unsigned char branch_head[20], buf_sha[20];
> +	struct strbuf buf;
> +	char *ptr;
> +	int len = 0;
> +
> +	memset(branch_head, 0, sizeof(branch_head));
> +	remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
> +	if (!remote_head)
> +		return;

Hmm.  This is a faithful translation of scripted version, but I wonder
what should happen when we got a non-commit here...

> +
> +	strbuf_init(&buf, 0);
> +	strbuf_addstr(&buf, "refs/heads/");
> +	strbuf_addstr(&buf, remote);
> +	get_sha1(buf.buf, branch_head);

This does not correspond to the computation of $bh in the scripted version
that makes sure "remote" is actually a bare name of branch, e.g. "master",
without any adornment like "master~5^3~8.  Your code would succeed and
leave the same object name in branch_head[] as remote_head->sha1, wouldn't
it?

> +	if (!hashcmp(remote_head->sha1, branch_head)) {
> +		strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
> +			sha1_to_hex(branch_head), remote);
> +		return;
> +	}
> +	/* See if remote matches <name>~<number>, or <name>^ */

The scripted version did not handle <name>^, so this is an extension.
Don't you want also handle <name>^^^ if we are extending it?

> +	ptr = strrchr(remote, '^');
> +	if (ptr && ptr[1] == '\0')
> +		len = ptr-remote;
> +	else {
> +		ptr = strrchr(remote, '~');
> +		if (ptr && ptr[1] != '0' && isdigit(ptr[1])) {
> +			len = ptr-remote;
> +			ptr++;
> +			for (ptr++; *ptr; ptr++)
> +				if (!isdigit(*ptr)) {
> +					len = 0;
> +					break;
> +				}
> +		}
> +	}
> +	if (len) {
> +		struct strbuf truname = STRBUF_INIT;
> +		strbuf_addstr(&truname, remote);
> +		strbuf_setlen(&truname, len);
> +		if (!get_sha1(truname.buf, buf_sha)) {

Again, isn't this wrong?  You are not making sure truname is the name of
existing local branch.  HEAD@{7}~23 will pass get_sha1() but you are not
merging an early part of HEAD@{7} branch.

> +			strbuf_addf(msg,
> +				"%s\t\tbranch '%s' (early part) of .\n",
> +				sha1_to_hex(remote_head->sha1), truname.buf);
> +			return;
> +		}
> +	}

> +int cmd_merge(int argc, const char **argv, const char *prefix)
> +{

This is an ultra-huge function.  I wonder if it can further split up to
make it easier to maintain.

> +	unsigned char sha1[20], result_tree[20];
> +	struct object *second_token = NULL;
> +	struct strbuf buf, head_arg;
> +	int flag, head_invalid, i, single_strategy;
> +	int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
> +	struct commit_list *common = NULL;
> +	struct path_list_item *best_strategy = NULL, *wt_strategy = NULL;
> +	struct commit_list **remotes = &remoteheads;
> +
> +	setup_work_tree();
> +	if (unmerged_cache())
> +		die("You are in the middle of a conflicted merge.");
> +
> +	/*
> +	 * Check if we are _not_ on a detached HEAD, i.e. if there is a
> +	 * current branch.
> +	 */
> +	branch = resolve_ref("HEAD", sha1, 0, &flag);

You are resolving for non-reading, so before an initial commit, you won't
get any error from this call, which is good.  And sha1[] will be cleared.
Otherwise you learn where the HEAD is in sha1[] at this point.

> +	if (branch && flag & REF_ISSYMREF) {
> +		const char *ptr = skip_prefix(branch, "refs/heads/");
> +		if (ptr)
> +			branch = ptr;
> +	}
> +
> +	git_config(git_merge_config, NULL);
> +
> +	/* for color.diff and diff.color */
> +	git_config(git_diff_ui_config, NULL);
> +
> +	/* for color.ui */
> +	if (diff_use_color_default == -1)
> +		diff_use_color_default = git_use_color_default;
> +
> +	argc = parse_options(argc, argv, builtin_merge_options,
> +			builtin_merge_usage, 0);
> +
> +	if (squash) {
> +		if (!allow_fast_forward)
> +			die("You cannot combine --squash with --no-ff.");
> +		option_commit = 0;
> +	}
> +
> +	if (!argc)
> +		usage_with_options(builtin_merge_usage,
> +			builtin_merge_options);
> +
> +	/*
> +	 * This could be traditional "merge <msg> HEAD <commit>..."  and
> +	 * the way we can tell it is to see if the second token is HEAD,
> +	 * but some people might have misused the interface and used a
> +	 * committish that is the same as HEAD there instead.
> +	 * Traditional format never would have "-m" so it is an
> +	 * additional safety measure to check for it.
> +	 */
> +	strbuf_init(&buf, 0);
> +	strbuf_init(&head_arg, 0);
> +	if (argc > 1)
> +		second_token = peel_to_type(argv[1], 0, NULL, OBJ_COMMIT);

If the second token was a string that could resolve to an object name that
does not peel to commit (say "merge -m 'HEAD^{tree}' other"), you will get
a complaint fro mpeel-to-type "I expected a commit but you gave something
else".  You (or more likely Dscho) might have said "that won't matter in
practice", but I think you should really do get_sha1() followed by
lookup_commit_reference_gently() here to avoid the errors.

> +	head_invalid = get_sha1("HEAD", head);

You've already done this earlier with resolve_ref() haven't you?

> +	if (!have_message && second_token &&
> +		!hashcmp(second_token->sha1, head)) {

Isn't this wrong if head_invalid is true?

> +		strbuf_addstr(&merge_msg, argv[0]);
> +		strbuf_addstr(&head_arg, argv[1]);
> +		argv += 2;
> +		argc -= 2;

I do not think there is any point using strbuf for head_arg.  Shouldn't it
simply be a "const char *"?

> +	} else if (head_invalid) {
> +		struct object *remote_head;
> +		/*
> +		 * If the merged head is a valid one there is no reason
> +		 * to forbid "git merge" into a branch yet to be born.
> +		 * We do the same for "git pull".
> +		 */
> +		if (argc != 1)
> +			die("Can merge only exactly one commit into "
> +				"empty head");
> +		remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);

Here we _do_ expect it to be a commit, so peel_to_type() is fine.

> +		if (!remote_head)
> +			die("%s - not something we can merge", argv[0]);
> +		update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
> +				DIE_ON_ERR);
> +		reset_hard(remote_head->sha1, 0);
> +		return 0;

Makes one wonder reset_hard() (aka "read-tree --reset -u HEAD") ever fail
and return here (iow, without calling die()).  The answer is luckily no
in this case, but it is somewhat unnerving to reviewers.

> +	} else {
> +		/* We are invoked directly as the first-class UI. */
> +		strbuf_addstr(&head_arg, "HEAD");
> +		/*
> +		 * All the rest are the commits being merged;
> +		 * prepare the standard merge summary message to
> +		 * be appended to the given message.  If remote
> +		 * is invalid we will die later in the common
> +		 * codepath so we discard the error in this
> +		 * loop.
> +		 */
> +		struct strbuf msg;

Decl-after-statement.

> +		strbuf_init(&msg, 0);
> +		for (i = 0; i < argc; i++)
> +			merge_name(argv[i], &msg);
> +		fmt_merge_msg(option_log, &msg, &merge_msg);
> +		if (merge_msg.len)
> +			strbuf_setlen(&merge_msg, merge_msg.len-1);
> +	}
> +
> +	if (head_invalid || !argc)
> +		usage_with_options(builtin_merge_usage,
> +			builtin_merge_options);
> +	strbuf_addstr(&buf, "merge");
> +	for (i = 0; i < argc; i++)
> +		strbuf_addf(&buf, " %s", argv[i]);
> +	setenv("GIT_REFLOG_ACTION", buf.buf, 0);
> +	strbuf_reset(&buf);
> +
> +	for (i = 0; i < argc; i++) {
> +		struct object *o;
> +
> +		o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
> +		if (!o)
> +			die("%s - not something we can merge", argv[i]);
> +		remotes = &commit_list_insert(lookup_commit(o->sha1),
> +			remotes)->next;
> +
> +		strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
> +		setenv(buf.buf, argv[i], 1);
> +		strbuf_reset(&buf);
> +	}
> +
> +	if (!use_strategies.nr) {
> +		if (!remoteheads->next)
> +			add_strategies(pull_twohead, DEFAULT_TWOHEAD);
> +		else
> +			add_strategies(pull_octopus, DEFAULT_OCTOPUS);
> +	}
> +
> +	for (i = 0; i < use_strategies.nr; i++) {
> +		if ((unsigned int)use_strategies.items[i].util &
> +			NO_FAST_FORWARD)
> +			allow_fast_forward = 0;
> +		if ((unsigned int)use_strategies.items[i].util & NO_TRIVIAL)
> +			allow_trivial = 0;

Can we abstract out these ugly casts?  Any code that use path_list to
store anything but list of paths (i.e. some value keyed with string) tends
to have this readability issue.

> +	}
> +
> +	if (!remoteheads->next)
> +		common = get_merge_bases(lookup_commit(head),
> +				remoteheads->item, 1);
> +	else {
> +		struct commit_list *list = remoteheads;
> +		commit_list_insert(lookup_commit(head), &list);
> +		common = get_octopus_merge_bases(list);
> +		free(list);
> +	}
> +
> +	update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
> +		DIE_ON_ERR);
> +
> +	if (!common)
> +		; /* No common ancestors found. We need a real merge. */
> +	else if (!remoteheads->next &&
> +		!hashcmp(common->item->object.sha1,
> +		remoteheads->item->object.sha1)) {

Wouldn't the latter be "common->item == remoteheads->item" simply?

You do not have the check to make sure there is only one common ancestor
(scripted version compares $common and $1 textually to achieve this), and
checking only the first one of them.  Is this correct?

> +		/*
> +		 * If head can reach all the remote heads then we are up
> +		 * to date.
> +		 */

The comment is wrong --- you are doing "... but first the most common case
of merging one remote" here.

> +		finish_up_to_date("Already up-to-date.");
> +		return 0;
> +	} else if (allow_fast_forward && !remoteheads->next &&
> +		!hashcmp(common->item->object.sha1, head)) {
> +		/* Again the most common case of merging one remote. */

Here again you are not checking there is only one common, and checking
only the first one of them.

> +		struct strbuf msg;
> +		struct object *o;
> +		char hex[41];
> +
> +		strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
> +
> +		printf("Updating %s..%s\n",
> +			hex,
> +			find_unique_abbrev(remoteheads->item->object.sha1,
> +			DEFAULT_ABBREV));
> +		refresh_cache(REFRESH_QUIET);
> +		strbuf_init(&msg, 0);
> +		strbuf_addstr(&msg, "Fast forward");
> +		if (have_message)
> +			strbuf_addstr(&msg,
> +				" (no commit created; -m option ignored)");
> +		o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
> +			0, NULL, OBJ_COMMIT);
> +		if (!o)
> +			return 0;
> +
> +		if (merge_one_remote(head, remoteheads->item->object.sha1))
> +			return 0;

Isn't "merge_one_remote()" just a "git checkout" after fast-forward?  The
function feels misnamed.

> +		finish(o->sha1, msg.buf);
> +		dropsave();
> +		return 0;
> +	} else if (!remoteheads->next && common->next)
> +		;

Here you are checking common->next but earlier if/elseif chain didn't so
it is too late.

> +		/*
> +		 * We are not doing octopus and not fast forward.  Need
> +		 * a real merge.
> +		 */
> +	else if (!remoteheads->next && option_commit) {
> +		/*
> +		 * We are not doing octopus, not fast forward, and have
> +		 * only one common.

Here again you did not check "have only one common" did you?

> +		 */
> +		refresh_cache(REFRESH_QUIET);
> +		if (allow_trivial) {
> +			/* See if it is really trivial. */
> +			git_committer_info(IDENT_ERROR_ON_NO_NAME);
> +			printf("Trying really trivial in-index merge...\n");
> +			if (!read_tree_trivial(common->item->object.sha1,
> +					head, remoteheads->item->object.sha1)) {
> +				unsigned char result_tree[20],
> +					result_commit[20];
> +				struct commit_list parent;
> +
> +				write_tree_trivial(result_tree);
> +				printf("Wonderful.\n");
> +				parent.item = remoteheads->item;
> +				parent.next = NULL;
> +				commit_tree_trivial(merge_msg.buf,
> +					result_tree, &parent,
> +					result_commit);
> +				finish(result_commit, "In-index merge");
> +				dropsave();
> +				return 0;
> +			}
> +			printf("Nope.\n");
> +		}

There weren't any good way to squelch error messages selectively from the
trivial one in the scripted version and that is the only reason we
surround read-tree with "Trying..." and "Wonderful/Nope.".  Literal
translation to make sure you get identical output in the first round of
this series is good, but after the code stabilizes, we may want to squelch
these messages.  Something to keep in mind but not now.

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf()
  2008-06-28 17:33                         ` [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf() Johannes Schindelin
@ 2008-06-29  8:07                           ` Junio C Hamano
  2008-06-29 13:40                             ` Johannes Schindelin
  0 siblings, 1 reply; 81+ messages in thread
From: Junio C Hamano @ 2008-06-29  8:07 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Miklos Vajna, git, Olivier Marin

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> Heh.  I suggested it, as I thought this was a perfect excuse to introduce 
> strbuf_initf(), which has been on my wishlist since long.

We may want to consider the change for its own merit, if there is a reason
for the vaddf patch, such as "this is needed because platform X and Y do
not have working vsnprintf()".  Perhaps we add compat/* something.

> Anyway, I'm fine with having them only in my personal fork.

It will cost you some "trust point" next time you try to sneak something
in as a part of a largely unrelated topic.  Please don't.

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 13/13] Build in merge
  2008-06-29  7:46                             ` Junio C Hamano
@ 2008-06-29  8:11                               ` Jakub Narebski
  2008-06-30  1:36                               ` Miklos Vajna
  1 sibling, 0 replies; 81+ messages in thread
From: Jakub Narebski @ 2008-06-29  8:11 UTC (permalink / raw)
  To: git

Junio C Hamano wrote:
> Miklos Vajna <vmiklos@frugalware.org> writes:

>> +             if (!remote_head)
>> +                     die("%s - not something we can merge", argv[0]);
>> +             update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
>> +                             DIE_ON_ERR);
>> +             reset_hard(remote_head->sha1, 0);
>> +             return 0;
> 
> Makes one wonder reset_hard() (aka "read-tree --reset -u HEAD") ever fail
> and return here (iow, without calling die()).  The answer is luckily no
> in this case, but it is somewhat unnerving to reviewers.

Do we have some guidelines on how to mark such non-returning calls,
or how to mark unreachable code (splint, formerly lclint, uses for
this /*@ unreachable @*/ annotation)?
-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 04/15] Add new test to ensure git-merge handles pull.twohead and pull.octopus
  2008-06-27 16:21       ` [PATCH 04/15] Add new test to ensure git-merge handles pull.twohead and pull.octopus Miklos Vajna
  2008-06-27 16:21         ` [PATCH 05/15] Move read_cache_unmerged() to read-cache.c Miklos Vajna
@ 2008-06-29 13:30         ` Olivier Marin
  2008-06-29 14:51           ` [PATCH] " Miklos Vajna
  2008-07-04 16:34         ` [PATCH 04/15] " Mike Ralphson
  2 siblings, 1 reply; 81+ messages in thread
From: Olivier Marin @ 2008-06-29 13:30 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: Junio C Hamano, git, Johannes Schindelin, Olivier Marin

Miklos Vajna a écrit :
> 

[...]

> +test_expect_success 'setup' '
> +	echo c0 >c0.c &&
> +	git add c0.c &&
> +	git commit -m c0 &&
> +	git tag c0 &&
> +	echo c1 >c1.c &&
> +	git add c1.c &&
> +	git commit -m c1 &&
> +	git tag c1 &&
> +	git reset --hard c0 &&
> +	echo c2 >c2.c &&
> +	git add c2.c &&
> +	git commit -m c2 &&
> +	git tag c2

Missing &&?

> +	git reset --hard c0 &&
> +	echo c3 >c3.c &&
> +	git add c3.c &&
> +	git commit -m c3 &&
> +	git tag c3
> +'

[...]

> +test_expect_success 'merge c1 with c2 and c3 (recursive and octopus in pull.octopus)' '
> +	git reset --hard c1 &&
> +	git config pull.octopus "recursive octopus" &&
> +	git merge c2 c3 &&
> +	test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
> +	test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
> +	test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
> +	test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)"

Duplicate the next line?

> +	test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
> +	git diff --exit-code &&
> +	test -f c0.c &&
> +	test -f c1.c &&
> +	test -f c2.c &&
> +	test -f c3.c
> +'
> +
> +conflict_count()
> +{
> +	eval $1=`{
> +		git diff-files --name-only
> +		git ls-files --unmerged
> +	} | wc -l`
> +}
> +
> +# c4 - c5
> +#    \ c6
> +#
> +# There are two conflicts here:
> +#
> +# 1) Because foo.c is renamed to bar.c, recursive will handle this,
> +# resolve won't.
> +#
> +# 2) One in conflict.c and that will always fail.
> +
> +test_expect_success 'setup conflicted merge' '
> +	git reset --hard c0 &&
> +	echo A >conflict.c &&
> +	git add conflict.c &&
> +	echo contents >foo.c &&
> +	git add foo.c &&
> +	git commit -m c4 &&
> +	git tag c4 &&
> +	echo B >conflict.c &&
> +	git add conflict.c &&
> +	git mv foo.c bar.c &&
> +	git commit -m c5 &&
> +	git tag c5 &&
> +	git reset --hard c4 &&
> +	echo C >conflict.c &&
> +	git add conflict.c &&
> +	echo secondline >> foo.c &&
> +	git add foo.c &&
> +	git commit -m c6 &&
> +	git tag c6
> +'
> +
> +# First do the merge with resolve and recursive then verify that
> +# recusive is choosen.
> +
> +test_expect_success 'merge picks up the best result' '
> +	git config pull.twohead "recursive resolve" &&
> +	git reset --hard c5 &&
> +	git merge -s resolve c6
> +	conflict_count resolve_count &&
> +	git reset --hard c5 &&
> +	git merge -s recursive c6
> +	conflict_count recursive_count &&
> +	git reset --hard c5 &&
> +	git merge c6
> +	conflict_count auto_count &&
> +	test "$auto_count" = "$recursive_count"
> +'

Should not "$auto_count" != "$resolve_count" also be tested to be
sure that recursive has been choosen?

Olivier.

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf()
  2008-06-29  8:07                           ` Junio C Hamano
@ 2008-06-29 13:40                             ` Johannes Schindelin
  2008-06-29 20:17                               ` Alex Riesen
  0 siblings, 1 reply; 81+ messages in thread
From: Johannes Schindelin @ 2008-06-29 13:40 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Miklos Vajna, git, Olivier Marin

Hi,

On Sun, 29 Jun 2008, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > Anyway, I'm fine with having them only in my personal fork.
> 
> It will cost you some "trust point" next time you try to sneak something
> in as a part of a largely unrelated topic.  Please don't.

Fine.  Fine!

I thought it made the code more readable, and that it also might have 
helped other sites where you have to strbuf_init() first, and often need 
curly brackets only because of that.  Making for ultra-ugly code.

Please note that I also added a simple test case for the vaddf() function, 
so your "trying to sneak through" is only half as bad as you made it 
sound.

Also, please note that the situation with SNPRINTF_RETURNS_BOGUS works, 
but is a thorn in this developers eye.

Anyway, I will not try to post this patch again,
Dscho

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 01/15] Move split_cmdline() to alias.c
  2008-06-27 16:21 ` [PATCH 01/15] Move split_cmdline() to alias.c Miklos Vajna
  2008-06-27 16:21   ` [PATCH 02/15] Move commit_list_count() to commit.c Miklos Vajna
@ 2008-06-29 14:05   ` Olivier Marin
  2008-06-29 14:15     ` Johannes Schindelin
  1 sibling, 1 reply; 81+ messages in thread
From: Olivier Marin @ 2008-06-29 14:05 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: Junio C Hamano, git, Johannes Schindelin, Olivier Marin

Miklos Vajna a écrit :
> split_cmdline() is currently used for aliases only, but later it can be
> useful for other builtins as well. Move it to alias.c for now,
> indicating that originally it's for aliases, but we'll have it in libgit
> this way.

This function does not trim cmdline. 

Perhaps, the following patch can be inserted after 1/15.

-- >8 --
From: Olivier Marin <dkr@freesurf.fr>
Date: Sat, 28 Jun 2008 13:06:21 +0200
Subject: [PATCH] split_cmdline(): ignore whitespace at start/end of cmdline

Signed-off-by: Olivier Marin <dkr@freesurf.fr>
---
 alias.c |   11 +++++++----
 1 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/alias.c b/alias.c
index ccb1108..7b69d18 100644
--- a/alias.c
+++ b/alias.c
@@ -24,14 +24,16 @@ char *alias_lookup(const char *alias)
 
 int split_cmdline(char *cmdline, const char ***argv)
 {
-       int src, dst, count = 0, size = 16;
+       int src = 0, dst, count = 0, size = 16;
        char quoted = 0;
 
        *argv = xmalloc(sizeof(char*) * size);
 
        /* split alias_string */
-       (*argv)[count++] = cmdline;
-       for (src = dst = 0; cmdline[src];) {
+       while (cmdline[src] && isspace(cmdline[src]))
+               src++;
+       (*argv)[count++] = cmdline + src;
+       for (dst = src; cmdline[src];) {
                char c = cmdline[src];
                if (!quoted && isspace(c)) {
                        cmdline[dst++] = 0;
@@ -42,7 +44,8 @@ int split_cmdline(char *cmdline, const char ***argv)
                                size += 16;
                                *argv = xrealloc(*argv, sizeof(char*) * size);
                        }
-                       (*argv)[count++] = cmdline + dst;
+                       if (cmdline[src])
+                               (*argv)[count++] = cmdline + dst;
                } else if (!quoted && (c == '\'' || c == '"')) {
                        quoted = c;
                        src++;
-- 
1.5.6.1.103.g191a2.dirty

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH 01/15] Move split_cmdline() to alias.c
  2008-06-29 14:05   ` [PATCH 01/15] Move split_cmdline() to alias.c Olivier Marin
@ 2008-06-29 14:15     ` Johannes Schindelin
  2008-06-29 14:29       ` Olivier Marin
  0 siblings, 1 reply; 81+ messages in thread
From: Johannes Schindelin @ 2008-06-29 14:15 UTC (permalink / raw)
  To: Olivier Marin; +Cc: Miklos Vajna, Junio C Hamano, git, Olivier Marin

[-- Attachment #1: Type: TEXT/PLAIN, Size: 615 bytes --]

Hi,

On Sun, 29 Jun 2008, Olivier Marin wrote:

> Miklos Vajna a écrit :
> > split_cmdline() is currently used for aliases only, but later it can be
> > useful for other builtins as well. Move it to alias.c for now,
> > indicating that originally it's for aliases, but we'll have it in libgit
> > this way.
> 
> This function does not trim cmdline. 

As the string comes either from the config (where it is trimmed), or from 
the command line (where the user can be stup^W^Wask for whitespace 
explicitely),  I do not see much merit in this patch.

Unless you can provide an example where it fails, of course,
Dscho

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 01/15] Move split_cmdline() to alias.c
  2008-06-29 14:15     ` Johannes Schindelin
@ 2008-06-29 14:29       ` Olivier Marin
  2008-06-29 14:43         ` Johannes Schindelin
  0 siblings, 1 reply; 81+ messages in thread
From: Olivier Marin @ 2008-06-29 14:29 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Miklos Vajna, Junio C Hamano, git, Olivier Marin

Johannes Schindelin a écrit :
> 
> As the string comes either from the config (where it is trimmed), or from 
> the command line (where the user can be stup^W^Wask for whitespace 
> explicitely),  I do not see much merit in this patch.

You are right, today it is not a problem because the usage is really limited
but Miklos's intention seems to make the function usable by everyone. As we
do not know how it will be used in the future, I think it is safer with my
patch.

Olivier.

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 01/15] Move split_cmdline() to alias.c
  2008-06-29 14:29       ` Olivier Marin
@ 2008-06-29 14:43         ` Johannes Schindelin
  2008-06-30 22:51           ` Olivier Marin
  0 siblings, 1 reply; 81+ messages in thread
From: Johannes Schindelin @ 2008-06-29 14:43 UTC (permalink / raw)
  To: Olivier Marin; +Cc: Miklos Vajna, Junio C Hamano, git, Olivier Marin

[-- Attachment #1: Type: TEXT/PLAIN, Size: 1815 bytes --]

Hi,

On Sun, 29 Jun 2008, Olivier Marin wrote:

> Johannes Schindelin a écrit :
> > 
> > As the string comes either from the config (where it is trimmed), or 
> > from the command line (where the user can be stup^W^Wask for 
> > whitespace explicitely), I do not see much merit in this patch.
> 
> You are right, today it is not a problem because the usage is really 
> limited but Miklos's intention seems to make the function usable by 
> everyone. As we do not know how it will be used in the future, I think 
> it is safer with my patch.

I am generally not a fan of crossing bridges miles before you reach them.

But if you want to keep running with this, you should add at least a few 
tests to show what you patch solves, and that it solves it.  Preferably 
with a "test_expect_failure" patch, and then a patch fixing it and 
changing that _failure to _success.

Here's a starter:

-- snipsnap --
diff --git a/Makefile b/Makefile
index d3e339a..83a9a30 100644
--- a/Makefile
+++ b/Makefile
@@ -1229,7 +1229,7 @@ endif
 
 ### Testing rules
 
-TEST_PROGRAMS = test-chmtime$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X test-absolute-path$X test-parse-options$X test-strbuf$X
+TEST_PROGRAMS = test-chmtime$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X test-absolute-path$X test-parse-options$X test-strbuf$X test-cmdline$X
 
 all:: $(TEST_PROGRAMS)
 
diff --git a/test-cmdline.c b/test-cmdline.c
new file mode 100644
index 0000000..2f6d9eb
--- /dev/null
+++ b/test-cmdline.c
@@ -0,0 +1,15 @@
+#include "cache.h"
+
+int main(int argc, char **argv)
+{
+	int i = 0;
+	const char **new_argv;
+
+	if (argc < 2 || (argc = split_cmdline(argv[1], &new_argv)) < 0)
+		return 1;
+
+	while (i < argc)
+		printf("arg %d: '%s'\n", i++, *new_argv++);
+
+	return 0;
+}

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH] Add new test to ensure git-merge handles pull.twohead and pull.octopus
  2008-06-29 13:30         ` [PATCH 04/15] Add new test to ensure git-merge handles pull.twohead and pull.octopus Olivier Marin
@ 2008-06-29 14:51           ` Miklos Vajna
  2008-06-29 15:11             ` Miklos Vajna
  0 siblings, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-06-29 14:51 UTC (permalink / raw)
  To: Olivier Marin; +Cc: Junio C Hamano, git, Johannes Schindelin

Test if the given strategies are used and test the case when multiple
strategies are configured using a space separated list.

Also test if the best strategy is picked if none is specified.  This is
done by adding a simple test case where recursive detects a rename, but
resolve does not, and verify that finally merge will pick up the
previous.

Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---

On Sun, Jun 29, 2008 at 03:30:56PM +0200, Olivier Marin <dkr+ml.git@free.fr> wrote:
> > +   git tag c2
>
> Missing &&?

Fixed.

> > +   test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)"
>
> Duplicate the next line?
>
> > +   test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&

Yes, it was duplicated.

> > +   test "$auto_count" = "$recursive_count"
> > +'
>
> Should not "$auto_count" != "$resolve_count" also be tested to be
> sure that recursive has been choosen?

Sure, changed.

Updated patch below.

 t/t7601-merge-pull-config.sh |  129 ++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 129 insertions(+), 0 deletions(-)
 create mode 100755 t/t7601-merge-pull-config.sh

diff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh
new file mode 100755
index 0000000..32585f8
--- /dev/null
+++ b/t/t7601-merge-pull-config.sh
@@ -0,0 +1,129 @@
+#!/bin/sh
+
+test_description='git-merge
+
+Testing pull.* configuration parsing.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	echo c0 >c0.c &&
+	git add c0.c &&
+	git commit -m c0 &&
+	git tag c0 &&
+	echo c1 >c1.c &&
+	git add c1.c &&
+	git commit -m c1 &&
+	git tag c1 &&
+	git reset --hard c0 &&
+	echo c2 >c2.c &&
+	git add c2.c &&
+	git commit -m c2 &&
+	git tag c2 &&
+	git reset --hard c0 &&
+	echo c3 >c3.c &&
+	git add c3.c &&
+	git commit -m c3 &&
+	git tag c3
+'
+
+test_expect_success 'merge c1 with c2' '
+	git reset --hard c1 &&
+	test -f c0.c &&
+	test -f c1.c &&
+	test ! -f c2.c &&
+	test ! -f c3.c &&
+	git merge c2 &&
+	test -f c1.c &&
+	test -f c2.c
+'
+
+test_expect_success 'merge c1 with c2 (ours in pull.twohead)' '
+	git reset --hard c1 &&
+	git config pull.twohead ours &&
+	git merge c2 &&
+	test -f c1.c &&
+	! test -f c2.c
+'
+
+test_expect_success 'merge c1 with c2 and c3 (recursive in pull.octopus)' '
+	git reset --hard c1 &&
+	git config pull.octopus "recursive" &&
+	test_must_fail git merge c2 c3 &&
+	test "$(git rev-parse c1)" = "$(git rev-parse HEAD)"
+'
+
+test_expect_success 'merge c1 with c2 and c3 (recursive and octopus in pull.octopus)' '
+	git reset --hard c1 &&
+	git config pull.octopus "recursive octopus" &&
+	git merge c2 c3 &&
+	test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
+	test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
+	test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
+	test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
+	git diff --exit-code &&
+	test -f c0.c &&
+	test -f c1.c &&
+	test -f c2.c &&
+	test -f c3.c
+'
+
+conflict_count()
+{
+	eval $1=`{
+		git diff-files --name-only
+		git ls-files --unmerged
+	} | wc -l`
+}
+
+# c4 - c5
+#    \ c6
+#
+# There are two conflicts here:
+#
+# 1) Because foo.c is renamed to bar.c, recursive will handle this,
+# resolve won't.
+#
+# 2) One in conflict.c and that will always fail.
+
+test_expect_success 'setup conflicted merge' '
+	git reset --hard c0 &&
+	echo A >conflict.c &&
+	git add conflict.c &&
+	echo contents >foo.c &&
+	git add foo.c &&
+	git commit -m c4 &&
+	git tag c4 &&
+	echo B >conflict.c &&
+	git add conflict.c &&
+	git mv foo.c bar.c &&
+	git commit -m c5 &&
+	git tag c5 &&
+	git reset --hard c4 &&
+	echo C >conflict.c &&
+	git add conflict.c &&
+	echo secondline >> foo.c &&
+	git add foo.c &&
+	git commit -m c6 &&
+	git tag c6
+'
+
+# First do the merge with resolve and recursive then verify that
+# recusive is choosen.
+
+test_expect_success 'merge picks up the best result' '
+	git config pull.twohead "recursive resolve" &&
+	git reset --hard c5 &&
+	git merge -s resolve c6
+	conflict_count resolve_count &&
+	git reset --hard c5 &&
+	git merge -s recursive c6
+	conflict_count recursive_count &&
+	git reset --hard c5 &&
+	git merge c6
+	conflict_count auto_count &&
+	test "$auto_count" = "$recursive_count" &&
+	test "$auto_count" != "$resolve_count"
+'
+
+test_done
-- 
1.5.6

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH] Add new test to ensure git-merge handles pull.twohead and pull.octopus
  2008-06-29 14:51           ` [PATCH] " Miklos Vajna
@ 2008-06-29 15:11             ` Miklos Vajna
  0 siblings, 0 replies; 81+ messages in thread
From: Miklos Vajna @ 2008-06-29 15:11 UTC (permalink / raw)
  To: Olivier Marin; +Cc: Junio C Hamano, git, Johannes Schindelin

[-- Attachment #1: Type: text/plain, Size: 203 bytes --]

On Sun, Jun 29, 2008 at 04:51:38PM +0200, Miklos Vajna <vmiklos@frugalware.org> wrote:
> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Oops, I forgot to remove this Signed-off-by. Fixed in my fork.

[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf()
  2008-06-29 13:40                             ` Johannes Schindelin
@ 2008-06-29 20:17                               ` Alex Riesen
  2008-06-29 20:24                                 ` Junio C Hamano
  0 siblings, 1 reply; 81+ messages in thread
From: Alex Riesen @ 2008-06-29 20:17 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Junio C Hamano, Miklos Vajna, git, Olivier Marin

Johannes Schindelin, Sun, Jun 29, 2008 15:40:57 +0200:
> On Sun, 29 Jun 2008, Junio C Hamano wrote:
> > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> > 
> > > Anyway, I'm fine with having them only in my personal fork.
> > 
> > It will cost you some "trust point" next time you try to sneak something
> > in as a part of a largely unrelated topic.  Please don't.
> 
> Fine.  Fine!
> 

See the positive side: you just won a "please review me carefully"
point. These have no negative side effects, usually.

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf()
  2008-06-29 20:17                               ` Alex Riesen
@ 2008-06-29 20:24                                 ` Junio C Hamano
  2008-06-29 20:30                                   ` Sverre Rabbelier
  0 siblings, 1 reply; 81+ messages in thread
From: Junio C Hamano @ 2008-06-29 20:24 UTC (permalink / raw)
  To: Alex Riesen; +Cc: Johannes Schindelin, Miklos Vajna, git, Olivier Marin

Alex Riesen <raa.lkml@gmail.com> writes:

> Johannes Schindelin, Sun, Jun 29, 2008 15:40:57 +0200:
>> On Sun, 29 Jun 2008, Junio C Hamano wrote:
>> > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
>> > 
>> > > Anyway, I'm fine with having them only in my personal fork.
>> > 
>> > It will cost you some "trust point" next time you try to sneak something
>> > in as a part of a largely unrelated topic.  Please don't.
>> 
>> Fine.  Fine!
>
> See the positive side: you just won a "please review me carefully"
> point. These have no negative side effects, usually.

Heh, he hasn't _yet_.  I said "next time" didn't I?

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf()
  2008-06-29 20:24                                 ` Junio C Hamano
@ 2008-06-29 20:30                                   ` Sverre Rabbelier
  0 siblings, 0 replies; 81+ messages in thread
From: Sverre Rabbelier @ 2008-06-29 20:30 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Alex Riesen, Johannes Schindelin, Miklos Vajna, git,
	Olivier Marin

On Sun, Jun 29, 2008 at 10:24 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Heh, he hasn't _yet_.  I said "next time" didn't I?

Now you sound like the bad guy from "Inspector Gadget" :P. "Next time
Dscho, next time!!!"

-- 
Cheers,

Sverre Rabbelier

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 13/13] Build in merge
  2008-06-29  7:46                             ` Junio C Hamano
  2008-06-29  8:11                               ` Jakub Narebski
@ 2008-06-30  1:36                               ` Miklos Vajna
  2008-06-30  1:39                                 ` Miklos Vajna
  2008-06-30  5:40                                 ` Junio C Hamano
  1 sibling, 2 replies; 81+ messages in thread
From: Miklos Vajna @ 2008-06-30  1:36 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

[-- Attachment #1: Type: text/plain, Size: 11348 bytes --]

On Sun, Jun 29, 2008 at 12:46:09AM -0700, Junio C Hamano <gitster@pobox.com> wrote:
> > +/* Get the name for the merge commit's message. */
> > +static void merge_name(const char *remote, struct strbuf *msg)
> > +{
> > +	struct object *remote_head;
> > +	unsigned char branch_head[20], buf_sha[20];
> > +	struct strbuf buf;
> > +	char *ptr;
> > +	int len = 0;
> > +
> > +	memset(branch_head, 0, sizeof(branch_head));
> > +	remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
> > +	if (!remote_head)
> > +		return;
> 
> Hmm.  This is a faithful translation of scripted version, but I wonder
> what should happen when we got a non-commit here...

Hm, I think we do not consider that case normal, at least git fsck
(since commit 6232f62) checks for it.

I replaced the silent return with a die().

> > +
> > +	strbuf_init(&buf, 0);
> > +	strbuf_addstr(&buf, "refs/heads/");
> > +	strbuf_addstr(&buf, remote);
> > +	get_sha1(buf.buf, branch_head);
> 
> This does not correspond to the computation of $bh in the scripted version
> that makes sure "remote" is actually a bare name of branch, e.g. "master",
> without any adornment like "master~5^3~8.  Your code would succeed and
> leave the same object name in branch_head[] as remote_head->sha1, wouldn't
> it?

I replaced it with a dwim_ref() call, to achieve the same behaviour.

> > +	if (!hashcmp(remote_head->sha1, branch_head)) {
> > +		strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
> > +			sha1_to_hex(branch_head), remote);
> > +		return;
> > +	}
> > +	/* See if remote matches <name>~<number>, or <name>^ */
> 
> The scripted version did not handle <name>^, so this is an extension.
> Don't you want also handle <name>^^^ if we are extending it?

I did so, now it accepts <name>^, <name>^^, <name>^^^, etc.

> > +	ptr = strrchr(remote, '^');
> > +	if (ptr && ptr[1] == '\0')
> > +		len = ptr-remote;
> > +	else {
> > +		ptr = strrchr(remote, '~');
> > +		if (ptr && ptr[1] != '0' && isdigit(ptr[1])) {
> > +			len = ptr-remote;
> > +			ptr++;
> > +			for (ptr++; *ptr; ptr++)
> > +				if (!isdigit(*ptr)) {
> > +					len = 0;
> > +					break;
> > +				}
> > +		}
> > +	}
> > +	if (len) {
> > +		struct strbuf truname = STRBUF_INIT;
> > +		strbuf_addstr(&truname, remote);
> > +		strbuf_setlen(&truname, len);
> > +		if (!get_sha1(truname.buf, buf_sha)) {
> 
> Again, isn't this wrong?  You are not making sure truname is the name of
> existing local branch.  HEAD@{7}~23 will pass get_sha1() but you are not
> merging an early part of HEAD@{7} branch.

Now I'm using dwim_ref() here as well.

> > +			strbuf_addf(msg,
> > +				"%s\t\tbranch '%s' (early part) of .\n",
> > +				sha1_to_hex(remote_head->sha1), truname.buf);
> > +			return;
> > +		}
> > +	}
> 
> > +int cmd_merge(int argc, const char **argv, const char *prefix)
> > +{
> 
> This is an ultra-huge function.  I wonder if it can further split up to
> make it easier to maintain.

Yes, just like the scripted version. Hm no, that was even longer. (I
mean I already introduced a lot of static functions to make the C
equivalent of the "main" part of git-merge.sh shorter.) OK, it was 474
lines long today, but now I introduced 3 new static functions and that
made it "only" 410 lines long.

> > +	/*
> > +	 * This could be traditional "merge <msg> HEAD <commit>..."  and
> > +	 * the way we can tell it is to see if the second token is HEAD,
> > +	 * but some people might have misused the interface and used a
> > +	 * committish that is the same as HEAD there instead.
> > +	 * Traditional format never would have "-m" so it is an
> > +	 * additional safety measure to check for it.
> > +	 */
> > +	strbuf_init(&buf, 0);
> > +	strbuf_init(&head_arg, 0);
> > +	if (argc > 1)
> > +		second_token = peel_to_type(argv[1], 0, NULL, OBJ_COMMIT);
> 
> If the second token was a string that could resolve to an object name that
> does not peel to commit (say "merge -m 'HEAD^{tree}' other"), you will get
> a complaint fro mpeel-to-type "I expected a commit but you gave something
> else".  You (or more likely Dscho) might have said "that won't matter in
> practice", but I think you should really do get_sha1() followed by
> lookup_commit_reference_gently() here to avoid the errors.

Fixed.

> 
> > +	head_invalid = get_sha1("HEAD", head);
> 
> You've already done this earlier with resolve_ref() haven't you?

Ah yes. I had the global 'head' and the local 'sha1' variable for the
same purpose, now I got rid of the local 'sha1' variable in cmd_merge()
so resolve_ref() writes now to the 'head' variable and then this line is
not necessary, as I can write head_invalid right after resolve_ref().

> 
> > +	if (!have_message && second_token &&
> > +		!hashcmp(second_token->sha1, head)) {
> 
> Isn't this wrong if head_invalid is true?

if head_invalid is true, then 'head' will be filled with 0s, hashcmp()
will never return 0 so the condition will be never true. That's what the
shell version:

        head_commit=$(git rev-parse --verify "HEAD" 2>/dev/null)

does as well.

> > +		strbuf_addstr(&merge_msg, argv[0]);
> > +		strbuf_addstr(&head_arg, argv[1]);
> > +		argv += 2;
> > +		argc -= 2;
> 
> I do not think there is any point using strbuf for head_arg.  Shouldn't it
> simply be a "const char *"?

Now it is.

> > +		if (!remote_head)
> > +			die("%s - not something we can merge", argv[0]);
> > +		update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
> > +				DIE_ON_ERR);
> > +		reset_hard(remote_head->sha1, 0);
> > +		return 0;
> 
> Makes one wonder reset_hard() (aka "read-tree --reset -u HEAD") ever fail
> and return here (iow, without calling die()).  The answer is luckily no
> in this case, but it is somewhat unnerving to reviewers.

Actually reset_hard does not return if an error occures:

        if (unpack_trees(1, &t, &opts))
                exit(128); /* We've already reported the error, finish dying */

That's exactly how we already have it in builtin-commit.

> 
> > +	} else {
> > +		/* We are invoked directly as the first-class UI. */
> > +		strbuf_addstr(&head_arg, "HEAD");
> > +		/*
> > +		 * All the rest are the commits being merged;
> > +		 * prepare the standard merge summary message to
> > +		 * be appended to the given message.  If remote
> > +		 * is invalid we will die later in the common
> > +		 * codepath so we discard the error in this
> > +		 * loop.
> > +		 */
> > +		struct strbuf msg;
> 
> Decl-after-statement.

You already fixed this. :-)

> > +	for (i = 0; i < use_strategies.nr; i++) {
> > +		if ((unsigned int)use_strategies.items[i].util &
> > +			NO_FAST_FORWARD)
> > +			allow_fast_forward = 0;
> > +		if ((unsigned int)use_strategies.items[i].util & NO_TRIVIAL)
> > +			allow_trivial = 0;
> 
> Can we abstract out these ugly casts?  Any code that use path_list to
> store anything but list of paths (i.e. some value keyed with string) tends
> to have this readability issue.

If you don't cast, you can't use the & operator. If I change the
path_list_item's util to be an unsigned number then I break fast-export.
I think if we _really_ want to get rid of those casts, we could have
something like:

diff --git a/path-list.h b/path-list.h
index ca2cbba..1f57e81 100644
--- a/path-list.h
+++ b/path-list.h
@@ -4,6 +4,7 @@
 struct path_list_item {
        char *path;
        void *util;
+       unsigned int flags;
 };
 struct path_list
 {

But I'm not sure if that's a good idea. Also, fast-export will still
have casts after such a change.

> > +	if (!common)
> > +		; /* No common ancestors found. We need a real merge. */
> > +	else if (!remoteheads->next &&
> > +		!hashcmp(common->item->object.sha1,
> > +		remoteheads->item->object.sha1)) {
> 
> Wouldn't the latter be "common->item == remoteheads->item" simply?

Right, changed.

> You do not have the check to make sure there is only one common ancestor
> (scripted version compares $common and $1 textually to achieve this), and
> checking only the first one of them.  Is this correct?

Yes. I changed it to:

        else if (!remoteheads->next && !common->next &&
                        common->item == remoteheads->item) {

And now I have the check.

> 
> > +		/*
> > +		 * If head can reach all the remote heads then we are up
> > +		 * to date.
> > +		 */
> 
> The comment is wrong --- you are doing "... but first the most common case
> of merging one remote" here.

I changed it to match the shell version:

/*
 * If head can reach all the merge then we are up to
 * date.
 * but first the most common case of merging one remote.
 */

> > +		finish_up_to_date("Already up-to-date.");
> > +		return 0;
> > +	} else if (allow_fast_forward && !remoteheads->next &&
> > +		!hashcmp(common->item->object.sha1, head)) {
> > +		/* Again the most common case of merging one remote. */
> 
> Here again you are not checking there is only one common, and checking
> only the first one of them.

Changed to:

        } else if (allow_fast_forward && !remoteheads->next &&
                        !common->next &&
                        !hashcmp(common->item->object.sha1, head)) {

Which should add the proper check.

> > +		if (merge_one_remote(head, remoteheads->item->object.sha1))
> > +			return 0;
> 
> Isn't "merge_one_remote()" just a "git checkout" after fast-forward?  The
> function feels misnamed.

Thanks. I'm terribly bad at naming. Renamed to checkout_fast_forward().

> > +		finish(o->sha1, msg.buf);
> > +		dropsave();
> > +		return 0;
> > +	} else if (!remoteheads->next && common->next)
> > +		;
> 
> Here you are checking common->next but earlier if/elseif chain didn't so
> it is too late.

Now, that I do, I think the condition is OK.

> > +	else if (!remoteheads->next && option_commit) {
> > +		/*
> > +		 * We are not doing octopus, not fast forward, and have
> > +		 * only one common.
> 
> Here again you did not check "have only one common" did you?

Actually the shell version did not check here, either, but yes, I would
have to. Now I do.

> > +			printf("Trying really trivial in-index merge...\n");
> > +			if (!read_tree_trivial(common->item->object.sha1,
> > +					head, remoteheads->item->object.sha1)) {
> > +				unsigned char result_tree[20],
> > +					result_commit[20];
> > +				struct commit_list parent;
> > +
> > +				write_tree_trivial(result_tree);
> > +				printf("Wonderful.\n");
> > +				parent.item = remoteheads->item;
> > +				parent.next = NULL;
> > +				commit_tree_trivial(merge_msg.buf,
> > +					result_tree, &parent,
> > +					result_commit);
> > +				finish(result_commit, "In-index merge");
> > +				dropsave();
> > +				return 0;
> > +			}
> > +			printf("Nope.\n");
> > +		}
> 
> There weren't any good way to squelch error messages selectively from the
> trivial one in the scripted version and that is the only reason we
> surround read-tree with "Trying..." and "Wonderful/Nope.".  Literal
> translation to make sure you get identical output in the first round of
> this series is good, but after the code stabilizes, we may want to squelch
> these messages.  Something to keep in mind but not now.

OK.

[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH 13/13] Build in merge
  2008-06-30  1:36                               ` Miklos Vajna
@ 2008-06-30  1:39                                 ` Miklos Vajna
  2008-06-30  5:44                                   ` Junio C Hamano
  2008-06-30 22:44                                   ` [PATCH 13/13] " Olivier Marin
  2008-06-30  5:40                                 ` Junio C Hamano
  1 sibling, 2 replies; 81+ messages in thread
From: Miklos Vajna @ 2008-06-30  1:39 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---

Interdiff since the previous version:

	git diff 9824cf3..8611c7b

Individual commits:

	git log 8611c7b

 Makefile                                      |    2 +-
 builtin-merge.c                               | 1173 +++++++++++++++++++++++++
 builtin.h                                     |    1 +
 git-merge.sh => contrib/examples/git-merge.sh |    0 
 git.c                                         |    1 +
 t/t7602-merge-octopus-many.sh                 |    2 +-
 6 files changed, 1177 insertions(+), 2 deletions(-)
 create mode 100644 builtin-merge.c
 rename git-merge.sh => contrib/examples/git-merge.sh (100%)

diff --git a/Makefile b/Makefile
index b003e3e..3d8c3d2 100644
--- a/Makefile
+++ b/Makefile
@@ -240,7 +240,6 @@ SCRIPT_SH += git-lost-found.sh
 SCRIPT_SH += git-merge-octopus.sh
 SCRIPT_SH += git-merge-one-file.sh
 SCRIPT_SH += git-merge-resolve.sh
-SCRIPT_SH += git-merge.sh
 SCRIPT_SH += git-merge-stupid.sh
 SCRIPT_SH += git-mergetool.sh
 SCRIPT_SH += git-parse-remote.sh
@@ -511,6 +510,7 @@ BUILTIN_OBJS += builtin-ls-remote.o
 BUILTIN_OBJS += builtin-ls-tree.o
 BUILTIN_OBJS += builtin-mailinfo.o
 BUILTIN_OBJS += builtin-mailsplit.o
+BUILTIN_OBJS += builtin-merge.o
 BUILTIN_OBJS += builtin-merge-base.o
 BUILTIN_OBJS += builtin-merge-file.o
 BUILTIN_OBJS += builtin-merge-ours.o
diff --git a/builtin-merge.c b/builtin-merge.c
new file mode 100644
index 0000000..187038c
--- /dev/null
+++ b/builtin-merge.c
@@ -0,0 +1,1173 @@
+/*
+ * Builtin "git merge"
+ *
+ * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
+ *
+ * Based on git-merge.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "parse-options.h"
+#include "builtin.h"
+#include "run-command.h"
+#include "path-list.h"
+#include "diff.h"
+#include "refs.h"
+#include "commit.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
+#include "dir.h"
+#include "utf8.h"
+#include "log-tree.h"
+#include "color.h"
+
+enum strategy {
+	DEFAULT_TWOHEAD = 1,
+	DEFAULT_OCTOPUS = 2,
+	NO_FAST_FORWARD = 4,
+	NO_TRIVIAL = 8
+};
+
+static const char * const builtin_merge_usage[] = {
+	"git-merge [options] <remote>...",
+	"git-merge [options] <msg> HEAD <remote>",
+	NULL
+};
+
+static int show_diffstat = 1, option_log, squash;
+static int option_commit = 1, allow_fast_forward = 1;
+static int allow_trivial = 1, have_message;
+static struct strbuf merge_msg;
+static struct commit_list *remoteheads;
+static unsigned char head[20];
+static struct path_list use_strategies;
+static const char *branch;
+
+static struct path_list_item strategy_items[] = {
+	{ "recur",      (void *)NO_TRIVIAL },
+	{ "recursive",  (void *)(DEFAULT_TWOHEAD | NO_TRIVIAL) },
+	{ "octopus",    (void *)DEFAULT_OCTOPUS },
+	{ "resolve",    (void *)0 },
+	{ "stupid",     (void *)0 },
+	{ "ours",       (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
+	{ "subtree",    (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
+};
+static struct path_list strategies = { strategy_items,
+	ARRAY_SIZE(strategy_items), 0, 0 };
+
+static const char *pull_twohead, *pull_octopus;
+
+static int option_parse_message(const struct option *opt,
+	const char *arg, int unset)
+{
+	struct strbuf *buf = opt->value;
+
+	if (unset)
+		strbuf_setlen(buf, 0);
+	else {
+		strbuf_addf(buf, "%s\n\n", arg);
+		have_message = 1;
+	}
+	return 0;
+}
+
+static struct path_list_item *unsorted_path_list_lookup(const char *path,
+	struct path_list *list)
+{
+	int i;
+
+	if (!path)
+		return NULL;
+
+	for (i = 0; i < list->nr; i++)
+		if (!strcmp(path, list->items[i].path))
+			return &list->items[i];
+	return NULL;
+}
+
+static inline void path_list_append_strategy(struct path_list_item *item)
+{
+	path_list_append(item->path, &use_strategies)->util = item->util;
+}
+
+static int option_parse_strategy(const struct option *opt,
+	const char *arg, int unset)
+{
+	int i;
+	struct path_list_item *item = unsorted_path_list_lookup(arg, &strategies);
+
+	if (unset)
+		return 0;
+
+	if (item)
+		path_list_append_strategy(item);
+	else {
+		struct strbuf err;
+		strbuf_init(&err, 0);
+		for (i = 0; i < strategies.nr; i++)
+			strbuf_addf(&err, " %s", strategies.items[i].path);
+		fprintf(stderr, "Could not find merge strategy '%s'.\n", arg);
+		fprintf(stderr, "Available strategies are:%s.\n", err.buf);
+		exit(1);
+	}
+	return 0;
+}
+
+static int option_parse_n(const struct option *opt,
+		const char *arg, int unset)
+{
+	show_diffstat = unset;
+	return 0;
+}
+
+static struct option builtin_merge_options[] = {
+	{ OPTION_CALLBACK, 'n', NULL, NULL, NULL,
+		"do not show a diffstat at the end of the merge",
+		PARSE_OPT_NOARG, option_parse_n },
+	OPT_BOOLEAN(0, "stat", &show_diffstat,
+		"show a diffstat at the end of the merge"),
+	OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
+	OPT_BOOLEAN(0, "log", &option_log,
+		"add list of one-line log to merge commit message"),
+	OPT_BOOLEAN(0, "squash", &squash,
+		"create a single commit instead of doing a merge"),
+	OPT_BOOLEAN(0, "commit", &option_commit,
+		"perform a commit if the merge succeeds (default)"),
+	OPT_BOOLEAN(0, "ff", &allow_fast_forward,
+		"allow fast forward (default)"),
+	OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
+		"merge strategy to use", option_parse_strategy),
+	OPT_CALLBACK('m', "message", &merge_msg, "message",
+		"message to be used for the merge commit (if any)",
+		option_parse_message),
+	OPT_END()
+};
+
+/* Cleans up metadata that is uninteresting after a succeeded merge. */
+static void dropsave(void)
+{
+	unlink(git_path("MERGE_HEAD"));
+	unlink(git_path("MERGE_MSG"));
+	unlink(git_path("MERGE_STASH"));
+}
+
+static void save_state(void)
+{
+	int fd;
+	struct child_process stash;
+	const char *argv[] = {"stash", "create", NULL};
+
+	fd = open(git_path("MERGE_STASH"), O_WRONLY | O_CREAT, 0666);
+	if (fd < 0)
+		die("Could not write to %s", git_path("MERGE_STASH"));
+	memset(&stash, 0, sizeof(stash));
+	stash.argv = argv;
+	stash.out = fd;
+	stash.git_cmd = 1;
+	run_command(&stash);
+}
+
+static void reset_hard(unsigned const char *sha1, int verbose)
+{
+	struct tree *tree;
+	struct unpack_trees_options opts;
+	struct tree_desc t;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.head_idx = -1;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.reset = 1;
+	if (verbose)
+		opts.verbose_update = 1;
+
+	tree = parse_tree_indirect(sha1);
+	if (!tree)
+		die("failed to unpack %s tree object", sha1_to_hex(sha1));
+	parse_tree(tree);
+	init_tree_desc(&t, tree->buffer, tree->size);
+	if (unpack_trees(1, &t, &opts))
+		exit(128); /* We've already reported the error, finish dying */
+}
+
+static void restore_state(void)
+{
+	struct strbuf sb;
+	const char *args[] = { "stash", "apply", NULL, NULL };
+
+	if (access(git_path("MERGE_STASH"), R_OK) < 0)
+		return;
+
+	reset_hard(head, 1);
+
+	strbuf_init(&sb, 0);
+	if (strbuf_read_file(&sb, git_path("MERGE_STASH"), 0) < 0)
+		die("could not read MERGE_STASH: %s", strerror(errno));
+	args[2] = sb.buf;
+
+	/*
+	 * It is OK to ignore error here, for example when there was
+	 * nothing to restore.
+	 */
+	run_command_v_opt(args, RUN_GIT_CMD);
+
+	strbuf_release(&sb);
+	refresh_cache(REFRESH_QUIET);
+}
+
+/* This is called when no merge was necessary. */
+static void finish_up_to_date(const char *msg)
+{
+	printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
+	dropsave();
+}
+
+static void squash_message(void)
+{
+	struct rev_info rev;
+	struct commit *commit;
+	struct strbuf out;
+	struct commit_list *j;
+	int fd;
+
+	printf("Squash commit -- not updating HEAD\n");
+	fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+	if (fd < 0)
+		die("Could not write to %s", git_path("SQUASH_MSG"));
+
+	init_revisions(&rev, NULL);
+	rev.ignore_merges = 1;
+	rev.commit_format = CMIT_FMT_MEDIUM;
+
+	commit = lookup_commit(head);
+	commit->object.flags |= UNINTERESTING;
+	add_pending_object(&rev, &commit->object, NULL);
+
+	for (j = remoteheads; j; j = j->next)
+		add_pending_object(&rev, &j->item->object, NULL);
+
+	setup_revisions(0, NULL, &rev, NULL);
+	if (prepare_revision_walk(&rev))
+		die("revision walk setup failed");
+
+	strbuf_init(&out, 0);
+	strbuf_addstr(&out, "Squashed commit of the following:\n");
+	while ((commit = get_revision(&rev)) != NULL) {
+		strbuf_addch(&out, '\n');
+		strbuf_addf(&out, "commit %s\n",
+			sha1_to_hex(commit->object.sha1));
+		pretty_print_commit(rev.commit_format, commit, &out, rev.abbrev,
+			NULL, NULL, rev.date_mode, 0);
+	}
+	write(fd, out.buf, out.len);
+	close(fd);
+	strbuf_release(&out);
+}
+
+static int run_hook(const char *name)
+{
+	struct child_process hook;
+	const char *argv[3], *env[2];
+	char index[PATH_MAX];
+
+	argv[0] = git_path("hooks/%s", name);
+	if (access(argv[0], X_OK) < 0)
+		return 0;
+
+	snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", get_index_file());
+	env[0] = index;
+	env[1] = NULL;
+
+	if (squash)
+		argv[1] = "1";
+	else
+		argv[1] = "0";
+	argv[2] = NULL;
+
+	memset(&hook, 0, sizeof(hook));
+	hook.argv = argv;
+	hook.no_stdin = 1;
+	hook.stdout_to_stderr = 1;
+	hook.env = env;
+
+	return run_command(&hook);
+}
+
+static void finish(const unsigned char *new_head, const char *msg)
+{
+	struct strbuf reflog_message;
+
+	strbuf_init(&reflog_message, 0);
+	if (!msg)
+		strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
+	else {
+		printf("%s\n", msg);
+		strbuf_addf(&reflog_message, "%s: %s",
+			getenv("GIT_REFLOG_ACTION"), msg);
+	}
+	if (squash) {
+		squash_message();
+	} else {
+		if (!merge_msg.len)
+			printf("No merge message -- not updating HEAD\n");
+		else {
+			const char *argv_gc_auto[] = { "gc", "--auto", NULL };
+			update_ref(reflog_message.buf, "HEAD",
+				new_head, head, 0,
+				DIE_ON_ERR);
+			/*
+			 * We ignore errors in 'gc --auto', since the
+			 * user should see them.
+			 */
+			run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+		}
+	}
+	if (new_head && show_diffstat) {
+		struct diff_options opts;
+		diff_setup(&opts);
+		opts.output_format |=
+			DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+		opts.detect_rename = DIFF_DETECT_RENAME;
+		if (diff_use_color_default > 0)
+			DIFF_OPT_SET(&opts, COLOR_DIFF);
+		diff_tree_sha1(head, new_head, "", &opts);
+		diffcore_std(&opts);
+		diff_flush(&opts);
+	}
+
+	/* Run a post-merge hook */
+	run_hook("post-merge");
+
+	strbuf_release(&reflog_message);
+}
+
+/* Get the name for the merge commit's message. */
+static void merge_name(const char *remote, struct strbuf *msg)
+{
+	struct object *remote_head;
+	unsigned char branch_head[20], buf_sha[20];
+	struct strbuf buf;
+	char *ptr;
+	int len = 0;
+	char *ref;
+
+	memset(branch_head, 0, sizeof(branch_head));
+	remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+	if (!remote_head)
+		die("'%s' does not point to a commit", remote);
+
+	strbuf_init(&buf, 0);
+	strbuf_addstr(&buf, "refs/heads/");
+	strbuf_addstr(&buf, remote);
+	dwim_ref(buf.buf, buf.len, branch_head, &ref);
+
+	if (!hashcmp(remote_head->sha1, branch_head)) {
+		strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
+			sha1_to_hex(branch_head), remote);
+		return;
+	}
+	/* See if remote matches <name>~<number>, or <name>^ */
+	ptr = strrchr(remote, '^');
+	if (ptr && ptr[1] == '\0') {
+		len = strlen(remote);
+		while ((ptr = (char *)memrchr(remote, '^', len)))
+			if (ptr && ptr[1] == '\0')
+				len = ptr - remote - 1;
+			else
+				break;
+	}
+	else {
+		ptr = strrchr(remote, '~');
+		if (ptr && ptr[1] != '0' && isdigit(ptr[1])) {
+			len = ptr-remote;
+			ptr++;
+			for (ptr++; *ptr; ptr++)
+				if (!isdigit(*ptr)) {
+					len = 0;
+					break;
+				}
+		}
+	}
+	if (len) {
+		struct strbuf truname = STRBUF_INIT;
+		strbuf_addstr(&truname, remote);
+		strbuf_setlen(&truname, len);
+		if (dwim_ref(truname.buf, truname.len, buf_sha, &ref)) {
+			strbuf_addf(msg,
+				"%s\t\tbranch '%s' (early part) of .\n",
+				sha1_to_hex(remote_head->sha1), truname.buf);
+			return;
+		}
+	}
+
+	if (!strcmp(remote, "FETCH_HEAD") &&
+			!access(git_path("FETCH_HEAD"), R_OK)) {
+		FILE *fp;
+		struct strbuf line;
+		char *ptr;
+
+		strbuf_init(&line, 0);
+		fp = fopen(git_path("FETCH_HEAD"), "r");
+		if (!fp)
+			die("could not open %s for reading: %s",
+				git_path("FETCH_HEAD"), strerror(errno));
+		strbuf_getline(&line, fp, '\n');
+		fclose(fp);
+		ptr = strstr(line.buf, "\tnot-for-merge\t");
+		if (ptr)
+			strbuf_remove(&line, ptr-line.buf+1, 13);
+		strbuf_addbuf(msg, &line);
+		strbuf_release(&line);
+		return;
+	}
+	strbuf_addf(msg, "%s\t\tcommit '%s'\n",
+		sha1_to_hex(remote_head->sha1), remote);
+}
+
+int git_merge_config(const char *k, const char *v, void *cb)
+{
+	if (branch && !prefixcmp(k, "branch.") &&
+		!prefixcmp(k + 7, branch) &&
+		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+		const char **argv;
+		int argc;
+		char *buf;
+
+		buf = xstrdup(v);
+		argc = split_cmdline(buf, &argv);
+		argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+		memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+		argc++;
+		parse_options(argc, argv, builtin_merge_options,
+			      builtin_merge_usage, 0);
+		free(buf);
+	}
+
+	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+		show_diffstat = git_config_bool(k, v);
+	else if (!strcmp(k, "pull.twohead"))
+		return git_config_string(&pull_twohead, k, v);
+	else if (!strcmp(k, "pull.octopus"))
+		return git_config_string(&pull_octopus, k, v);
+	return 0;
+}
+
+static int read_tree_trivial(unsigned char *common, unsigned char *head,
+	unsigned char *one)
+{
+	int i, nr_trees = 0;
+	struct tree *trees[MAX_UNPACK_TREES];
+	struct tree_desc t[MAX_UNPACK_TREES];
+	struct unpack_trees_options opts;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.head_idx = -1;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.verbose_update = 1;
+	opts.trivial_merges_only = 1;
+	opts.merge = 1;
+	trees[nr_trees] = parse_tree_indirect(common);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(head);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(one);
+	if (!trees[nr_trees++])
+		return -1;
+	opts.fn = threeway_merge;
+	cache_tree_free(&active_cache_tree);
+	opts.head_idx = 2;
+	for (i = 0; i < nr_trees; i++) {
+		parse_tree(trees[i]);
+		init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+	}
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+	return 0;
+}
+
+static int commit_tree_trivial(const char *msg, unsigned const char *tree,
+		struct commit_list *parents, unsigned char *ret)
+{
+	struct commit_list *i;
+	struct strbuf buf;
+	int encoding_is_utf8;
+
+	/* Not having i18n.commitencoding is the same as having utf-8 */
+	encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
+
+	strbuf_init(&buf, 8192); /* should avoid reallocs for the headers */
+	strbuf_addf(&buf, "tree %s\n", sha1_to_hex(tree));
+
+	for (i = parents; i; i = i->next)
+		strbuf_addf(&buf, "parent %s\n",
+			sha1_to_hex(i->item->object.sha1));
+
+	/* Person/date information */
+	strbuf_addf(&buf, "author %s\n",
+		git_author_info(IDENT_ERROR_ON_NO_NAME));
+	strbuf_addf(&buf, "committer %s\n",
+		git_committer_info(IDENT_ERROR_ON_NO_NAME));
+	if (!encoding_is_utf8)
+		strbuf_addf(&buf, "encoding %s\n", git_commit_encoding);
+	strbuf_addch(&buf, '\n');
+
+	/* And add the comment */
+	strbuf_addstr(&buf, msg);
+
+	write_sha1_file(buf.buf, buf.len, commit_type, ret);
+	strbuf_release(&buf);
+	return *ret;
+}
+
+static void write_tree_trivial(unsigned char *sha1)
+{
+	if (write_cache_as_tree(sha1, 0, NULL))
+		die("git write-tree failed to write a tree");
+}
+
+static int try_merge_strategy(char *strategy, struct commit_list *common,
+	const char *head_arg)
+{
+	const char **args;
+	int i = 0, ret;
+	struct commit_list *j;
+	struct strbuf buf;
+
+	args = xmalloc((4 + commit_list_count(common) +
+			commit_list_count(remoteheads)) * sizeof(char *));
+	strbuf_init(&buf, 0);
+	strbuf_addf(&buf, "merge-%s", strategy);
+	args[i++] = buf.buf;
+	for (j = common; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i++] = "--";
+	args[i++] = head_arg;
+	for (j = remoteheads; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i] = NULL;
+	ret = run_command_v_opt(args, RUN_GIT_CMD);
+	strbuf_release(&buf);
+	i = 1;
+	for (j = common; j; j = j->next)
+		free((void *)args[i++]);
+	i += 2;
+	for (j = remoteheads; j; j = j->next)
+		free((void *)args[i++]);
+	free(args);
+	return -ret;
+}
+
+static void count_diff_files(struct diff_queue_struct *q,
+		struct diff_options *opt, void *data)
+{
+	int *count = data;
+
+	(*count) += q->nr;
+}
+
+static int count_unmerged_entries(void)
+{
+	const struct index_state *state = &the_index;
+	int i, ret = 0;
+
+	for (i = 0; i < state->cache_nr; i++)
+		if (ce_stage(state->cache[i]))
+			ret++;
+
+	return ret;
+}
+
+static int checkout_fast_forward(unsigned char *head, unsigned char *remote)
+{
+	struct tree *trees[MAX_UNPACK_TREES];
+	struct unpack_trees_options opts;
+	struct tree_desc t[MAX_UNPACK_TREES];
+	int i, fd, nr_trees = 0;
+	struct dir_struct dir;
+	struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+	if (read_cache_unmerged())
+		die("you need to resolve your current index first");
+
+	fd = hold_locked_index(lock_file, 1);
+
+	memset(&trees, 0, sizeof(trees));
+	memset(&opts, 0, sizeof(opts));
+	memset(&t, 0, sizeof(t));
+	dir.show_ignored = 1;
+	dir.exclude_per_dir = ".gitignore";
+	opts.dir = &dir;
+
+	opts.head_idx = 1;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.verbose_update = 1;
+	opts.merge = 1;
+	opts.fn = twoway_merge;
+
+	trees[nr_trees] = parse_tree_indirect(head);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(remote);
+	if (!trees[nr_trees++])
+		return -1;
+	for (i = 0; i < nr_trees; i++) {
+		parse_tree(trees[i]);
+		init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+	}
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+	if (write_cache(fd, active_cache, active_nr) ||
+		commit_locked_index(lock_file))
+		die("unable to write new index file");
+	return 0;
+}
+
+static void split_merge_strategies(const char *string, struct path_list *list)
+{
+	char *p, *q, *buf;
+
+	if (!string)
+		return;
+
+	list->strdup_paths = 1;
+	buf = xstrdup(string);
+	q = buf;
+	for (;;) {
+		p = strchr(q, ' ');
+		if (!p) {
+			path_list_append(q, list);
+			free(buf);
+			return;
+		} else {
+			*p = '\0';
+			path_list_append(q, list);
+			q = ++p;
+		}
+	}
+}
+
+static void add_strategies(const char *string, enum strategy strategy)
+{
+	struct path_list list;
+	int i;
+
+	memset(&list, 0, sizeof(list));
+	split_merge_strategies(string, &list);
+	if (list.nr) {
+		for (i = 0; i < list.nr; i++) {
+			struct path_list_item *item;
+
+			item = unsorted_path_list_lookup(list.items[i].path,
+				&strategies);
+			if (item)
+				path_list_append_strategy(item);
+		}
+		return;
+	}
+	for (i = 0; i < strategies.nr; i++)
+		if ((enum strategy)strategies.items[i].util & strategy)
+			path_list_append_strategy(&strategies.items[i]);
+}
+
+static int merge_trivial(void)
+{
+	unsigned char result_tree[20], result_commit[20];
+	struct commit_list parent;
+
+	write_tree_trivial(result_tree);
+	printf("Wonderful.\n");
+	parent.item = remoteheads->item;
+	parent.next = NULL;
+	commit_tree_trivial(merge_msg.buf, result_tree, &parent,
+		result_commit);
+	finish(result_commit, "In-index merge");
+	dropsave();
+	return 0;
+}
+
+static int finish_automerge(struct commit_list *common,
+	unsigned char *result_tree,
+	struct path_list_item *wt_strategy)
+{
+	struct commit_list *parents = NULL, *j;
+	struct strbuf buf = STRBUF_INIT;
+	unsigned char result_commit[20];
+
+	free_commit_list(common);
+	if (allow_fast_forward) {
+		parents = remoteheads;
+		commit_list_insert(lookup_commit(head), &parents);
+		parents = reduce_heads(parents);
+	} else {
+		struct commit_list **pptr = &parents;
+
+		pptr = &commit_list_insert(lookup_commit(head),
+				pptr)->next;
+		for (j = remoteheads; j; j = j->next)
+			pptr = &commit_list_insert(j->item, pptr)->next;
+	}
+	free_commit_list(remoteheads);
+	strbuf_addch(&merge_msg, '\n');
+	commit_tree_trivial(merge_msg.buf, result_tree, parents,
+			result_commit);
+	free_commit_list(parents);
+	strbuf_addf(&buf, "Merge made by %s.", wt_strategy->path);
+	finish(result_commit, buf.buf);
+	strbuf_release(&buf);
+	dropsave();
+	return 0;
+}
+
+static int suggest_conflicts(void)
+{
+	FILE *fp;
+	int pos;
+
+	fp = fopen(git_path("MERGE_MSG"), "a");
+	if (!fp)
+		die("Could open %s for writing", git_path("MERGE_MSG"));
+	fprintf(fp, "\nConflicts:\n");
+	for (pos = 0; pos < active_nr; pos++) {
+		struct cache_entry *ce = active_cache[pos];
+
+		if (ce_stage(ce)) {
+			fprintf(fp, "\t%s\n", ce->name);
+			while (pos + 1 < active_nr &&
+					!strcmp(ce->name,
+						active_cache[pos + 1]->name))
+				pos++;
+		}
+	}
+	fclose(fp);
+	rerere();
+	printf("Automatic merge failed; "
+			"fix conflicts and then commit the result.\n");
+	return 1;
+}
+
+int cmd_merge(int argc, const char **argv, const char *prefix)
+{
+	unsigned char result_tree[20];
+	struct commit *second_token = NULL;
+	struct strbuf buf;
+	const char *head_arg;
+	int flag, head_invalid = 0, i, single_strategy;
+	int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
+	struct commit_list *common = NULL;
+	struct path_list_item *best_strategy = NULL, *wt_strategy = NULL;
+	struct commit_list **remotes = &remoteheads;
+
+	setup_work_tree();
+	if (unmerged_cache())
+		die("You are in the middle of a conflicted merge.");
+
+	/*
+	 * Check if we are _not_ on a detached HEAD, i.e. if there is a
+	 * current branch.
+	 */
+	branch = resolve_ref("HEAD", head, 0, &flag);
+	if (branch && flag & REF_ISSYMREF) {
+		const char *ptr = skip_prefix(branch, "refs/heads/");
+		if (ptr)
+			branch = ptr;
+	} else
+		head_invalid = 1;
+
+	git_config(git_merge_config, NULL);
+
+	/* for color.diff and diff.color */
+	git_config(git_diff_ui_config, NULL);
+
+	/* for color.ui */
+	if (diff_use_color_default == -1)
+		diff_use_color_default = git_use_color_default;
+
+	argc = parse_options(argc, argv, builtin_merge_options,
+			builtin_merge_usage, 0);
+
+	if (squash) {
+		if (!allow_fast_forward)
+			die("You cannot combine --squash with --no-ff.");
+		option_commit = 0;
+	}
+
+	if (!argc)
+		usage_with_options(builtin_merge_usage,
+			builtin_merge_options);
+
+	/*
+	 * This could be traditional "merge <msg> HEAD <commit>..."  and
+	 * the way we can tell it is to see if the second token is HEAD,
+	 * but some people might have misused the interface and used a
+	 * committish that is the same as HEAD there instead.
+	 * Traditional format never would have "-m" so it is an
+	 * additional safety measure to check for it.
+	 */
+	strbuf_init(&buf, 0);
+	if (argc > 1) {
+		unsigned char second_sha1[20];
+
+		if (get_sha1(argv[1], second_sha1))
+			die("Not a valid ref: %s", argv[1]);
+		second_token = lookup_commit_reference_gently(second_sha1, 0);
+		if (!second_token)
+			die("'%s' is not a commit", argv[1]);
+	}
+
+	if (!have_message && second_token &&
+		!hashcmp(second_token->object.sha1, head)) {
+		strbuf_addstr(&merge_msg, argv[0]);
+		head_arg = argv[1];
+		argv += 2;
+		argc -= 2;
+	} else if (head_invalid) {
+		struct object *remote_head;
+		/*
+		 * If the merged head is a valid one there is no reason
+		 * to forbid "git merge" into a branch yet to be born.
+		 * We do the same for "git pull".
+		 */
+		if (argc != 1)
+			die("Can merge only exactly one commit into "
+				"empty head");
+		remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+		if (!remote_head)
+			die("%s - not something we can merge", argv[0]);
+		update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
+				DIE_ON_ERR);
+		reset_hard(remote_head->sha1, 0);
+		return 0;
+	} else {
+		struct strbuf msg;
+
+		/* We are invoked directly as the first-class UI. */
+		head_arg = "HEAD";
+
+		/*
+		 * All the rest are the commits being merged;
+		 * prepare the standard merge summary message to
+		 * be appended to the given message.  If remote
+		 * is invalid we will die later in the common
+		 * codepath so we discard the error in this
+		 * loop.
+		 */
+		strbuf_init(&msg, 0);
+		for (i = 0; i < argc; i++)
+			merge_name(argv[i], &msg);
+		fmt_merge_msg(option_log, &msg, &merge_msg);
+		if (merge_msg.len)
+			strbuf_setlen(&merge_msg, merge_msg.len-1);
+	}
+
+	if (head_invalid || !argc)
+		usage_with_options(builtin_merge_usage,
+			builtin_merge_options);
+
+	strbuf_addstr(&buf, "merge");
+	for (i = 0; i < argc; i++)
+		strbuf_addf(&buf, " %s", argv[i]);
+	setenv("GIT_REFLOG_ACTION", buf.buf, 0);
+	strbuf_reset(&buf);
+
+	for (i = 0; i < argc; i++) {
+		struct object *o;
+
+		o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
+		if (!o)
+			die("%s - not something we can merge", argv[i]);
+		remotes = &commit_list_insert(lookup_commit(o->sha1),
+			remotes)->next;
+
+		strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
+		setenv(buf.buf, argv[i], 1);
+		strbuf_reset(&buf);
+	}
+
+	if (!use_strategies.nr) {
+		if (!remoteheads->next)
+			add_strategies(pull_twohead, DEFAULT_TWOHEAD);
+		else
+			add_strategies(pull_octopus, DEFAULT_OCTOPUS);
+	}
+
+	for (i = 0; i < use_strategies.nr; i++) {
+		if ((unsigned int)use_strategies.items[i].util &
+			NO_FAST_FORWARD)
+			allow_fast_forward = 0;
+		if ((unsigned int)use_strategies.items[i].util & NO_TRIVIAL)
+			allow_trivial = 0;
+	}
+
+	if (!remoteheads->next)
+		common = get_merge_bases(lookup_commit(head),
+				remoteheads->item, 1);
+	else {
+		struct commit_list *list = remoteheads;
+		commit_list_insert(lookup_commit(head), &list);
+		common = get_octopus_merge_bases(list);
+		free(list);
+	}
+
+	update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
+		DIE_ON_ERR);
+
+	if (!common)
+		; /* No common ancestors found. We need a real merge. */
+	else if (!remoteheads->next && !common->next &&
+			common->item == remoteheads->item) {
+		/*
+		 * If head can reach all the merge then we are up to date.
+		 * but first the most common case of merging one remote.
+		 */
+		finish_up_to_date("Already up-to-date.");
+		return 0;
+	} else if (allow_fast_forward && !remoteheads->next &&
+			!common->next &&
+			!hashcmp(common->item->object.sha1, head)) {
+		/* Again the most common case of merging one remote. */
+		struct strbuf msg;
+		struct object *o;
+		char hex[41];
+
+		strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+
+		printf("Updating %s..%s\n",
+			hex,
+			find_unique_abbrev(remoteheads->item->object.sha1,
+			DEFAULT_ABBREV));
+		refresh_cache(REFRESH_QUIET);
+		strbuf_init(&msg, 0);
+		strbuf_addstr(&msg, "Fast forward");
+		if (have_message)
+			strbuf_addstr(&msg,
+				" (no commit created; -m option ignored)");
+		o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
+			0, NULL, OBJ_COMMIT);
+		if (!o)
+			return 0;
+
+		if (checkout_fast_forward(head, remoteheads->item->object.sha1))
+			return 0;
+
+		finish(o->sha1, msg.buf);
+		dropsave();
+		return 0;
+	} else if (!remoteheads->next && common->next)
+		;
+		/*
+		 * We are not doing octopus and not fast forward.  Need
+		 * a real merge.
+		 */
+	else if (!remoteheads->next && !common->next && option_commit) {
+		/*
+		 * We are not doing octopus, not fast forward, and have
+		 * only one common.
+		 */
+		refresh_cache(REFRESH_QUIET);
+		if (allow_trivial) {
+			/* See if it is really trivial. */
+			git_committer_info(IDENT_ERROR_ON_NO_NAME);
+			printf("Trying really trivial in-index merge...\n");
+			if (!read_tree_trivial(common->item->object.sha1,
+					head, remoteheads->item->object.sha1))
+				return merge_trivial();
+			printf("Nope.\n");
+		}
+	} else {
+		/*
+		 * An octopus.  If we can reach all the remote we are up
+		 * to date.
+		 */
+		int up_to_date = 1;
+		struct commit_list *j;
+
+		for (j = remoteheads; j; j = j->next) {
+			struct commit_list *common_one;
+
+			/*
+			 * Here we *have* to calculate the individual
+			 * merge_bases again, otherwise "git merge HEAD^
+			 * HEAD^^" would be missed.
+			 */
+			common_one = get_merge_bases(lookup_commit(head),
+				j->item, 1);
+			if (hashcmp(common_one->item->object.sha1,
+				j->item->object.sha1)) {
+				up_to_date = 0;
+				break;
+			}
+		}
+		if (up_to_date) {
+			finish_up_to_date("Already up-to-date. Yeeah!");
+			return 0;
+		}
+	}
+
+	/* We are going to make a new commit. */
+	git_committer_info(IDENT_ERROR_ON_NO_NAME);
+
+	/*
+	 * At this point, we need a real merge.  No matter what strategy
+	 * we use, it would operate on the index, possibly affecting the
+	 * working tree, and when resolved cleanly, have the desired
+	 * tree in the index -- this means that the index must be in
+	 * sync with the head commit.  The strategies are responsible
+	 * to ensure this.
+	 */
+	if (use_strategies.nr != 1) {
+		/*
+		 * Stash away the local changes so that we can try more
+		 * than one.
+		 */
+		save_state();
+		single_strategy = 0;
+	} else {
+		unlink(git_path("MERGE_STASH"));
+		single_strategy = 1;
+	}
+
+	for (i = 0; i < use_strategies.nr; i++) {
+		int ret;
+		if (i) {
+			printf("Rewinding the tree to pristine...\n");
+			restore_state();
+		}
+		if (!single_strategy)
+			printf("Trying merge strategy %s...\n",
+				use_strategies.items[i].path);
+		/*
+		 * Remember which strategy left the state in the working
+		 * tree.
+		 */
+		wt_strategy = &use_strategies.items[i];
+
+		ret = try_merge_strategy(use_strategies.items[i].path,
+			common, head_arg);
+		if (!option_commit && !ret) {
+			merge_was_ok = 1;
+			/*
+			 * This is necessary here just to avoid writing
+			 * the tree, but later we will *not* exit with
+			 * status code 1 because merge_was_ok is set.
+			 */
+			ret = 1;
+		}
+
+		if (ret) {
+			/*
+			 * The backend exits with 1 when conflicts are
+			 * left to be resolved, with 2 when it does not
+			 * handle the given merge at all.
+			 */
+			if (ret == 1) {
+				int cnt = 0;
+				struct rev_info rev;
+
+				if (read_cache() < 0)
+					die("failed to read the cache");
+
+				/* Check how many files differ. */
+				init_revisions(&rev, "");
+				setup_revisions(0, NULL, &rev, NULL);
+				rev.diffopt.output_format |=
+					DIFF_FORMAT_CALLBACK;
+				rev.diffopt.format_callback = count_diff_files;
+				rev.diffopt.format_callback_data = &cnt;
+				run_diff_files(&rev, 0);
+
+				/*
+				 * Check how many unmerged entries are
+				 * there.
+				 */
+				cnt += count_unmerged_entries();
+
+				if (best_cnt <= 0 || cnt <= best_cnt) {
+					best_strategy =
+						&use_strategies.items[i];
+					best_cnt = cnt;
+				}
+			}
+			if (merge_was_ok)
+				break;
+			else
+				continue;
+		}
+
+		/* Automerge succeeded. */
+		write_tree_trivial(result_tree);
+		automerge_was_ok = 1;
+		break;
+	}
+
+	/*
+	 * If we have a resulting tree, that means the strategy module
+	 * auto resolved the merge cleanly.
+	 */
+	if (automerge_was_ok)
+		return finish_automerge(common, result_tree, wt_strategy);
+
+	/*
+	 * Pick the result from the best strategy and have the user fix
+	 * it up.
+	 */
+	if (!best_strategy) {
+		restore_state();
+		if (use_strategies.nr > 1)
+			fprintf(stderr,
+				"No merge strategy handled the merge.\n");
+		else
+			fprintf(stderr, "Merge with strategy %s failed.\n",
+				use_strategies.items[0].path);
+		return 2;
+	} else if (best_strategy == wt_strategy)
+		; /* We already have its result in the working tree. */
+	else {
+		printf("Rewinding the tree to pristine...\n");
+		restore_state();
+		printf("Using the %s to prepare resolving by hand.\n",
+			best_strategy->path);
+		try_merge_strategy(best_strategy->path, common, head_arg);
+	}
+
+	if (squash)
+		finish(NULL, NULL);
+	else {
+		int fd;
+		struct commit_list *j;
+
+		for (j = remoteheads; j; j = j->next)
+			strbuf_addf(&buf, "%s\n",
+				sha1_to_hex(j->item->object.sha1));
+		fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
+		if (fd < 0)
+			die("Could open %s for writing",
+				git_path("MERGE_HEAD"));
+		if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+			die("Could not write to %s", git_path("MERGE_HEAD"));
+		close(fd);
+		strbuf_addch(&merge_msg, '\n');
+		fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+		if (fd < 0)
+			die("Could open %s for writing", git_path("MERGE_MSG"));
+		if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
+			merge_msg.len)
+			die("Could not write to %s", git_path("MERGE_MSG"));
+		close(fd);
+	}
+
+	if (merge_was_ok) {
+		fprintf(stderr, "Automatic merge went well; "
+			"stopped before committing as requested\n");
+		return 0;
+	} else
+		return suggest_conflicts();
+}
diff --git a/builtin.h b/builtin.h
index 2b01fea..8bf5280 100644
--- a/builtin.h
+++ b/builtin.h
@@ -60,6 +60,7 @@ extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_ls_remote(int argc, const char **argv, const char *prefix);
 extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
 extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
+extern int cmd_merge(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
diff --git a/git-merge.sh b/contrib/examples/git-merge.sh
similarity index 100%
rename from git-merge.sh
rename to contrib/examples/git-merge.sh
diff --git a/git.c b/git.c
index 2fbe96b..770aadd 100644
--- a/git.c
+++ b/git.c
@@ -271,6 +271,7 @@ static void handle_internal_command(int argc, const char **argv)
 		{ "ls-remote", cmd_ls_remote },
 		{ "mailinfo", cmd_mailinfo },
 		{ "mailsplit", cmd_mailsplit },
+		{ "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
 		{ "merge-base", cmd_merge_base, RUN_SETUP },
 		{ "merge-file", cmd_merge_file },
 		{ "merge-ours", cmd_merge_ours, RUN_SETUP },
diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh
index f3a4bb2..fcb8285 100755
--- a/t/t7602-merge-octopus-many.sh
+++ b/t/t7602-merge-octopus-many.sh
@@ -23,7 +23,7 @@ test_expect_success 'setup' '
 	done
 '
 
-test_expect_failure 'merge c1 with c2, c3, c4, ... c29' '
+test_expect_success 'merge c1 with c2, c3, c4, ... c29' '
 	git reset --hard c1 &&
 	i=2 &&
 	refs="" &&
-- 
1.5.6.1

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH 13/13] Build in merge
  2008-06-30  1:36                               ` Miklos Vajna
  2008-06-30  1:39                                 ` Miklos Vajna
@ 2008-06-30  5:40                                 ` Junio C Hamano
  2008-06-30 22:48                                   ` Miklos Vajna
  1 sibling, 1 reply; 81+ messages in thread
From: Junio C Hamano @ 2008-06-30  5:40 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: git, Johannes Schindelin, Olivier Marin

Miklos Vajna <vmiklos@frugalware.org> writes:

>> > +		if (!remote_head)
>> > +			die("%s - not something we can merge", argv[0]);
>> > +		update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
>> > +				DIE_ON_ERR);
>> > +		reset_hard(remote_head->sha1, 0);
>> > +		return 0;
>> 
>> Makes one wonder reset_hard() (aka "read-tree --reset -u HEAD") ever fail
>> and return here (iow, without calling die()).  The answer is luckily no
>> in this case, but it is somewhat unnerving to reviewers.
>
> Actually reset_hard does not return if an error occures:

I know that; didn't I already say "Luckily no"?  The point was it was not
apparent from the above 6 lines alone.

>> > +	for (i = 0; i < use_strategies.nr; i++) {
>> > +		if ((unsigned int)use_strategies.items[i].util &
>> > +			NO_FAST_FORWARD)
>> > +			allow_fast_forward = 0;
>> > +		if ((unsigned int)use_strategies.items[i].util & NO_TRIVIAL)
>> > +			allow_trivial = 0;
>> 
>> Can we abstract out these ugly casts?  Any code that use path_list to
>> store anything but list of paths (i.e. some value keyed with string) tends
>> to have this readability issue.
>
> If you don't cast, you can't use the & operator. If I change the
> path_list_item's util to be an unsigned number then I break fast-export.
> I think if we _really_ want to get rid of those casts, we could have
> something like:

No, no, no.  That is not what I meant.

The places that use use_strategies in your code knows too much about the
internal implementation detail of path_list, while path_list pretends to
be a general purpose "table keyed with string" facility.  The fact is that
the table is not a very useful general purpose abstraction unless you are
pointing at some structures that exist regardless of your use of path_list
(e.g. you have some "struct object" and you hold pointers in a path_list).
It does not work very well as an abstraction for use case like yours.

With something like:

        static inline unsigned nth_strategy_flags(struct path_list *s, int nth)
        {
                return (unsigned) s->items[nth].util;
        }

the checks would be more like:

	if (nth_strategy_flags(&use_strategies, i) & NO_FAST_FORWARD)
		...

or even:

        static inline check_nth_strategy_flags(struct path_list_item *i, unsigned flags)
        {
        	return !((unsigned) i->util & flags);
        }

        if (check_nth_strategy_flags(&use_strategies,items[i], NO_FAST_FORWARD)
        	...	

either of which would be much easier on the eye.


> Actually the shell version did not check here, either, but yes, I would
> have to. Now I do.

The scripted did not have to do so explicitly, as the one before that have
already caught "more than one common" case in the case arm before this
part.  It is already known that "have only one common" condition is true
when you reached the corresponding part of the scripted version.

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 13/13] Build in merge
  2008-06-30  1:39                                 ` Miklos Vajna
@ 2008-06-30  5:44                                   ` Junio C Hamano
  2008-06-30 17:41                                     ` Alex Riesen
  2008-07-01  2:13                                     ` Miklos Vajna
  2008-06-30 22:44                                   ` [PATCH 13/13] " Olivier Marin
  1 sibling, 2 replies; 81+ messages in thread
From: Junio C Hamano @ 2008-06-30  5:44 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: git, Johannes Schindelin, Olivier Marin

Miklos Vajna <vmiklos@frugalware.org> writes:

> diff --git a/builtin-merge.c b/builtin-merge.c
> new file mode 100644
> index 0000000..187038c
> --- /dev/null
> +++ b/builtin-merge.c
> @@ -0,0 +1,1173 @@
> ...
> +static void save_state(void)
> +{
> +	int fd;
> +	struct child_process stash;
> +	const char *argv[] = {"stash", "create", NULL};
> +
> +	fd = open(git_path("MERGE_STASH"), O_WRONLY | O_CREAT, 0666);
> +	if (fd < 0)
> +		die("Could not write to %s", git_path("MERGE_STASH"));
> +	memset(&stash, 0, sizeof(stash));
> +	stash.argv = argv;
> +	stash.out = fd;
> +	stash.git_cmd = 1;
> +	run_command(&stash);
> +}

I first thought "heh, that's clever" until I noticed that we use "stash
create" with "stash apply" these days instead of cpio for this.  I suspect
that we can do away without leaving the stash in this temporary file, but
that comment applies to the scripted version as well.

By the way, it would be consistent to name counterpart to dropsave in the
scripted version as "drop_save" if you use "save_state" and "restore_state".

> +static void reset_hard(unsigned const char *sha1, int verbose)
> +{
> +	struct tree *tree;
> +	struct unpack_trees_options opts;
> +	struct tree_desc t;
> +
> +	memset(&opts, 0, sizeof(opts));
> +	opts.head_idx = -1;
> +	opts.src_index = &the_index;
> +	opts.dst_index = &the_index;
> +	opts.update = 1;
> +	opts.reset = 1;
> +	if (verbose)
> +		opts.verbose_update = 1;
> +
> +	tree = parse_tree_indirect(sha1);
> +	if (!tree)
> +		die("failed to unpack %s tree object", sha1_to_hex(sha1));
> +	parse_tree(tree);
> +	init_tree_desc(&t, tree->buffer, tree->size);
> +	if (unpack_trees(1, &t, &opts))
> +		exit(128); /* We've already reported the error, finish dying */
> +}

Isn't this trashing all the cached stat info from the index?  If this is
emulating "reset --hard", it also should set opts.merge and do
oneway_merge, after reading the current index in, I think.  Resetting the
index and the working tree is not particularly performance critical part,
but trashing the cached stat info would hurt the performance of everything
that reads the index after this function returns quite badly.  I suspect
that you might be better off forking the real thing (reset --hard) if you
cannot get it right here.

> +/* Get the name for the merge commit's message. */
> +static void merge_name(const char *remote, struct strbuf *msg)
> ...
> +	strbuf_init(&buf, 0);
> +	strbuf_addstr(&buf, "refs/heads/");
> +	strbuf_addstr(&buf, remote);
> +	dwim_ref(buf.buf, buf.len, branch_head, &ref);
> +	if (!hashcmp(remote_head->sha1, branch_head)) {
> +		strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
> +			sha1_to_hex(branch_head), remote);
> +		return;
> +	}

Hmm, why not resolve_ref() so that it does not dwim at all?  The point of
the code is so that you can be confident that 'blah' *is* a local branch
when you say "branch 'blah'".

> +	/* See if remote matches <name>~<number>, or <name>^ */
> +	ptr = strrchr(remote, '^');
> +	if (ptr && ptr[1] == '\0') {
> +		len = strlen(remote);
> +		while ((ptr = (char *)memrchr(remote, '^', len)))
> +			if (ptr && ptr[1] == '\0')
> +				len = ptr - remote - 1;
> +			else
> +				break;

That's a funny way to say:

	for (len = 0, ptr = remote + strlen(remote);
             remote < ptr && ptr[-1] == '^';
             ptr--)
		len++;

> +	if (len) {
> +		struct strbuf truname = STRBUF_INIT;
> +		strbuf_addstr(&truname, remote);
> +		strbuf_setlen(&truname, len);
> +		if (dwim_ref(truname.buf, truname.len, buf_sha, &ref)) {
> +			strbuf_addf(msg,
> +				"%s\t\tbranch '%s' (early part) of .\n",
> +				sha1_to_hex(remote_head->sha1), truname.buf);
> +			return;

Isn't this wrong?  Giving "v1.5.6~20" to this code will strip ~20 and make
remote = "v1.5.6", to which dwim_ref() will happily say Ok, and you end up
saying "branch 'v1.5.6' (early part)", don't you?

> +static int read_tree_trivial(unsigned char *common, unsigned char *head,
> +	unsigned char *one)
> +{
> +	int i, nr_trees = 0;
> +	struct tree *trees[MAX_UNPACK_TREES];
> +	struct tree_desc t[MAX_UNPACK_TREES];
> +	struct unpack_trees_options opts;
> +
> +	memset(&opts, 0, sizeof(opts));
> +	opts.head_idx = -1;

Is this the correct head_idx value for this three-way merge?  I think it
should be 2 but please double check.

> +static int commit_tree_trivial(const char *msg, unsigned const char *tree,
> +		struct commit_list *parents, unsigned char *ret)
> +{
>  ...
> +}

We may want to have another patch before this one to abstract most of
cmd_commit_tree() out, perhaps?

> +int cmd_merge(int argc, const char **argv, const char *prefix)
> ...
> +	/*
> +	 * This could be traditional "merge <msg> HEAD <commit>..."  and
> +	 * the way we can tell it is to see if the second token is HEAD,
> +	 * but some people might have misused the interface and used a
> +	 * committish that is the same as HEAD there instead.
> +	 * Traditional format never would have "-m" so it is an
> +	 * additional safety measure to check for it.
> +	 */
> +	strbuf_init(&buf, 0);
> +	if (argc > 1) {
> +		unsigned char second_sha1[20];
> +
> +		if (get_sha1(argv[1], second_sha1))
> +			die("Not a valid ref: %s", argv[1]);
> +		second_token = lookup_commit_reference_gently(second_sha1, 0);
> +		if (!second_token)
> +			die("'%s' is not a commit", argv[1]);

Interesting.

This _superficially_ is quite wrong, because the purpose of this part of
the code is to tell if we got old-style invocation, and we should not
barfing merely because what we got is _not_ old-style.  If it is not
old-style, then it would be new-style, and the logic to tell if it is
old-style should ideally not have much knowledge about the new-style
invocation to say "hey, that's an incocrrect new-style invocation".  By
the way, this part should probably be in a separate function:

	static int is_old_style_invocation(int ac, const char **gv);

Old-style invocation of "git merge" (primarily by "git pull") was
to call it as:

	git merge "message here" HEAD $commit1 $commit2...

and it checks the second token ("HEAD" in the above, but people can misuse
the interface to name the current branch name).  If the second token is
not a ref that resolves to a commit, all you know is that this is _not_ an
old-style invocation, and calling the program with new-style is not a
crime.

The only reason this is wrong only superficially is because new style
invocation would always be:

	git merge [options] $commit1 $commit2...

after stripping the options, and these seemingly wrong die() will complain
when you try to create an Octopus with the new-style syntax and the
parameter given as the second remote parent is not a commit.  So the logic
is wrong, the fact that the user gets the same error message for incorrect
old-style invocation (perhaps "git merge <msg> HAED $commit") and
incorrect new-style invocation "git merge $commit1 $nonsense" is just an
accident, and the end result does not hurt, but asks for a "Huh? why does
it check and complain only the second parent here but not the first one?".

It is interesting, but feels quite dirty.

> +	}
> +
> +	if (!have_message && second_token &&
> +		!hashcmp(second_token->object.sha1, head)) {

You need to know that resolve_ref() cleared head[] when head_invalid is
true when reading this code to notice that, unlike the previous round of
this patch, it is Ok not to check head_invalid is fine here.  I somehow
feel it is an unnecessary optimization/obfuscation.

But once you have "is_old_style_invocation" suggested earlier, this part
would look much cleaner and the above comment would become unnecessary.

> +	for (i = 0; i < argc; i++) {
> +		struct object *o;
> +
> +		o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
> +		if (!o)
> +			die("%s - not something we can merge", argv[i]);
> +		remotes = &commit_list_insert(lookup_commit(o->sha1),
> +			remotes)->next;
> +
> +		strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
> +		setenv(buf.buf, argv[i], 1);
> +		strbuf_reset(&buf);
> +	}
> +
> +		o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
> +			0, NULL, OBJ_COMMIT);
> +		if (!o)
> +			return 0;
> +
> +		if (checkout_fast_forward(head, remoteheads->item->object.sha1))
> +			return 0;

When o does not peel well, or checkout_fast_forward() returns failure,
that would be a failure case, wouldn't it?  Why return 0?

Maybe you misread "exit" in shell scripts?  It does not mean exit(0); it
means "exit with the same exit status as the last command".  So

	new_head=$(git rev-parse ...) &&
        git read-tree -m -u ... &&
        finish || exit

will exit non-zero if any of the commands chained by && fails.

> +		if (allow_trivial) {
> +			/* See if it is really trivial. */
> +			git_committer_info(IDENT_ERROR_ON_NO_NAME);
> +			printf("Trying really trivial in-index merge...\n");
> +			if (!read_tree_trivial(common->item->object.sha1,
> +					head, remoteheads->item->object.sha1))
> +				return merge_trivial();
> +			printf("Nope.\n");

Nicer, much nicer.

> +	/*
> +	 * At this point, we need a real merge.  No matter what strategy
> +	 * we use, it would operate on the index, possibly affecting the
> +	 * working tree, and when resolved cleanly, have the desired
> +	 * tree in the index -- this means that the index must be in
> +	 * sync with the head commit.  The strategies are responsible
> +	 * to ensure this.
> +	 */
> +	if (use_strategies.nr != 1) {
> +		/*
> +		 * Stash away the local changes so that we can try more
> +		 * than one.
> +		 */
> +		save_state();
> +		single_strategy = 0;
> +	} else {
> +		unlink(git_path("MERGE_STASH"));
> +		single_strategy = 1;

I think s/single_strategy/(use_strategies.nr == 1)/ in the remainder of the
code would be taking advantage of working in C ;-)

> +		if (ret) {
> +			/*
> +			 * The backend exits with 1 when conflicts are
> +			 * left to be resolved, with 2 when it does not
> +			 * handle the given merge at all.
> +			 */
> +			if (ret == 1) {

Probably from here til ...

> +				int cnt = 0;
> ...
> +				cnt += count_unmerged_entries();

... here should be a separate "evaluate_result()" function.

> +				if (best_cnt <= 0 || cnt <= best_cnt) {
> +					best_strategy =
> +						&use_strategies.items[i];
> +					best_cnt = cnt;
> +				}
> +			}
> +			if (merge_was_ok)
> +				break;
> +			else
> +				continue;
> +		}
> +
> +		/* Automerge succeeded. */
> +		write_tree_trivial(result_tree);
> +		automerge_was_ok = 1;
> +		break;
> +	}

The part of the file I did not comment in this message (except for the
"cast" thing) looked straightforward enough.  I did not very carefully
hunt for bugs, but I did not see anything obviously wrong.

Thanks.

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 13/13] Build in merge
  2008-06-30  5:44                                   ` Junio C Hamano
@ 2008-06-30 17:41                                     ` Alex Riesen
  2008-07-01  2:13                                     ` Miklos Vajna
  1 sibling, 0 replies; 81+ messages in thread
From: Alex Riesen @ 2008-06-30 17:41 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Miklos Vajna, git, Johannes Schindelin, Olivier Marin

2008/6/30 Junio C Hamano <gitster@pobox.com>:
> Miklos Vajna <vmiklos@frugalware.org> writes:
>> +     /* See if remote matches <name>~<number>, or <name>^ */
>> +     ptr = strrchr(remote, '^');
>> +     if (ptr && ptr[1] == '\0') {
>> +             len = strlen(remote);
>> +             while ((ptr = (char *)memrchr(remote, '^', len)))
>> +                     if (ptr && ptr[1] == '\0')
>> +                             len = ptr - remote - 1;
>> +                     else
>> +                             break;
>
> That's a funny way to say:
>
>        for (len = 0, ptr = remote + strlen(remote);
>             remote < ptr && ptr[-1] == '^';
>             ptr--)
>                len++;
>

Besides, Cygwin has no memrchr

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 13/13] Build in merge
  2008-06-30  1:39                                 ` Miklos Vajna
  2008-06-30  5:44                                   ` Junio C Hamano
@ 2008-06-30 22:44                                   ` Olivier Marin
  2008-06-30 22:58                                     ` Miklos Vajna
  1 sibling, 1 reply; 81+ messages in thread
From: Olivier Marin @ 2008-06-30 22:44 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: Junio C Hamano, git, Johannes Schindelin

Miklos Vajna a écrit :
> 
> +static void finish(const unsigned char *new_head, const char *msg)
> +{

[...]

> +	if (new_head && show_diffstat) {
> +		struct diff_options opts;
> +		diff_setup(&opts);
> +		opts.output_format |=
> +			DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
> +		opts.detect_rename = DIFF_DETECT_RENAME;
> +		if (diff_use_color_default > 0)
> +			DIFF_OPT_SET(&opts, COLOR_DIFF);

Ah, I missed that. You should call diff_setup_done(), to finish diff_setup()
and have a recursive diffstat.

For example:

$ git checkout -b test c0f5c69
$ git merge 2dce956
Updating c0f5c69..2dce956
Fast forward
 help.c |   22 ++++++++++++++--------
 1 files changed, 14 insertions(+), 8 deletions(-)

is wrong. Correct diffstat is:

$ git diff --stat c0f5c69..2dce956
 Documentation/gitcli.txt        |   37 +++++++++++++++++++++++++++++++++----
 Documentation/gittutorial-2.txt |   10 +++++-----
 help.c                          |   22 ++++++++++++++--------
 t/test-lib.sh                   |    2 +-
 4 files changed, 53 insertions(+), 18 deletions(-)

> +		diff_tree_sha1(head, new_head, "", &opts);
> +		diffcore_std(&opts);
> +		diff_flush(&opts);
> +	}

[...]

> +int cmd_merge(int argc, const char **argv, const char *prefix)
> +{

[...]

> +	git_config(git_merge_config, NULL);
> +
> +	/* for color.diff and diff.color */
> +	git_config(git_diff_ui_config, NULL);

Also, what about doing "return git_diff_ui_config(k, v, cb)" at the end of
git_merge_config() instead, to avoid parsing config files twice.

> +
> +	/* for color.ui */
> +	if (diff_use_color_default == -1)
> +		diff_use_color_default = git_use_color_default;
> +

Olivier.

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 13/13] Build in merge
  2008-06-30  5:40                                 ` Junio C Hamano
@ 2008-06-30 22:48                                   ` Miklos Vajna
  0 siblings, 0 replies; 81+ messages in thread
From: Miklos Vajna @ 2008-06-30 22:48 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

[-- Attachment #1: Type: text/plain, Size: 2370 bytes --]

On Sun, Jun 29, 2008 at 10:40:53PM -0700, Junio C Hamano <gitster@pobox.com> wrote:
> > Actually reset_hard does not return if an error occures:
> 
> I know that; didn't I already say "Luckily no"?  The point was it was not
> apparent from the above 6 lines alone.

Ah, OK.

> 
> >> > +	for (i = 0; i < use_strategies.nr; i++) {
> >> > +		if ((unsigned int)use_strategies.items[i].util &
> >> > +			NO_FAST_FORWARD)
> >> > +			allow_fast_forward = 0;
> >> > +		if ((unsigned int)use_strategies.items[i].util & NO_TRIVIAL)
> >> > +			allow_trivial = 0;
> >> 
> >> Can we abstract out these ugly casts?  Any code that use path_list to
> >> store anything but list of paths (i.e. some value keyed with string) tends
> >> to have this readability issue.
> >
> > If you don't cast, you can't use the & operator. If I change the
> > path_list_item's util to be an unsigned number then I break fast-export.
> > I think if we _really_ want to get rid of those casts, we could have
> > something like:
> 
> No, no, no.  That is not what I meant.
> 
> The places that use use_strategies in your code knows too much about the
> internal implementation detail of path_list, while path_list pretends to
> be a general purpose "table keyed with string" facility.  The fact is that
> the table is not a very useful general purpose abstraction unless you are
> pointing at some structures that exist regardless of your use of path_list
> (e.g. you have some "struct object" and you hold pointers in a path_list).
> It does not work very well as an abstraction for use case like yours.
> 
> With something like:
> 
>         static inline unsigned nth_strategy_flags(struct path_list *s, int nth)
>         {
>                 return (unsigned) s->items[nth].util;
>         }
> 
> the checks would be more like:
> 
> 	if (nth_strategy_flags(&use_strategies, i) & NO_FAST_FORWARD)
> 		...
> 
> or even:
> 
>         static inline check_nth_strategy_flags(struct path_list_item *i, unsigned flags)
>         {
>         	return !((unsigned) i->util & flags);
>         }
> 
>         if (check_nth_strategy_flags(&use_strategies,items[i], NO_FAST_FORWARD)
>         	...	
> 
> either of which would be much easier on the eye.

Probably this is subjective, but I think the previous form is easier to
read, so I choose that one.

[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 01/15] Move split_cmdline() to alias.c
  2008-06-29 14:43         ` Johannes Schindelin
@ 2008-06-30 22:51           ` Olivier Marin
  0 siblings, 0 replies; 81+ messages in thread
From: Olivier Marin @ 2008-06-30 22:51 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Miklos Vajna, Junio C Hamano, git

Johannes Schindelin a écrit :
> 
> I am generally not a fan of crossing bridges miles before you reach them.
> 

OK, as you want. :-) I don't really care about this patch, anyway.

Olivier.

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 13/13] Build in merge
  2008-06-30 22:44                                   ` [PATCH 13/13] " Olivier Marin
@ 2008-06-30 22:58                                     ` Miklos Vajna
  0 siblings, 0 replies; 81+ messages in thread
From: Miklos Vajna @ 2008-06-30 22:58 UTC (permalink / raw)
  To: Olivier Marin; +Cc: Junio C Hamano, git, Johannes Schindelin

[-- Attachment #1: Type: text/plain, Size: 1474 bytes --]

On Tue, Jul 01, 2008 at 12:44:23AM +0200, Olivier Marin <dkr+ml.git@free.fr> wrote:
> > +	if (new_head && show_diffstat) {
> > +		struct diff_options opts;
> > +		diff_setup(&opts);
> > +		opts.output_format |=
> > +			DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
> > +		opts.detect_rename = DIFF_DETECT_RENAME;
> > +		if (diff_use_color_default > 0)
> > +			DIFF_OPT_SET(&opts, COLOR_DIFF);
> 
> Ah, I missed that. You should call diff_setup_done(), to finish diff_setup()
> and have a recursive diffstat.
> 
> For example:
> 
> $ git checkout -b test c0f5c69
> $ git merge 2dce956
> Updating c0f5c69..2dce956
> Fast forward
>  help.c |   22 ++++++++++++++--------
>  1 files changed, 14 insertions(+), 8 deletions(-)
> 
> is wrong. Correct diffstat is:
> 
> $ git diff --stat c0f5c69..2dce956
>  Documentation/gitcli.txt        |   37 +++++++++++++++++++++++++++++++++----
>  Documentation/gittutorial-2.txt |   10 +++++-----
>  help.c                          |   22 ++++++++++++++--------
>  t/test-lib.sh                   |    2 +-
>  4 files changed, 53 insertions(+), 18 deletions(-)

Fixed in my working branch.

> > +	git_config(git_merge_config, NULL);
> > +
> > +	/* for color.diff and diff.color */
> > +	git_config(git_diff_ui_config, NULL);
> 
> Also, what about doing "return git_diff_ui_config(k, v, cb)" at the end of
> git_merge_config() instead, to avoid parsing config files twice.

Sure, changed.

Thanks! :)

[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 13/13] Build in merge
  2008-06-30  5:44                                   ` Junio C Hamano
  2008-06-30 17:41                                     ` Alex Riesen
@ 2008-07-01  2:13                                     ` Miklos Vajna
  2008-07-01  2:22                                       ` [PATCH 13/14] git-commit-tree: make it usable from other builtins Miklos Vajna
  2008-07-01  2:22                                       ` [PATCH 14/14] Build in merge Miklos Vajna
  1 sibling, 2 replies; 81+ messages in thread
From: Miklos Vajna @ 2008-07-01  2:13 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

[-- Attachment #1: Type: text/plain, Size: 11364 bytes --]

On Sun, Jun 29, 2008 at 10:44:43PM -0700, Junio C Hamano <gitster@pobox.com> wrote:
> > +static void save_state(void)
> > +{
> > +	int fd;
> > +	struct child_process stash;
> > +	const char *argv[] = {"stash", "create", NULL};
> > +
> > +	fd = open(git_path("MERGE_STASH"), O_WRONLY | O_CREAT, 0666);
> > +	if (fd < 0)
> > +		die("Could not write to %s", git_path("MERGE_STASH"));
> > +	memset(&stash, 0, sizeof(stash));
> > +	stash.argv = argv;
> > +	stash.out = fd;
> > +	stash.git_cmd = 1;
> > +	run_command(&stash);
> > +}
> 
> I first thought "heh, that's clever" until I noticed that we use "stash
> create" with "stash apply" these days instead of cpio for this.  I suspect
> that we can do away without leaving the stash in this temporary file, but
> that comment applies to the scripted version as well.

We can. I just did it. ;-)

> By the way, it would be consistent to name counterpart to dropsave in the
> scripted version as "drop_save" if you use "save_state" and "restore_state".

OK, renamed.

> 
> > +static void reset_hard(unsigned const char *sha1, int verbose)
> > +{
> > +	struct tree *tree;
> > +	struct unpack_trees_options opts;
> > +	struct tree_desc t;
> > +
> > +	memset(&opts, 0, sizeof(opts));
> > +	opts.head_idx = -1;
> > +	opts.src_index = &the_index;
> > +	opts.dst_index = &the_index;
> > +	opts.update = 1;
> > +	opts.reset = 1;
> > +	if (verbose)
> > +		opts.verbose_update = 1;
> > +
> > +	tree = parse_tree_indirect(sha1);
> > +	if (!tree)
> > +		die("failed to unpack %s tree object", sha1_to_hex(sha1));
> > +	parse_tree(tree);
> > +	init_tree_desc(&t, tree->buffer, tree->size);
> > +	if (unpack_trees(1, &t, &opts))
> > +		exit(128); /* We've already reported the error, finish dying */
> > +}
> 
> Isn't this trashing all the cached stat info from the index?  If this is
> emulating "reset --hard", it also should set opts.merge and do
> oneway_merge, after reading the current index in, I think.  Resetting the
> index and the working tree is not particularly performance critical part,
> but trashing the cached stat info would hurt the performance of everything
> that reads the index after this function returns quite badly.  I suspect
> that you might be better off forking the real thing (reset --hard) if you
> cannot get it right here.

I just realized that builtin-reset forks read-tree as well, so I did
almost the same.

> > +/* Get the name for the merge commit's message. */
> > +static void merge_name(const char *remote, struct strbuf *msg)
> > ...
> > +	strbuf_init(&buf, 0);
> > +	strbuf_addstr(&buf, "refs/heads/");
> > +	strbuf_addstr(&buf, remote);
> > +	dwim_ref(buf.buf, buf.len, branch_head, &ref);
> > +	if (!hashcmp(remote_head->sha1, branch_head)) {
> > +		strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
> > +			sha1_to_hex(branch_head), remote);
> > +		return;
> > +	}
> 
> Hmm, why not resolve_ref() so that it does not dwim at all?  The point of
> the code is so that you can be confident that 'blah' *is* a local branch
> when you say "branch 'blah'".

Yes. I'm now using resolve_ref().

> > +	/* See if remote matches <name>~<number>, or <name>^ */
> > +	ptr = strrchr(remote, '^');
> > +	if (ptr && ptr[1] == '\0') {
> > +		len = strlen(remote);
> > +		while ((ptr = (char *)memrchr(remote, '^', len)))
> > +			if (ptr && ptr[1] == '\0')
> > +				len = ptr - remote - 1;
> > +			else
> > +				break;
> 
> That's a funny way to say:
> 
> 	for (len = 0, ptr = remote + strlen(remote);
>              remote < ptr && ptr[-1] == '^';
>              ptr--)
> 		len++;

Ah, and this way I don't need memrchr(), which was pointed out to be
problemtic on Cygwin.

> 
> > +	if (len) {
> > +		struct strbuf truname = STRBUF_INIT;
> > +		strbuf_addstr(&truname, remote);
> > +		strbuf_setlen(&truname, len);
> > +		if (dwim_ref(truname.buf, truname.len, buf_sha, &ref)) {
> > +			strbuf_addf(msg,
> > +				"%s\t\tbranch '%s' (early part) of .\n",
> > +				sha1_to_hex(remote_head->sha1), truname.buf);
> > +			return;
> 
> Isn't this wrong?  Giving "v1.5.6~20" to this code will strip ~20 and make
> remote = "v1.5.6", to which dwim_ref() will happily say Ok, and you end up
> saying "branch 'v1.5.6' (early part)", don't you?

Right. Now I do

        strbuf_addstr(&truname, "refs/heads/");

Before appending the remote name to truname, so that should exclude
tags.

> > +static int read_tree_trivial(unsigned char *common, unsigned char *head,
> > +	unsigned char *one)
> > +{
> > +	int i, nr_trees = 0;
> > +	struct tree *trees[MAX_UNPACK_TREES];
> > +	struct tree_desc t[MAX_UNPACK_TREES];
> > +	struct unpack_trees_options opts;
> > +
> > +	memset(&opts, 0, sizeof(opts));
> > +	opts.head_idx = -1;
> 
> Is this the correct head_idx value for this three-way merge?  I think it
> should be 2 but please double check.

Yes, you are right. I just checked builtin-read-tree and it's 2, not -1.

> > +static int commit_tree_trivial(const char *msg, unsigned const char *tree,
> > +		struct commit_list *parents, unsigned char *ret)
> > +{
> >  ...
> > +}
> 
> We may want to have another patch before this one to abstract most of
> cmd_commit_tree() out, perhaps?

Done. And now builtin-merge uses commit_tree() as well.

> > +int cmd_merge(int argc, const char **argv, const char *prefix)
> > ...
> > +	/*
> > +	 * This could be traditional "merge <msg> HEAD <commit>..."  and
> > +	 * the way we can tell it is to see if the second token is HEAD,
> > +	 * but some people might have misused the interface and used a
> > +	 * committish that is the same as HEAD there instead.
> > +	 * Traditional format never would have "-m" so it is an
> > +	 * additional safety measure to check for it.
> > +	 */
> > +	strbuf_init(&buf, 0);
> > +	if (argc > 1) {
> > +		unsigned char second_sha1[20];
> > +
> > +		if (get_sha1(argv[1], second_sha1))
> > +			die("Not a valid ref: %s", argv[1]);
> > +		second_token = lookup_commit_reference_gently(second_sha1, 0);
> > +		if (!second_token)
> > +			die("'%s' is not a commit", argv[1]);
> 
> Interesting.
> 
> This _superficially_ is quite wrong, because the purpose of this part of
> the code is to tell if we got old-style invocation, and we should not
> barfing merely because what we got is _not_ old-style.  If it is not
> old-style, then it would be new-style, and the logic to tell if it is
> old-style should ideally not have much knowledge about the new-style
> invocation to say "hey, that's an incocrrect new-style invocation".  By
> the way, this part should probably be in a separate function:
> 
> 	static int is_old_style_invocation(int ac, const char **gv);

OK, I broke out is_old_style_invocation() from cmd_merge().

> Old-style invocation of "git merge" (primarily by "git pull") was
> to call it as:
> 
> 	git merge "message here" HEAD $commit1 $commit2...
> 
> and it checks the second token ("HEAD" in the above, but people can misuse
> the interface to name the current branch name).  If the second token is
> not a ref that resolves to a commit, all you know is that this is _not_ an
> old-style invocation, and calling the program with new-style is not a
> crime.
> 
> The only reason this is wrong only superficially is because new style
> invocation would always be:
> 
> 	git merge [options] $commit1 $commit2...
> 
> after stripping the options, and these seemingly wrong die() will complain
> when you try to create an Octopus with the new-style syntax and the
> parameter given as the second remote parent is not a commit.  So the logic
> is wrong, the fact that the user gets the same error message for incorrect
> old-style invocation (perhaps "git merge <msg> HAED $commit") and
> incorrect new-style invocation "git merge $commit1 $nonsense" is just an
> accident, and the end result does not hurt, but asks for a "Huh? why does
> it check and complain only the second parent here but not the first one?".
> 
> It is interesting, but feels quite dirty.

Now if the second token is a valid SHA1 then I die() if it's not a
commit, but otherwise I just assume it's a new-style invocation.

> > +	if (!have_message && second_token &&
> > +		!hashcmp(second_token->object.sha1, head)) {
> 
> You need to know that resolve_ref() cleared head[] when head_invalid is
> true when reading this code to notice that, unlike the previous round of
> this patch, it is Ok not to check head_invalid is fine here.  I somehow
> feel it is an unnecessary optimization/obfuscation.
> 
> But once you have "is_old_style_invocation" suggested earlier, this part
> would look much cleaner and the above comment would become unnecessary.

Yes, now it's just:

       if (!have_message && is_old_style_invocation(argc, argv)) {

> 
> > +	for (i = 0; i < argc; i++) {
> > +		struct object *o;
> > +
> > +		o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
> > +		if (!o)
> > +			die("%s - not something we can merge", argv[i]);
> > +		remotes = &commit_list_insert(lookup_commit(o->sha1),
> > +			remotes)->next;
> > +
> > +		strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
> > +		setenv(buf.buf, argv[i], 1);
> > +		strbuf_reset(&buf);
> > +	}
> > +
> > +		o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
> > +			0, NULL, OBJ_COMMIT);
> > +		if (!o)
> > +			return 0;
> > +
> > +		if (checkout_fast_forward(head, remoteheads->item->object.sha1))
> > +			return 0;
> 
> When o does not peel well, or checkout_fast_forward() returns failure,
> that would be a failure case, wouldn't it?  Why return 0?
> 
> Maybe you misread "exit" in shell scripts?  It does not mean exit(0); it
> means "exit with the same exit status as the last command".  So
> 
> 	new_head=$(git rev-parse ...) &&
>         git read-tree -m -u ... &&
>         finish || exit
> 
> will exit non-zero if any of the commands chained by && fails.

Thanks, that was the case. I thought "false || exit" exits with status
code 0.

> > +	/*
> > +	 * At this point, we need a real merge.  No matter what strategy
> > +	 * we use, it would operate on the index, possibly affecting the
> > +	 * working tree, and when resolved cleanly, have the desired
> > +	 * tree in the index -- this means that the index must be in
> > +	 * sync with the head commit.  The strategies are responsible
> > +	 * to ensure this.
> > +	 */
> > +	if (use_strategies.nr != 1) {
> > +		/*
> > +		 * Stash away the local changes so that we can try more
> > +		 * than one.
> > +		 */
> > +		save_state();
> > +		single_strategy = 0;
> > +	} else {
> > +		unlink(git_path("MERGE_STASH"));
> > +		single_strategy = 1;
> 
> I think s/single_strategy/(use_strategies.nr == 1)/ in the remainder of the
> code would be taking advantage of working in C ;-)

I dropped single_strategy.

> 
> > +		if (ret) {
> > +			/*
> > +			 * The backend exits with 1 when conflicts are
> > +			 * left to be resolved, with 2 when it does not
> > +			 * handle the given merge at all.
> > +			 */
> > +			if (ret == 1) {
> 
> Probably from here til ...
> 
> > +				int cnt = 0;
> > ...
> > +				cnt += count_unmerged_entries();
> 
> ... here should be a separate "evaluate_result()" function.

Done.

[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH 13/14] git-commit-tree: make it usable from other builtins
  2008-07-01  2:13                                     ` Miklos Vajna
@ 2008-07-01  2:22                                       ` Miklos Vajna
  2008-07-01  5:07                                         ` Johannes Schindelin
  2008-07-01  5:50                                         ` Junio C Hamano
  2008-07-01  2:22                                       ` [PATCH 14/14] Build in merge Miklos Vajna
  1 sibling, 2 replies; 81+ messages in thread
From: Miklos Vajna @ 2008-07-01  2:22 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

Move all functionality (except option parsing) from cmd_commit_tree() to
commit_tree(), so that other builtins can use it without a child
process.

Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---

This patch is against master, you need to pull from me or first rebase
mv/merge-in-c against current master.

I did it this way to avoid an unnecessary conflict with Dscho's recent
patch to commit-tree (ef98c5c).

 builtin-commit-tree.c |   71 ++++++++++++++++++++++++++++--------------------
 builtin.h             |    4 +++
 2 files changed, 45 insertions(+), 30 deletions(-)

diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c
index 3881f6c..7a9a309 100644
--- a/builtin-commit-tree.c
+++ b/builtin-commit-tree.c
@@ -45,41 +45,19 @@ static const char commit_utf8_warn[] =
 "You may want to amend it after fixing the message, or set the config\n"
 "variable i18n.commitencoding to the encoding your project uses.\n";
 
-int cmd_commit_tree(int argc, const char **argv, const char *prefix)
+int commit_tree(const char *msg, unsigned char *tree,
+		struct commit_list *parents, unsigned char *ret)
 {
-	int i;
-	struct commit_list *parents = NULL;
-	unsigned char tree_sha1[20];
-	unsigned char commit_sha1[20];
-	struct strbuf buffer;
 	int encoding_is_utf8;
+	struct strbuf buffer;
 
-	git_config(git_default_config, NULL);
-
-	if (argc < 2)
-		usage(commit_tree_usage);
-	if (get_sha1(argv[1], tree_sha1))
-		die("Not a valid object name %s", argv[1]);
-
-	check_valid(tree_sha1, OBJ_TREE);
-	for (i = 2; i < argc; i += 2) {
-		unsigned char sha1[20];
-		const char *a, *b;
-		a = argv[i]; b = argv[i+1];
-		if (!b || strcmp(a, "-p"))
-			usage(commit_tree_usage);
-
-		if (get_sha1(b, sha1))
-			die("Not a valid object name %s", b);
-		check_valid(sha1, OBJ_COMMIT);
-		new_parent(lookup_commit(sha1), &parents);
-	}
+	check_valid(tree, OBJ_TREE);
 
 	/* Not having i18n.commitencoding is the same as having utf-8 */
 	encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
 
 	strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */
-	strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree_sha1));
+	strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree));
 
 	/*
 	 * NOTE! This ordering means that the same exact tree merged with a
@@ -102,14 +80,47 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 	strbuf_addch(&buffer, '\n');
 
 	/* And add the comment */
-	if (strbuf_read(&buffer, 0, 0) < 0)
-		die("git-commit-tree: read returned %s", strerror(errno));
+	strbuf_addstr(&buffer, msg);
 
 	/* And check the encoding */
 	if (encoding_is_utf8 && !is_utf8(buffer.buf))
 		fprintf(stderr, commit_utf8_warn);
 
-	if (!write_sha1_file(buffer.buf, buffer.len, commit_type, commit_sha1)) {
+	return write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
+}
+
+int cmd_commit_tree(int argc, const char **argv, const char *prefix)
+{
+	int i;
+	struct commit_list *parents = NULL;
+	unsigned char tree_sha1[20];
+	unsigned char commit_sha1[20];
+	struct strbuf buffer = STRBUF_INIT;
+
+	git_config(git_default_config, NULL);
+
+	if (argc < 2)
+		usage(commit_tree_usage);
+	if (get_sha1(argv[1], tree_sha1))
+		die("Not a valid object name %s", argv[1]);
+
+	for (i = 2; i < argc; i += 2) {
+		unsigned char sha1[20];
+		const char *a, *b;
+		a = argv[i]; b = argv[i+1];
+		if (!b || strcmp(a, "-p"))
+			usage(commit_tree_usage);
+
+		if (get_sha1(b, sha1))
+			die("Not a valid object name %s", b);
+		check_valid(sha1, OBJ_COMMIT);
+		new_parent(lookup_commit(sha1), &parents);
+	}
+
+	if (strbuf_read(&buffer, 0, 0) < 0)
+		die("git-commit-tree: read returned %s", strerror(errno));
+
+	if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1)) {
 		printf("%s\n", sha1_to_hex(commit_sha1));
 		return 0;
 	}
diff --git a/builtin.h b/builtin.h
index 2b01fea..05ee56f 100644
--- a/builtin.h
+++ b/builtin.h
@@ -3,6 +3,8 @@
 
 #include "git-compat-util.h"
 #include "strbuf.h"
+#include "cache.h"
+#include "commit.h"
 
 extern const char git_version_string[];
 extern const char git_usage_string[];
@@ -14,6 +16,8 @@ extern void prune_packed_objects(int);
 extern int read_line_with_nul(char *buf, int size, FILE *file);
 extern int fmt_merge_msg(int merge_summary, struct strbuf *in,
 	struct strbuf *out);
+extern int commit_tree(const char *msg, unsigned char *tree,
+		struct commit_list *parents, unsigned char *ret);
 
 extern int cmd_add(int argc, const char **argv, const char *prefix);
 extern int cmd_annotate(int argc, const char **argv, const char *prefix);
-- 
1.5.6.1

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH 14/14] Build in merge
  2008-07-01  2:13                                     ` Miklos Vajna
  2008-07-01  2:22                                       ` [PATCH 13/14] git-commit-tree: make it usable from other builtins Miklos Vajna
@ 2008-07-01  2:22                                       ` Miklos Vajna
  2008-07-01  2:37                                         ` [PATCH 00/14] " Miklos Vajna
  1 sibling, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-07-01  2:22 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---

Interdiff since the previous version:

        git diff fab9109..b86e8ad

Individual commits:

        git log b86e8ad

 Makefile                                      |    2 +-
 builtin-merge.c                               | 1148 +++++++++++++++++++++++++
 builtin.h                                     |    1 +
 git-merge.sh => contrib/examples/git-merge.sh |    0 
 git.c                                         |    1 +
 t/t7602-merge-octopus-many.sh                 |    2 +-
 6 files changed, 1152 insertions(+), 2 deletions(-)
 create mode 100644 builtin-merge.c
 rename git-merge.sh => contrib/examples/git-merge.sh (100%)

diff --git a/Makefile b/Makefile
index bf77292..fbc53e9 100644
--- a/Makefile
+++ b/Makefile
@@ -240,7 +240,6 @@ SCRIPT_SH += git-lost-found.sh
 SCRIPT_SH += git-merge-octopus.sh
 SCRIPT_SH += git-merge-one-file.sh
 SCRIPT_SH += git-merge-resolve.sh
-SCRIPT_SH += git-merge.sh
 SCRIPT_SH += git-merge-stupid.sh
 SCRIPT_SH += git-mergetool.sh
 SCRIPT_SH += git-parse-remote.sh
@@ -515,6 +514,7 @@ BUILTIN_OBJS += builtin-ls-remote.o
 BUILTIN_OBJS += builtin-ls-tree.o
 BUILTIN_OBJS += builtin-mailinfo.o
 BUILTIN_OBJS += builtin-mailsplit.o
+BUILTIN_OBJS += builtin-merge.o
 BUILTIN_OBJS += builtin-merge-base.o
 BUILTIN_OBJS += builtin-merge-file.o
 BUILTIN_OBJS += builtin-merge-ours.o
diff --git a/builtin-merge.c b/builtin-merge.c
new file mode 100644
index 0000000..bb8d985
--- /dev/null
+++ b/builtin-merge.c
@@ -0,0 +1,1148 @@
+/*
+ * Builtin "git merge"
+ *
+ * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
+ *
+ * Based on git-merge.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "parse-options.h"
+#include "builtin.h"
+#include "run-command.h"
+#include "path-list.h"
+#include "diff.h"
+#include "refs.h"
+#include "commit.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
+#include "dir.h"
+#include "utf8.h"
+#include "log-tree.h"
+#include "color.h"
+
+enum strategy {
+	DEFAULT_TWOHEAD = 1,
+	DEFAULT_OCTOPUS = 2,
+	NO_FAST_FORWARD = 4,
+	NO_TRIVIAL = 8
+};
+
+static const char * const builtin_merge_usage[] = {
+	"git-merge [options] <remote>...",
+	"git-merge [options] <msg> HEAD <remote>",
+	NULL
+};
+
+static int show_diffstat = 1, option_log, squash;
+static int option_commit = 1, allow_fast_forward = 1;
+static int allow_trivial = 1, have_message;
+static struct strbuf merge_msg;
+static struct commit_list *remoteheads;
+static unsigned char head[20], stash[20];
+static struct path_list use_strategies;
+static const char *branch;
+
+static struct path_list_item strategy_items[] = {
+	{ "recur",      (void *)NO_TRIVIAL },
+	{ "recursive",  (void *)(DEFAULT_TWOHEAD | NO_TRIVIAL) },
+	{ "octopus",    (void *)DEFAULT_OCTOPUS },
+	{ "resolve",    (void *)0 },
+	{ "stupid",     (void *)0 },
+	{ "ours",       (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
+	{ "subtree",    (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
+};
+static struct path_list strategies = { strategy_items,
+	ARRAY_SIZE(strategy_items), 0, 0 };
+
+static const char *pull_twohead, *pull_octopus;
+
+static int option_parse_message(const struct option *opt,
+	const char *arg, int unset)
+{
+	struct strbuf *buf = opt->value;
+
+	if (unset)
+		strbuf_setlen(buf, 0);
+	else {
+		strbuf_addf(buf, "%s\n\n", arg);
+		have_message = 1;
+	}
+	return 0;
+}
+
+static struct path_list_item *unsorted_path_list_lookup(const char *path,
+	struct path_list *list)
+{
+	int i;
+
+	if (!path)
+		return NULL;
+
+	for (i = 0; i < list->nr; i++)
+		if (!strcmp(path, list->items[i].path))
+			return &list->items[i];
+	return NULL;
+}
+
+static inline void path_list_append_strategy(struct path_list_item *item)
+{
+	path_list_append(item->path, &use_strategies)->util = item->util;
+}
+
+static int option_parse_strategy(const struct option *opt,
+	const char *arg, int unset)
+{
+	int i;
+	struct path_list_item *item = unsorted_path_list_lookup(arg, &strategies);
+
+	if (unset)
+		return 0;
+
+	if (item)
+		path_list_append_strategy(item);
+	else {
+		struct strbuf err;
+		strbuf_init(&err, 0);
+		for (i = 0; i < strategies.nr; i++)
+			strbuf_addf(&err, " %s", strategies.items[i].path);
+		fprintf(stderr, "Could not find merge strategy '%s'.\n", arg);
+		fprintf(stderr, "Available strategies are:%s.\n", err.buf);
+		exit(1);
+	}
+	return 0;
+}
+
+static int option_parse_n(const struct option *opt,
+		const char *arg, int unset)
+{
+	show_diffstat = unset;
+	return 0;
+}
+
+static struct option builtin_merge_options[] = {
+	{ OPTION_CALLBACK, 'n', NULL, NULL, NULL,
+		"do not show a diffstat at the end of the merge",
+		PARSE_OPT_NOARG, option_parse_n },
+	OPT_BOOLEAN(0, "stat", &show_diffstat,
+		"show a diffstat at the end of the merge"),
+	OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
+	OPT_BOOLEAN(0, "log", &option_log,
+		"add list of one-line log to merge commit message"),
+	OPT_BOOLEAN(0, "squash", &squash,
+		"create a single commit instead of doing a merge"),
+	OPT_BOOLEAN(0, "commit", &option_commit,
+		"perform a commit if the merge succeeds (default)"),
+	OPT_BOOLEAN(0, "ff", &allow_fast_forward,
+		"allow fast forward (default)"),
+	OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
+		"merge strategy to use", option_parse_strategy),
+	OPT_CALLBACK('m', "message", &merge_msg, "message",
+		"message to be used for the merge commit (if any)",
+		option_parse_message),
+	OPT_END()
+};
+
+/* Cleans up metadata that is uninteresting after a succeeded merge. */
+static void drop_save(void)
+{
+	unlink(git_path("MERGE_HEAD"));
+	unlink(git_path("MERGE_MSG"));
+}
+
+static void save_state(void)
+{
+	int len;
+	struct child_process cp;
+	struct strbuf buffer = STRBUF_INIT;
+	const char *argv[] = {"stash", "create", NULL};
+
+	memset(&cp, 0, sizeof(cp));
+	cp.argv = argv;
+	cp.out = -1;
+	cp.git_cmd = 1;
+
+	if (start_command(&cp))
+		die("could not run stash.");
+	len = strbuf_read(&buffer, cp.out, 1024);
+	close(cp.out);
+
+	if (finish_command(&cp) || len < 0)
+		die("stash failed");
+	else if (!len)
+		return;
+	strbuf_setlen(&buffer, buffer.len-1);
+	if (get_sha1(buffer.buf, stash))
+		die("not a valid object: %s", buffer.buf);
+}
+
+static void reset_hard(unsigned const char *sha1, int verbose)
+{
+	int i = 0;
+	const char *args[6];
+
+	args[i++] = "read-tree";
+	if (verbose)
+		args[i++] = "-v";
+	args[i++] = "--reset";
+	args[i++] = "-u";
+	args[i++] = sha1_to_hex(sha1);
+	args[i] = NULL;
+
+	if (run_command_v_opt(args, RUN_GIT_CMD))
+		die("read-tree failed");
+}
+
+static void restore_state(void)
+{
+	struct strbuf sb;
+	const char *args[] = { "stash", "apply", NULL, NULL };
+
+	if (is_null_sha1(stash))
+		return;
+
+	reset_hard(head, 1);
+
+	strbuf_init(&sb, 0);
+	args[2] = sha1_to_hex(stash);
+
+	/*
+	 * It is OK to ignore error here, for example when there was
+	 * nothing to restore.
+	 */
+	run_command_v_opt(args, RUN_GIT_CMD);
+
+	strbuf_release(&sb);
+	refresh_cache(REFRESH_QUIET);
+}
+
+/* This is called when no merge was necessary. */
+static void finish_up_to_date(const char *msg)
+{
+	printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
+	drop_save();
+}
+
+static void squash_message(void)
+{
+	struct rev_info rev;
+	struct commit *commit;
+	struct strbuf out;
+	struct commit_list *j;
+	int fd;
+
+	printf("Squash commit -- not updating HEAD\n");
+	fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+	if (fd < 0)
+		die("Could not write to %s", git_path("SQUASH_MSG"));
+
+	init_revisions(&rev, NULL);
+	rev.ignore_merges = 1;
+	rev.commit_format = CMIT_FMT_MEDIUM;
+
+	commit = lookup_commit(head);
+	commit->object.flags |= UNINTERESTING;
+	add_pending_object(&rev, &commit->object, NULL);
+
+	for (j = remoteheads; j; j = j->next)
+		add_pending_object(&rev, &j->item->object, NULL);
+
+	setup_revisions(0, NULL, &rev, NULL);
+	if (prepare_revision_walk(&rev))
+		die("revision walk setup failed");
+
+	strbuf_init(&out, 0);
+	strbuf_addstr(&out, "Squashed commit of the following:\n");
+	while ((commit = get_revision(&rev)) != NULL) {
+		strbuf_addch(&out, '\n');
+		strbuf_addf(&out, "commit %s\n",
+			sha1_to_hex(commit->object.sha1));
+		pretty_print_commit(rev.commit_format, commit, &out, rev.abbrev,
+			NULL, NULL, rev.date_mode, 0);
+	}
+	write(fd, out.buf, out.len);
+	close(fd);
+	strbuf_release(&out);
+}
+
+static int run_hook(const char *name)
+{
+	struct child_process hook;
+	const char *argv[3], *env[2];
+	char index[PATH_MAX];
+
+	argv[0] = git_path("hooks/%s", name);
+	if (access(argv[0], X_OK) < 0)
+		return 0;
+
+	snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", get_index_file());
+	env[0] = index;
+	env[1] = NULL;
+
+	if (squash)
+		argv[1] = "1";
+	else
+		argv[1] = "0";
+	argv[2] = NULL;
+
+	memset(&hook, 0, sizeof(hook));
+	hook.argv = argv;
+	hook.no_stdin = 1;
+	hook.stdout_to_stderr = 1;
+	hook.env = env;
+
+	return run_command(&hook);
+}
+
+static void finish(const unsigned char *new_head, const char *msg)
+{
+	struct strbuf reflog_message;
+
+	strbuf_init(&reflog_message, 0);
+	if (!msg)
+		strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
+	else {
+		printf("%s\n", msg);
+		strbuf_addf(&reflog_message, "%s: %s",
+			getenv("GIT_REFLOG_ACTION"), msg);
+	}
+	if (squash) {
+		squash_message();
+	} else {
+		if (!merge_msg.len)
+			printf("No merge message -- not updating HEAD\n");
+		else {
+			const char *argv_gc_auto[] = { "gc", "--auto", NULL };
+			update_ref(reflog_message.buf, "HEAD",
+				new_head, head, 0,
+				DIE_ON_ERR);
+			/*
+			 * We ignore errors in 'gc --auto', since the
+			 * user should see them.
+			 */
+			run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+		}
+	}
+	if (new_head && show_diffstat) {
+		struct diff_options opts;
+		diff_setup(&opts);
+		opts.output_format |=
+			DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+		opts.detect_rename = DIFF_DETECT_RENAME;
+		if (diff_use_color_default > 0)
+			DIFF_OPT_SET(&opts, COLOR_DIFF);
+		if (diff_setup_done(&opts) < 0)
+			die("diff_setup_done failed");
+		diff_tree_sha1(head, new_head, "", &opts);
+		diffcore_std(&opts);
+		diff_flush(&opts);
+	}
+
+	/* Run a post-merge hook */
+	run_hook("post-merge");
+
+	strbuf_release(&reflog_message);
+}
+
+/* Get the name for the merge commit's message. */
+static void merge_name(const char *remote, struct strbuf *msg)
+{
+	struct object *remote_head;
+	unsigned char branch_head[20], buf_sha[20];
+	struct strbuf buf;
+	const char *ptr;
+	int len = 0;
+
+	memset(branch_head, 0, sizeof(branch_head));
+	remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+	if (!remote_head)
+		die("'%s' does not point to a commit", remote);
+
+	strbuf_init(&buf, 0);
+	strbuf_addstr(&buf, "refs/heads/");
+	strbuf_addstr(&buf, remote);
+	resolve_ref(buf.buf, branch_head, 0, 0);
+
+	if (!hashcmp(remote_head->sha1, branch_head)) {
+		strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
+			sha1_to_hex(branch_head), remote);
+		return;
+	}
+	/* See if remote matches <name>~<number>, or <name>^ */
+	ptr = strrchr(remote, '^');
+	if (ptr && ptr[1] == '\0') {
+		for (len = 0, ptr = remote + strlen(remote);
+				remote < ptr && ptr[-1] == '^';
+				ptr--)
+			len++;
+	}
+	else {
+		ptr = strrchr(remote, '~');
+		if (ptr && ptr[1] != '0' && isdigit(ptr[1])) {
+			len = ptr-remote;
+			ptr++;
+			for (ptr++; *ptr; ptr++)
+				if (!isdigit(*ptr)) {
+					len = 0;
+					break;
+				}
+		}
+	}
+	if (len) {
+		struct strbuf truname = STRBUF_INIT;
+		strbuf_addstr(&truname, "refs/heads/");
+		strbuf_addstr(&truname, remote);
+		strbuf_setlen(&truname, len+11);
+		if (resolve_ref(truname.buf, buf_sha, 0, 0)) {
+			strbuf_addf(msg,
+				"%s\t\tbranch '%s' (early part) of .\n",
+				sha1_to_hex(remote_head->sha1), truname.buf);
+			return;
+		}
+	}
+
+	if (!strcmp(remote, "FETCH_HEAD") &&
+			!access(git_path("FETCH_HEAD"), R_OK)) {
+		FILE *fp;
+		struct strbuf line;
+		char *ptr;
+
+		strbuf_init(&line, 0);
+		fp = fopen(git_path("FETCH_HEAD"), "r");
+		if (!fp)
+			die("could not open %s for reading: %s",
+				git_path("FETCH_HEAD"), strerror(errno));
+		strbuf_getline(&line, fp, '\n');
+		fclose(fp);
+		ptr = strstr(line.buf, "\tnot-for-merge\t");
+		if (ptr)
+			strbuf_remove(&line, ptr-line.buf+1, 13);
+		strbuf_addbuf(msg, &line);
+		strbuf_release(&line);
+		return;
+	}
+	strbuf_addf(msg, "%s\t\tcommit '%s'\n",
+		sha1_to_hex(remote_head->sha1), remote);
+}
+
+int git_merge_config(const char *k, const char *v, void *cb)
+{
+	if (branch && !prefixcmp(k, "branch.") &&
+		!prefixcmp(k + 7, branch) &&
+		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+		const char **argv;
+		int argc;
+		char *buf;
+
+		buf = xstrdup(v);
+		argc = split_cmdline(buf, &argv);
+		argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+		memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+		argc++;
+		parse_options(argc, argv, builtin_merge_options,
+			      builtin_merge_usage, 0);
+		free(buf);
+	}
+
+	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+		show_diffstat = git_config_bool(k, v);
+	else if (!strcmp(k, "pull.twohead"))
+		return git_config_string(&pull_twohead, k, v);
+	else if (!strcmp(k, "pull.octopus"))
+		return git_config_string(&pull_octopus, k, v);
+	return git_diff_ui_config(k, v, cb);
+}
+
+static int read_tree_trivial(unsigned char *common, unsigned char *head,
+	unsigned char *one)
+{
+	int i, nr_trees = 0;
+	struct tree *trees[MAX_UNPACK_TREES];
+	struct tree_desc t[MAX_UNPACK_TREES];
+	struct unpack_trees_options opts;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.head_idx = 2;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.verbose_update = 1;
+	opts.trivial_merges_only = 1;
+	opts.merge = 1;
+	trees[nr_trees] = parse_tree_indirect(common);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(head);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(one);
+	if (!trees[nr_trees++])
+		return -1;
+	opts.fn = threeway_merge;
+	cache_tree_free(&active_cache_tree);
+	opts.head_idx = 2;
+	for (i = 0; i < nr_trees; i++) {
+		parse_tree(trees[i]);
+		init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+	}
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+	return 0;
+}
+
+static void write_tree_trivial(unsigned char *sha1)
+{
+	if (write_cache_as_tree(sha1, 0, NULL))
+		die("git write-tree failed to write a tree");
+}
+
+static int try_merge_strategy(char *strategy, struct commit_list *common,
+	const char *head_arg)
+{
+	const char **args;
+	int i = 0, ret;
+	struct commit_list *j;
+	struct strbuf buf;
+
+	args = xmalloc((4 + commit_list_count(common) +
+			commit_list_count(remoteheads)) * sizeof(char *));
+	strbuf_init(&buf, 0);
+	strbuf_addf(&buf, "merge-%s", strategy);
+	args[i++] = buf.buf;
+	for (j = common; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i++] = "--";
+	args[i++] = head_arg;
+	for (j = remoteheads; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i] = NULL;
+	ret = run_command_v_opt(args, RUN_GIT_CMD);
+	strbuf_release(&buf);
+	i = 1;
+	for (j = common; j; j = j->next)
+		free((void *)args[i++]);
+	i += 2;
+	for (j = remoteheads; j; j = j->next)
+		free((void *)args[i++]);
+	free(args);
+	return -ret;
+}
+
+static void count_diff_files(struct diff_queue_struct *q,
+		struct diff_options *opt, void *data)
+{
+	int *count = data;
+
+	(*count) += q->nr;
+}
+
+static int count_unmerged_entries(void)
+{
+	const struct index_state *state = &the_index;
+	int i, ret = 0;
+
+	for (i = 0; i < state->cache_nr; i++)
+		if (ce_stage(state->cache[i]))
+			ret++;
+
+	return ret;
+}
+
+static int checkout_fast_forward(unsigned char *head, unsigned char *remote)
+{
+	struct tree *trees[MAX_UNPACK_TREES];
+	struct unpack_trees_options opts;
+	struct tree_desc t[MAX_UNPACK_TREES];
+	int i, fd, nr_trees = 0;
+	struct dir_struct dir;
+	struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+	if (read_cache_unmerged())
+		die("you need to resolve your current index first");
+
+	fd = hold_locked_index(lock_file, 1);
+
+	memset(&trees, 0, sizeof(trees));
+	memset(&opts, 0, sizeof(opts));
+	memset(&t, 0, sizeof(t));
+	dir.show_ignored = 1;
+	dir.exclude_per_dir = ".gitignore";
+	opts.dir = &dir;
+
+	opts.head_idx = 1;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.verbose_update = 1;
+	opts.merge = 1;
+	opts.fn = twoway_merge;
+
+	trees[nr_trees] = parse_tree_indirect(head);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(remote);
+	if (!trees[nr_trees++])
+		return -1;
+	for (i = 0; i < nr_trees; i++) {
+		parse_tree(trees[i]);
+		init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+	}
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+	if (write_cache(fd, active_cache, active_nr) ||
+		commit_locked_index(lock_file))
+		die("unable to write new index file");
+	return 0;
+}
+
+static void split_merge_strategies(const char *string, struct path_list *list)
+{
+	char *p, *q, *buf;
+
+	if (!string)
+		return;
+
+	list->strdup_paths = 1;
+	buf = xstrdup(string);
+	q = buf;
+	for (;;) {
+		p = strchr(q, ' ');
+		if (!p) {
+			path_list_append(q, list);
+			free(buf);
+			return;
+		} else {
+			*p = '\0';
+			path_list_append(q, list);
+			q = ++p;
+		}
+	}
+}
+
+static void add_strategies(const char *string, enum strategy strategy)
+{
+	struct path_list list;
+	int i;
+
+	memset(&list, 0, sizeof(list));
+	split_merge_strategies(string, &list);
+	if (list.nr) {
+		for (i = 0; i < list.nr; i++) {
+			struct path_list_item *item;
+
+			item = unsorted_path_list_lookup(list.items[i].path,
+				&strategies);
+			if (item)
+				path_list_append_strategy(item);
+		}
+		return;
+	}
+	for (i = 0; i < strategies.nr; i++)
+		if ((enum strategy)strategies.items[i].util & strategy)
+			path_list_append_strategy(&strategies.items[i]);
+}
+
+static int merge_trivial(void)
+{
+	unsigned char result_tree[20], result_commit[20];
+	struct commit_list parent;
+
+	write_tree_trivial(result_tree);
+	printf("Wonderful.\n");
+	parent.item = remoteheads->item;
+	parent.next = NULL;
+	commit_tree(merge_msg.buf, result_tree, &parent, result_commit);
+	finish(result_commit, "In-index merge");
+	drop_save();
+	return 0;
+}
+
+static int finish_automerge(struct commit_list *common,
+	unsigned char *result_tree,
+	struct path_list_item *wt_strategy)
+{
+	struct commit_list *parents = NULL, *j;
+	struct strbuf buf = STRBUF_INIT;
+	unsigned char result_commit[20];
+
+	free_commit_list(common);
+	if (allow_fast_forward) {
+		parents = remoteheads;
+		commit_list_insert(lookup_commit(head), &parents);
+		parents = reduce_heads(parents);
+	} else {
+		struct commit_list **pptr = &parents;
+
+		pptr = &commit_list_insert(lookup_commit(head),
+				pptr)->next;
+		for (j = remoteheads; j; j = j->next)
+			pptr = &commit_list_insert(j->item, pptr)->next;
+	}
+	free_commit_list(remoteheads);
+	strbuf_addch(&merge_msg, '\n');
+	commit_tree(merge_msg.buf, result_tree, parents, result_commit);
+	strbuf_addf(&buf, "Merge made by %s.", wt_strategy->path);
+	finish(result_commit, buf.buf);
+	strbuf_release(&buf);
+	drop_save();
+	return 0;
+}
+
+static int suggest_conflicts(void)
+{
+	FILE *fp;
+	int pos;
+
+	fp = fopen(git_path("MERGE_MSG"), "a");
+	if (!fp)
+		die("Could open %s for writing", git_path("MERGE_MSG"));
+	fprintf(fp, "\nConflicts:\n");
+	for (pos = 0; pos < active_nr; pos++) {
+		struct cache_entry *ce = active_cache[pos];
+
+		if (ce_stage(ce)) {
+			fprintf(fp, "\t%s\n", ce->name);
+			while (pos + 1 < active_nr &&
+					!strcmp(ce->name,
+						active_cache[pos + 1]->name))
+				pos++;
+		}
+	}
+	fclose(fp);
+	rerere();
+	printf("Automatic merge failed; "
+			"fix conflicts and then commit the result.\n");
+	return 1;
+}
+
+static inline unsigned nth_strategy_flags(struct path_list *s, int nth)
+{
+	return (unsigned) s->items[nth].util;
+}
+
+static struct commit *is_old_style_invocation(int argc, const char **argv)
+{
+	struct commit *second_token = NULL;
+	if (argc > 1) {
+		unsigned char second_sha1[20];
+
+		if (get_sha1(argv[1], second_sha1))
+			return NULL;
+		second_token = lookup_commit_reference_gently(second_sha1, 0);
+		if (!second_token)
+			die("'%s' is not a commit", argv[1]);
+		if (hashcmp(second_token->object.sha1, head))
+			return NULL;
+	}
+	return second_token;
+}
+
+static int evaluate_result(void)
+{
+	int cnt = 0;
+	struct rev_info rev;
+
+	if (read_cache() < 0)
+		die("failed to read the cache");
+
+	/* Check how many files differ. */
+	init_revisions(&rev, "");
+	setup_revisions(0, NULL, &rev, NULL);
+	rev.diffopt.output_format |=
+		DIFF_FORMAT_CALLBACK;
+	rev.diffopt.format_callback = count_diff_files;
+	rev.diffopt.format_callback_data = &cnt;
+	run_diff_files(&rev, 0);
+
+	/*
+	 * Check how many unmerged entries are
+	 * there.
+	 */
+	cnt += count_unmerged_entries();
+
+	return cnt;
+}
+
+int cmd_merge(int argc, const char **argv, const char *prefix)
+{
+	unsigned char result_tree[20];
+	struct strbuf buf;
+	const char *head_arg;
+	int flag, head_invalid = 0, i;
+	int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
+	struct commit_list *common = NULL;
+	struct path_list_item *best_strategy = NULL, *wt_strategy = NULL;
+	struct commit_list **remotes = &remoteheads;
+
+	setup_work_tree();
+	if (unmerged_cache())
+		die("You are in the middle of a conflicted merge.");
+
+	/*
+	 * Check if we are _not_ on a detached HEAD, i.e. if there is a
+	 * current branch.
+	 */
+	branch = resolve_ref("HEAD", head, 0, &flag);
+	if (branch && flag & REF_ISSYMREF) {
+		const char *ptr = skip_prefix(branch, "refs/heads/");
+		if (ptr)
+			branch = ptr;
+	} else
+		head_invalid = 1;
+
+	git_config(git_merge_config, NULL);
+
+	/* for color.ui */
+	if (diff_use_color_default == -1)
+		diff_use_color_default = git_use_color_default;
+
+	argc = parse_options(argc, argv, builtin_merge_options,
+			builtin_merge_usage, 0);
+
+	if (squash) {
+		if (!allow_fast_forward)
+			die("You cannot combine --squash with --no-ff.");
+		option_commit = 0;
+	}
+
+	if (!argc)
+		usage_with_options(builtin_merge_usage,
+			builtin_merge_options);
+
+	/*
+	 * This could be traditional "merge <msg> HEAD <commit>..."  and
+	 * the way we can tell it is to see if the second token is HEAD,
+	 * but some people might have misused the interface and used a
+	 * committish that is the same as HEAD there instead.
+	 * Traditional format never would have "-m" so it is an
+	 * additional safety measure to check for it.
+	 */
+	strbuf_init(&buf, 0);
+
+	if (!have_message && is_old_style_invocation(argc, argv)) {
+		strbuf_addstr(&merge_msg, argv[0]);
+		head_arg = argv[1];
+		argv += 2;
+		argc -= 2;
+	} else if (head_invalid) {
+		struct object *remote_head;
+		/*
+		 * If the merged head is a valid one there is no reason
+		 * to forbid "git merge" into a branch yet to be born.
+		 * We do the same for "git pull".
+		 */
+		if (argc != 1)
+			die("Can merge only exactly one commit into "
+				"empty head");
+		remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+		if (!remote_head)
+			die("%s - not something we can merge", argv[0]);
+		update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
+				DIE_ON_ERR);
+		reset_hard(remote_head->sha1, 0);
+		return 0;
+	} else {
+		struct strbuf msg;
+
+		/* We are invoked directly as the first-class UI. */
+		head_arg = "HEAD";
+
+		/*
+		 * All the rest are the commits being merged;
+		 * prepare the standard merge summary message to
+		 * be appended to the given message.  If remote
+		 * is invalid we will die later in the common
+		 * codepath so we discard the error in this
+		 * loop.
+		 */
+		strbuf_init(&msg, 0);
+		for (i = 0; i < argc; i++)
+			merge_name(argv[i], &msg);
+		fmt_merge_msg(option_log, &msg, &merge_msg);
+		if (merge_msg.len)
+			strbuf_setlen(&merge_msg, merge_msg.len-1);
+	}
+
+	if (head_invalid || !argc)
+		usage_with_options(builtin_merge_usage,
+			builtin_merge_options);
+
+	strbuf_addstr(&buf, "merge");
+	for (i = 0; i < argc; i++)
+		strbuf_addf(&buf, " %s", argv[i]);
+	setenv("GIT_REFLOG_ACTION", buf.buf, 0);
+	strbuf_reset(&buf);
+
+	for (i = 0; i < argc; i++) {
+		struct object *o;
+
+		o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
+		if (!o)
+			die("%s - not something we can merge", argv[i]);
+		remotes = &commit_list_insert(lookup_commit(o->sha1),
+			remotes)->next;
+
+		strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
+		setenv(buf.buf, argv[i], 1);
+		strbuf_reset(&buf);
+	}
+
+	if (!use_strategies.nr) {
+		if (!remoteheads->next)
+			add_strategies(pull_twohead, DEFAULT_TWOHEAD);
+		else
+			add_strategies(pull_octopus, DEFAULT_OCTOPUS);
+	}
+
+	for (i = 0; i < use_strategies.nr; i++) {
+		if (nth_strategy_flags(&use_strategies, i) & NO_FAST_FORWARD)
+			allow_fast_forward = 0;
+		if (nth_strategy_flags(&use_strategies, i) & NO_TRIVIAL)
+			allow_trivial = 0;
+	}
+
+	if (!remoteheads->next)
+		common = get_merge_bases(lookup_commit(head),
+				remoteheads->item, 1);
+	else {
+		struct commit_list *list = remoteheads;
+		commit_list_insert(lookup_commit(head), &list);
+		common = get_octopus_merge_bases(list);
+		free(list);
+	}
+
+	update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
+		DIE_ON_ERR);
+
+	if (!common)
+		; /* No common ancestors found. We need a real merge. */
+	else if (!remoteheads->next && !common->next &&
+			common->item == remoteheads->item) {
+		/*
+		 * If head can reach all the merge then we are up to date.
+		 * but first the most common case of merging one remote.
+		 */
+		finish_up_to_date("Already up-to-date.");
+		return 0;
+	} else if (allow_fast_forward && !remoteheads->next &&
+			!common->next &&
+			!hashcmp(common->item->object.sha1, head)) {
+		/* Again the most common case of merging one remote. */
+		struct strbuf msg;
+		struct object *o;
+		char hex[41];
+
+		strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+
+		printf("Updating %s..%s\n",
+			hex,
+			find_unique_abbrev(remoteheads->item->object.sha1,
+			DEFAULT_ABBREV));
+		refresh_cache(REFRESH_QUIET);
+		strbuf_init(&msg, 0);
+		strbuf_addstr(&msg, "Fast forward");
+		if (have_message)
+			strbuf_addstr(&msg,
+				" (no commit created; -m option ignored)");
+		o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
+			0, NULL, OBJ_COMMIT);
+		if (!o)
+			return 1;
+
+		if (checkout_fast_forward(head, remoteheads->item->object.sha1))
+			return 1;
+
+		finish(o->sha1, msg.buf);
+		drop_save();
+		return 0;
+	} else if (!remoteheads->next && common->next)
+		;
+		/*
+		 * We are not doing octopus and not fast forward.  Need
+		 * a real merge.
+		 */
+	else if (!remoteheads->next && !common->next && option_commit) {
+		/*
+		 * We are not doing octopus, not fast forward, and have
+		 * only one common.
+		 */
+		refresh_cache(REFRESH_QUIET);
+		if (allow_trivial) {
+			/* See if it is really trivial. */
+			git_committer_info(IDENT_ERROR_ON_NO_NAME);
+			printf("Trying really trivial in-index merge...\n");
+			if (!read_tree_trivial(common->item->object.sha1,
+					head, remoteheads->item->object.sha1))
+				return merge_trivial();
+			printf("Nope.\n");
+		}
+	} else {
+		/*
+		 * An octopus.  If we can reach all the remote we are up
+		 * to date.
+		 */
+		int up_to_date = 1;
+		struct commit_list *j;
+
+		for (j = remoteheads; j; j = j->next) {
+			struct commit_list *common_one;
+
+			/*
+			 * Here we *have* to calculate the individual
+			 * merge_bases again, otherwise "git merge HEAD^
+			 * HEAD^^" would be missed.
+			 */
+			common_one = get_merge_bases(lookup_commit(head),
+				j->item, 1);
+			if (hashcmp(common_one->item->object.sha1,
+				j->item->object.sha1)) {
+				up_to_date = 0;
+				break;
+			}
+		}
+		if (up_to_date) {
+			finish_up_to_date("Already up-to-date. Yeeah!");
+			return 0;
+		}
+	}
+
+	/* We are going to make a new commit. */
+	git_committer_info(IDENT_ERROR_ON_NO_NAME);
+
+	/*
+	 * At this point, we need a real merge.  No matter what strategy
+	 * we use, it would operate on the index, possibly affecting the
+	 * working tree, and when resolved cleanly, have the desired
+	 * tree in the index -- this means that the index must be in
+	 * sync with the head commit.  The strategies are responsible
+	 * to ensure this.
+	 */
+	if (use_strategies.nr != 1) {
+		/*
+		 * Stash away the local changes so that we can try more
+		 * than one.
+		 */
+		save_state();
+	} else {
+		memcpy(stash, null_sha1, 20);
+	}
+
+	for (i = 0; i < use_strategies.nr; i++) {
+		int ret;
+		if (i) {
+			printf("Rewinding the tree to pristine...\n");
+			restore_state();
+		}
+		if (use_strategies.nr != 1)
+			printf("Trying merge strategy %s...\n",
+				use_strategies.items[i].path);
+		/*
+		 * Remember which strategy left the state in the working
+		 * tree.
+		 */
+		wt_strategy = &use_strategies.items[i];
+
+		ret = try_merge_strategy(use_strategies.items[i].path,
+			common, head_arg);
+		if (!option_commit && !ret) {
+			merge_was_ok = 1;
+			/*
+			 * This is necessary here just to avoid writing
+			 * the tree, but later we will *not* exit with
+			 * status code 1 because merge_was_ok is set.
+			 */
+			ret = 1;
+		}
+
+		if (ret) {
+			/*
+			 * The backend exits with 1 when conflicts are
+			 * left to be resolved, with 2 when it does not
+			 * handle the given merge at all.
+			 */
+			if (ret == 1) {
+				int cnt = evaluate_result();
+
+				if (best_cnt <= 0 || cnt <= best_cnt) {
+					best_strategy =
+						&use_strategies.items[i];
+					best_cnt = cnt;
+				}
+			}
+			if (merge_was_ok)
+				break;
+			else
+				continue;
+		}
+
+		/* Automerge succeeded. */
+		write_tree_trivial(result_tree);
+		automerge_was_ok = 1;
+		break;
+	}
+
+	/*
+	 * If we have a resulting tree, that means the strategy module
+	 * auto resolved the merge cleanly.
+	 */
+	if (automerge_was_ok)
+		return finish_automerge(common, result_tree, wt_strategy);
+
+	/*
+	 * Pick the result from the best strategy and have the user fix
+	 * it up.
+	 */
+	if (!best_strategy) {
+		restore_state();
+		if (use_strategies.nr > 1)
+			fprintf(stderr,
+				"No merge strategy handled the merge.\n");
+		else
+			fprintf(stderr, "Merge with strategy %s failed.\n",
+				use_strategies.items[0].path);
+		return 2;
+	} else if (best_strategy == wt_strategy)
+		; /* We already have its result in the working tree. */
+	else {
+		printf("Rewinding the tree to pristine...\n");
+		restore_state();
+		printf("Using the %s to prepare resolving by hand.\n",
+			best_strategy->path);
+		try_merge_strategy(best_strategy->path, common, head_arg);
+	}
+
+	if (squash)
+		finish(NULL, NULL);
+	else {
+		int fd;
+		struct commit_list *j;
+
+		for (j = remoteheads; j; j = j->next)
+			strbuf_addf(&buf, "%s\n",
+				sha1_to_hex(j->item->object.sha1));
+		fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
+		if (fd < 0)
+			die("Could open %s for writing",
+				git_path("MERGE_HEAD"));
+		if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+			die("Could not write to %s", git_path("MERGE_HEAD"));
+		close(fd);
+		strbuf_addch(&merge_msg, '\n');
+		fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+		if (fd < 0)
+			die("Could open %s for writing", git_path("MERGE_MSG"));
+		if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
+			merge_msg.len)
+			die("Could not write to %s", git_path("MERGE_MSG"));
+		close(fd);
+	}
+
+	if (merge_was_ok) {
+		fprintf(stderr, "Automatic merge went well; "
+			"stopped before committing as requested\n");
+		return 0;
+	} else
+		return suggest_conflicts();
+}
diff --git a/builtin.h b/builtin.h
index 05ee56f..0e605d4 100644
--- a/builtin.h
+++ b/builtin.h
@@ -64,6 +64,7 @@ extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_ls_remote(int argc, const char **argv, const char *prefix);
 extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
 extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
+extern int cmd_merge(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
diff --git a/git-merge.sh b/contrib/examples/git-merge.sh
similarity index 100%
rename from git-merge.sh
rename to contrib/examples/git-merge.sh
diff --git a/git.c b/git.c
index 2fbe96b..770aadd 100644
--- a/git.c
+++ b/git.c
@@ -271,6 +271,7 @@ static void handle_internal_command(int argc, const char **argv)
 		{ "ls-remote", cmd_ls_remote },
 		{ "mailinfo", cmd_mailinfo },
 		{ "mailsplit", cmd_mailsplit },
+		{ "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
 		{ "merge-base", cmd_merge_base, RUN_SETUP },
 		{ "merge-file", cmd_merge_file },
 		{ "merge-ours", cmd_merge_ours, RUN_SETUP },
diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh
index f3a4bb2..fcb8285 100755
--- a/t/t7602-merge-octopus-many.sh
+++ b/t/t7602-merge-octopus-many.sh
@@ -23,7 +23,7 @@ test_expect_success 'setup' '
 	done
 '
 
-test_expect_failure 'merge c1 with c2, c3, c4, ... c29' '
+test_expect_success 'merge c1 with c2, c3, c4, ... c29' '
 	git reset --hard c1 &&
 	i=2 &&
 	refs="" &&
-- 
1.5.6.1

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH 00/14] Build in merge
  2008-07-01  2:22                                       ` [PATCH 14/14] Build in merge Miklos Vajna
@ 2008-07-01  2:37                                         ` Miklos Vajna
  2008-07-01  2:37                                           ` [PATCH 08/14] Add new test to ensure git-merge handles more than 25 refs Miklos Vajna
  0 siblings, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-07-01  2:37 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

Sorry for the resend, but I again squashed an unrelated hunk to the last
patch, so I'm sending the changed patches:

08: Add new test to ensure git-merge handles more than 25 refs.
13: git-commit-tree: make it usable from other builtins
14: Build in merge

I really hope I won't overlook this next time.

The interdiff from the previous version is empty, I just moved that
unrelated hunk from patch 13 to patch 08.

Junio C Hamano (2):
  Introduce get_merge_bases_many()
  Introduce reduce_heads()

Miklos Vajna (12):
  Move split_cmdline() to alias.c
  Move commit_list_count() to commit.c
  Move parse-options's skip_prefix() to git-compat-util.h
  Add new test to ensure git-merge handles pull.twohead and
    pull.octopus
  Move read_cache_unmerged() to read-cache.c
  git-fmt-merge-msg: make it usable from other builtins
  Introduce get_octopus_merge_bases() in commit.c
  Add new test to ensure git-merge handles more than 25 refs.
  Add new test case to ensure git-merge reduces octopus parents when
    possible
  Add new test case to ensure git-merge prepends the custom merge
    message
  git-commit-tree: make it usable from other builtins
  Build in merge

 Makefile                                      |    2 +-
 alias.c                                       |   54 ++
 builtin-commit-tree.c                         |   71 +-
 builtin-fmt-merge-msg.c                       |  155 ++--
 builtin-merge-recursive.c                     |    8 -
 builtin-merge.c                               | 1148 +++++++++++++++++++++++++
 builtin-read-tree.c                           |   24 -
 builtin-remote.c                              |   39 +-
 builtin.h                                     |    8 +
 cache.h                                       |    3 +
 commit.c                                      |  136 +++-
 commit.h                                      |    4 +
 git-merge.sh => contrib/examples/git-merge.sh |    0 
 git-compat-util.h                             |    6 +
 git.c                                         |   54 +--
 parse-options.c                               |    6 -
 read-cache.c                                  |   31 +
 t/t7601-merge-pull-config.sh                  |  129 +++
 t/t7602-merge-octopus-many.sh                 |   52 ++
 t/t7603-merge-reduce-heads.sh                 |   63 ++
 t/t7604-merge-custom-message.sh               |   37 +
 21 files changed, 1811 insertions(+), 219 deletions(-)
 create mode 100644 builtin-merge.c
 rename git-merge.sh => contrib/examples/git-merge.sh (100%)
 create mode 100755 t/t7601-merge-pull-config.sh
 create mode 100755 t/t7602-merge-octopus-many.sh
 create mode 100755 t/t7603-merge-reduce-heads.sh
 create mode 100755 t/t7604-merge-custom-message.sh

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH 08/14] Add new test to ensure git-merge handles more than 25 refs.
  2008-07-01  2:37                                         ` [PATCH 00/14] " Miklos Vajna
@ 2008-07-01  2:37                                           ` Miklos Vajna
  2008-07-01  2:37                                             ` [PATCH 13/14] git-commit-tree: make it usable from other builtins Miklos Vajna
  0 siblings, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-07-01  2:37 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

The old shell version handled only 25 refs but we no longer have this
limitation. Add a test to make sure this limitation will not be
introduced again in the future.

Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---
 t/t7602-merge-octopus-many.sh |   52 +++++++++++++++++++++++++++++++++++++++++
 1 files changed, 52 insertions(+), 0 deletions(-)
 create mode 100755 t/t7602-merge-octopus-many.sh

diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh
new file mode 100755
index 0000000..fcb8285
--- /dev/null
+++ b/t/t7602-merge-octopus-many.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+test_description='git-merge
+
+Testing octopus merge with more than 25 refs.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	echo c0 > c0.c &&
+	git add c0.c &&
+	git commit -m c0 &&
+	git tag c0 &&
+	i=1 &&
+	while test $i -le 30
+	do
+		git reset --hard c0 &&
+		echo c$i > c$i.c &&
+		git add c$i.c &&
+		git commit -m c$i &&
+		git tag c$i &&
+		i=`expr $i + 1` || return 1
+	done
+'
+
+test_expect_success 'merge c1 with c2, c3, c4, ... c29' '
+	git reset --hard c1 &&
+	i=2 &&
+	refs="" &&
+	while test $i -le 30
+	do
+		refs="$refs c$i"
+		i=`expr $i + 1`
+	done
+	git merge $refs &&
+	test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
+	i=1 &&
+	while test $i -le 30
+	do
+		test "$(git rev-parse c$i)" = "$(git rev-parse HEAD^$i)" &&
+		i=`expr $i + 1` || return 1
+	done &&
+	git diff --exit-code &&
+	i=1 &&
+	while test $i -le 30
+	do
+		test -f c$i.c &&
+		i=`expr $i + 1` || return 1
+	done
+'
+
+test_done
-- 
1.5.6.1

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH 13/14] git-commit-tree: make it usable from other builtins
  2008-07-01  2:37                                           ` [PATCH 08/14] Add new test to ensure git-merge handles more than 25 refs Miklos Vajna
@ 2008-07-01  2:37                                             ` Miklos Vajna
  2008-07-01  2:37                                               ` [PATCH 14/14] Build in merge Miklos Vajna
  0 siblings, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-07-01  2:37 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

Move all functionality (except option parsing) from cmd_commit_tree() to
commit_tree(), so that other builtins can use it without a child
process.

Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---
 builtin-commit-tree.c |   71 ++++++++++++++++++++++++++++--------------------
 builtin.h             |    4 +++
 2 files changed, 45 insertions(+), 30 deletions(-)

diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c
index 3881f6c..7a9a309 100644
--- a/builtin-commit-tree.c
+++ b/builtin-commit-tree.c
@@ -45,41 +45,19 @@ static const char commit_utf8_warn[] =
 "You may want to amend it after fixing the message, or set the config\n"
 "variable i18n.commitencoding to the encoding your project uses.\n";
 
-int cmd_commit_tree(int argc, const char **argv, const char *prefix)
+int commit_tree(const char *msg, unsigned char *tree,
+		struct commit_list *parents, unsigned char *ret)
 {
-	int i;
-	struct commit_list *parents = NULL;
-	unsigned char tree_sha1[20];
-	unsigned char commit_sha1[20];
-	struct strbuf buffer;
 	int encoding_is_utf8;
+	struct strbuf buffer;
 
-	git_config(git_default_config, NULL);
-
-	if (argc < 2)
-		usage(commit_tree_usage);
-	if (get_sha1(argv[1], tree_sha1))
-		die("Not a valid object name %s", argv[1]);
-
-	check_valid(tree_sha1, OBJ_TREE);
-	for (i = 2; i < argc; i += 2) {
-		unsigned char sha1[20];
-		const char *a, *b;
-		a = argv[i]; b = argv[i+1];
-		if (!b || strcmp(a, "-p"))
-			usage(commit_tree_usage);
-
-		if (get_sha1(b, sha1))
-			die("Not a valid object name %s", b);
-		check_valid(sha1, OBJ_COMMIT);
-		new_parent(lookup_commit(sha1), &parents);
-	}
+	check_valid(tree, OBJ_TREE);
 
 	/* Not having i18n.commitencoding is the same as having utf-8 */
 	encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
 
 	strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */
-	strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree_sha1));
+	strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree));
 
 	/*
 	 * NOTE! This ordering means that the same exact tree merged with a
@@ -102,14 +80,47 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 	strbuf_addch(&buffer, '\n');
 
 	/* And add the comment */
-	if (strbuf_read(&buffer, 0, 0) < 0)
-		die("git-commit-tree: read returned %s", strerror(errno));
+	strbuf_addstr(&buffer, msg);
 
 	/* And check the encoding */
 	if (encoding_is_utf8 && !is_utf8(buffer.buf))
 		fprintf(stderr, commit_utf8_warn);
 
-	if (!write_sha1_file(buffer.buf, buffer.len, commit_type, commit_sha1)) {
+	return write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
+}
+
+int cmd_commit_tree(int argc, const char **argv, const char *prefix)
+{
+	int i;
+	struct commit_list *parents = NULL;
+	unsigned char tree_sha1[20];
+	unsigned char commit_sha1[20];
+	struct strbuf buffer = STRBUF_INIT;
+
+	git_config(git_default_config, NULL);
+
+	if (argc < 2)
+		usage(commit_tree_usage);
+	if (get_sha1(argv[1], tree_sha1))
+		die("Not a valid object name %s", argv[1]);
+
+	for (i = 2; i < argc; i += 2) {
+		unsigned char sha1[20];
+		const char *a, *b;
+		a = argv[i]; b = argv[i+1];
+		if (!b || strcmp(a, "-p"))
+			usage(commit_tree_usage);
+
+		if (get_sha1(b, sha1))
+			die("Not a valid object name %s", b);
+		check_valid(sha1, OBJ_COMMIT);
+		new_parent(lookup_commit(sha1), &parents);
+	}
+
+	if (strbuf_read(&buffer, 0, 0) < 0)
+		die("git-commit-tree: read returned %s", strerror(errno));
+
+	if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1)) {
 		printf("%s\n", sha1_to_hex(commit_sha1));
 		return 0;
 	}
diff --git a/builtin.h b/builtin.h
index 2b01fea..05ee56f 100644
--- a/builtin.h
+++ b/builtin.h
@@ -3,6 +3,8 @@
 
 #include "git-compat-util.h"
 #include "strbuf.h"
+#include "cache.h"
+#include "commit.h"
 
 extern const char git_version_string[];
 extern const char git_usage_string[];
@@ -14,6 +16,8 @@ extern void prune_packed_objects(int);
 extern int read_line_with_nul(char *buf, int size, FILE *file);
 extern int fmt_merge_msg(int merge_summary, struct strbuf *in,
 	struct strbuf *out);
+extern int commit_tree(const char *msg, unsigned char *tree,
+		struct commit_list *parents, unsigned char *ret);
 
 extern int cmd_add(int argc, const char **argv, const char *prefix);
 extern int cmd_annotate(int argc, const char **argv, const char *prefix);
-- 
1.5.6.1

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* [PATCH 14/14] Build in merge
  2008-07-01  2:37                                             ` [PATCH 13/14] git-commit-tree: make it usable from other builtins Miklos Vajna
@ 2008-07-01  2:37                                               ` Miklos Vajna
  2008-07-01  6:23                                                 ` Junio C Hamano
  2008-07-01  7:27                                                 ` [PATCH 14/14] " Junio C Hamano
  0 siblings, 2 replies; 81+ messages in thread
From: Miklos Vajna @ 2008-07-01  2:37 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---
 Makefile                                      |    2 +-
 builtin-merge.c                               | 1148 +++++++++++++++++++++++++
 builtin.h                                     |    1 +
 git-merge.sh => contrib/examples/git-merge.sh |    0 
 git.c                                         |    1 +
 5 files changed, 1151 insertions(+), 1 deletions(-)
 create mode 100644 builtin-merge.c
 rename git-merge.sh => contrib/examples/git-merge.sh (100%)

diff --git a/Makefile b/Makefile
index bf77292..fbc53e9 100644
--- a/Makefile
+++ b/Makefile
@@ -240,7 +240,6 @@ SCRIPT_SH += git-lost-found.sh
 SCRIPT_SH += git-merge-octopus.sh
 SCRIPT_SH += git-merge-one-file.sh
 SCRIPT_SH += git-merge-resolve.sh
-SCRIPT_SH += git-merge.sh
 SCRIPT_SH += git-merge-stupid.sh
 SCRIPT_SH += git-mergetool.sh
 SCRIPT_SH += git-parse-remote.sh
@@ -515,6 +514,7 @@ BUILTIN_OBJS += builtin-ls-remote.o
 BUILTIN_OBJS += builtin-ls-tree.o
 BUILTIN_OBJS += builtin-mailinfo.o
 BUILTIN_OBJS += builtin-mailsplit.o
+BUILTIN_OBJS += builtin-merge.o
 BUILTIN_OBJS += builtin-merge-base.o
 BUILTIN_OBJS += builtin-merge-file.o
 BUILTIN_OBJS += builtin-merge-ours.o
diff --git a/builtin-merge.c b/builtin-merge.c
new file mode 100644
index 0000000..bb8d985
--- /dev/null
+++ b/builtin-merge.c
@@ -0,0 +1,1148 @@
+/*
+ * Builtin "git merge"
+ *
+ * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
+ *
+ * Based on git-merge.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "parse-options.h"
+#include "builtin.h"
+#include "run-command.h"
+#include "path-list.h"
+#include "diff.h"
+#include "refs.h"
+#include "commit.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
+#include "dir.h"
+#include "utf8.h"
+#include "log-tree.h"
+#include "color.h"
+
+enum strategy {
+	DEFAULT_TWOHEAD = 1,
+	DEFAULT_OCTOPUS = 2,
+	NO_FAST_FORWARD = 4,
+	NO_TRIVIAL = 8
+};
+
+static const char * const builtin_merge_usage[] = {
+	"git-merge [options] <remote>...",
+	"git-merge [options] <msg> HEAD <remote>",
+	NULL
+};
+
+static int show_diffstat = 1, option_log, squash;
+static int option_commit = 1, allow_fast_forward = 1;
+static int allow_trivial = 1, have_message;
+static struct strbuf merge_msg;
+static struct commit_list *remoteheads;
+static unsigned char head[20], stash[20];
+static struct path_list use_strategies;
+static const char *branch;
+
+static struct path_list_item strategy_items[] = {
+	{ "recur",      (void *)NO_TRIVIAL },
+	{ "recursive",  (void *)(DEFAULT_TWOHEAD | NO_TRIVIAL) },
+	{ "octopus",    (void *)DEFAULT_OCTOPUS },
+	{ "resolve",    (void *)0 },
+	{ "stupid",     (void *)0 },
+	{ "ours",       (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
+	{ "subtree",    (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
+};
+static struct path_list strategies = { strategy_items,
+	ARRAY_SIZE(strategy_items), 0, 0 };
+
+static const char *pull_twohead, *pull_octopus;
+
+static int option_parse_message(const struct option *opt,
+	const char *arg, int unset)
+{
+	struct strbuf *buf = opt->value;
+
+	if (unset)
+		strbuf_setlen(buf, 0);
+	else {
+		strbuf_addf(buf, "%s\n\n", arg);
+		have_message = 1;
+	}
+	return 0;
+}
+
+static struct path_list_item *unsorted_path_list_lookup(const char *path,
+	struct path_list *list)
+{
+	int i;
+
+	if (!path)
+		return NULL;
+
+	for (i = 0; i < list->nr; i++)
+		if (!strcmp(path, list->items[i].path))
+			return &list->items[i];
+	return NULL;
+}
+
+static inline void path_list_append_strategy(struct path_list_item *item)
+{
+	path_list_append(item->path, &use_strategies)->util = item->util;
+}
+
+static int option_parse_strategy(const struct option *opt,
+	const char *arg, int unset)
+{
+	int i;
+	struct path_list_item *item = unsorted_path_list_lookup(arg, &strategies);
+
+	if (unset)
+		return 0;
+
+	if (item)
+		path_list_append_strategy(item);
+	else {
+		struct strbuf err;
+		strbuf_init(&err, 0);
+		for (i = 0; i < strategies.nr; i++)
+			strbuf_addf(&err, " %s", strategies.items[i].path);
+		fprintf(stderr, "Could not find merge strategy '%s'.\n", arg);
+		fprintf(stderr, "Available strategies are:%s.\n", err.buf);
+		exit(1);
+	}
+	return 0;
+}
+
+static int option_parse_n(const struct option *opt,
+		const char *arg, int unset)
+{
+	show_diffstat = unset;
+	return 0;
+}
+
+static struct option builtin_merge_options[] = {
+	{ OPTION_CALLBACK, 'n', NULL, NULL, NULL,
+		"do not show a diffstat at the end of the merge",
+		PARSE_OPT_NOARG, option_parse_n },
+	OPT_BOOLEAN(0, "stat", &show_diffstat,
+		"show a diffstat at the end of the merge"),
+	OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
+	OPT_BOOLEAN(0, "log", &option_log,
+		"add list of one-line log to merge commit message"),
+	OPT_BOOLEAN(0, "squash", &squash,
+		"create a single commit instead of doing a merge"),
+	OPT_BOOLEAN(0, "commit", &option_commit,
+		"perform a commit if the merge succeeds (default)"),
+	OPT_BOOLEAN(0, "ff", &allow_fast_forward,
+		"allow fast forward (default)"),
+	OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
+		"merge strategy to use", option_parse_strategy),
+	OPT_CALLBACK('m', "message", &merge_msg, "message",
+		"message to be used for the merge commit (if any)",
+		option_parse_message),
+	OPT_END()
+};
+
+/* Cleans up metadata that is uninteresting after a succeeded merge. */
+static void drop_save(void)
+{
+	unlink(git_path("MERGE_HEAD"));
+	unlink(git_path("MERGE_MSG"));
+}
+
+static void save_state(void)
+{
+	int len;
+	struct child_process cp;
+	struct strbuf buffer = STRBUF_INIT;
+	const char *argv[] = {"stash", "create", NULL};
+
+	memset(&cp, 0, sizeof(cp));
+	cp.argv = argv;
+	cp.out = -1;
+	cp.git_cmd = 1;
+
+	if (start_command(&cp))
+		die("could not run stash.");
+	len = strbuf_read(&buffer, cp.out, 1024);
+	close(cp.out);
+
+	if (finish_command(&cp) || len < 0)
+		die("stash failed");
+	else if (!len)
+		return;
+	strbuf_setlen(&buffer, buffer.len-1);
+	if (get_sha1(buffer.buf, stash))
+		die("not a valid object: %s", buffer.buf);
+}
+
+static void reset_hard(unsigned const char *sha1, int verbose)
+{
+	int i = 0;
+	const char *args[6];
+
+	args[i++] = "read-tree";
+	if (verbose)
+		args[i++] = "-v";
+	args[i++] = "--reset";
+	args[i++] = "-u";
+	args[i++] = sha1_to_hex(sha1);
+	args[i] = NULL;
+
+	if (run_command_v_opt(args, RUN_GIT_CMD))
+		die("read-tree failed");
+}
+
+static void restore_state(void)
+{
+	struct strbuf sb;
+	const char *args[] = { "stash", "apply", NULL, NULL };
+
+	if (is_null_sha1(stash))
+		return;
+
+	reset_hard(head, 1);
+
+	strbuf_init(&sb, 0);
+	args[2] = sha1_to_hex(stash);
+
+	/*
+	 * It is OK to ignore error here, for example when there was
+	 * nothing to restore.
+	 */
+	run_command_v_opt(args, RUN_GIT_CMD);
+
+	strbuf_release(&sb);
+	refresh_cache(REFRESH_QUIET);
+}
+
+/* This is called when no merge was necessary. */
+static void finish_up_to_date(const char *msg)
+{
+	printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
+	drop_save();
+}
+
+static void squash_message(void)
+{
+	struct rev_info rev;
+	struct commit *commit;
+	struct strbuf out;
+	struct commit_list *j;
+	int fd;
+
+	printf("Squash commit -- not updating HEAD\n");
+	fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+	if (fd < 0)
+		die("Could not write to %s", git_path("SQUASH_MSG"));
+
+	init_revisions(&rev, NULL);
+	rev.ignore_merges = 1;
+	rev.commit_format = CMIT_FMT_MEDIUM;
+
+	commit = lookup_commit(head);
+	commit->object.flags |= UNINTERESTING;
+	add_pending_object(&rev, &commit->object, NULL);
+
+	for (j = remoteheads; j; j = j->next)
+		add_pending_object(&rev, &j->item->object, NULL);
+
+	setup_revisions(0, NULL, &rev, NULL);
+	if (prepare_revision_walk(&rev))
+		die("revision walk setup failed");
+
+	strbuf_init(&out, 0);
+	strbuf_addstr(&out, "Squashed commit of the following:\n");
+	while ((commit = get_revision(&rev)) != NULL) {
+		strbuf_addch(&out, '\n');
+		strbuf_addf(&out, "commit %s\n",
+			sha1_to_hex(commit->object.sha1));
+		pretty_print_commit(rev.commit_format, commit, &out, rev.abbrev,
+			NULL, NULL, rev.date_mode, 0);
+	}
+	write(fd, out.buf, out.len);
+	close(fd);
+	strbuf_release(&out);
+}
+
+static int run_hook(const char *name)
+{
+	struct child_process hook;
+	const char *argv[3], *env[2];
+	char index[PATH_MAX];
+
+	argv[0] = git_path("hooks/%s", name);
+	if (access(argv[0], X_OK) < 0)
+		return 0;
+
+	snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", get_index_file());
+	env[0] = index;
+	env[1] = NULL;
+
+	if (squash)
+		argv[1] = "1";
+	else
+		argv[1] = "0";
+	argv[2] = NULL;
+
+	memset(&hook, 0, sizeof(hook));
+	hook.argv = argv;
+	hook.no_stdin = 1;
+	hook.stdout_to_stderr = 1;
+	hook.env = env;
+
+	return run_command(&hook);
+}
+
+static void finish(const unsigned char *new_head, const char *msg)
+{
+	struct strbuf reflog_message;
+
+	strbuf_init(&reflog_message, 0);
+	if (!msg)
+		strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
+	else {
+		printf("%s\n", msg);
+		strbuf_addf(&reflog_message, "%s: %s",
+			getenv("GIT_REFLOG_ACTION"), msg);
+	}
+	if (squash) {
+		squash_message();
+	} else {
+		if (!merge_msg.len)
+			printf("No merge message -- not updating HEAD\n");
+		else {
+			const char *argv_gc_auto[] = { "gc", "--auto", NULL };
+			update_ref(reflog_message.buf, "HEAD",
+				new_head, head, 0,
+				DIE_ON_ERR);
+			/*
+			 * We ignore errors in 'gc --auto', since the
+			 * user should see them.
+			 */
+			run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+		}
+	}
+	if (new_head && show_diffstat) {
+		struct diff_options opts;
+		diff_setup(&opts);
+		opts.output_format |=
+			DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+		opts.detect_rename = DIFF_DETECT_RENAME;
+		if (diff_use_color_default > 0)
+			DIFF_OPT_SET(&opts, COLOR_DIFF);
+		if (diff_setup_done(&opts) < 0)
+			die("diff_setup_done failed");
+		diff_tree_sha1(head, new_head, "", &opts);
+		diffcore_std(&opts);
+		diff_flush(&opts);
+	}
+
+	/* Run a post-merge hook */
+	run_hook("post-merge");
+
+	strbuf_release(&reflog_message);
+}
+
+/* Get the name for the merge commit's message. */
+static void merge_name(const char *remote, struct strbuf *msg)
+{
+	struct object *remote_head;
+	unsigned char branch_head[20], buf_sha[20];
+	struct strbuf buf;
+	const char *ptr;
+	int len = 0;
+
+	memset(branch_head, 0, sizeof(branch_head));
+	remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+	if (!remote_head)
+		die("'%s' does not point to a commit", remote);
+
+	strbuf_init(&buf, 0);
+	strbuf_addstr(&buf, "refs/heads/");
+	strbuf_addstr(&buf, remote);
+	resolve_ref(buf.buf, branch_head, 0, 0);
+
+	if (!hashcmp(remote_head->sha1, branch_head)) {
+		strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
+			sha1_to_hex(branch_head), remote);
+		return;
+	}
+	/* See if remote matches <name>~<number>, or <name>^ */
+	ptr = strrchr(remote, '^');
+	if (ptr && ptr[1] == '\0') {
+		for (len = 0, ptr = remote + strlen(remote);
+				remote < ptr && ptr[-1] == '^';
+				ptr--)
+			len++;
+	}
+	else {
+		ptr = strrchr(remote, '~');
+		if (ptr && ptr[1] != '0' && isdigit(ptr[1])) {
+			len = ptr-remote;
+			ptr++;
+			for (ptr++; *ptr; ptr++)
+				if (!isdigit(*ptr)) {
+					len = 0;
+					break;
+				}
+		}
+	}
+	if (len) {
+		struct strbuf truname = STRBUF_INIT;
+		strbuf_addstr(&truname, "refs/heads/");
+		strbuf_addstr(&truname, remote);
+		strbuf_setlen(&truname, len+11);
+		if (resolve_ref(truname.buf, buf_sha, 0, 0)) {
+			strbuf_addf(msg,
+				"%s\t\tbranch '%s' (early part) of .\n",
+				sha1_to_hex(remote_head->sha1), truname.buf);
+			return;
+		}
+	}
+
+	if (!strcmp(remote, "FETCH_HEAD") &&
+			!access(git_path("FETCH_HEAD"), R_OK)) {
+		FILE *fp;
+		struct strbuf line;
+		char *ptr;
+
+		strbuf_init(&line, 0);
+		fp = fopen(git_path("FETCH_HEAD"), "r");
+		if (!fp)
+			die("could not open %s for reading: %s",
+				git_path("FETCH_HEAD"), strerror(errno));
+		strbuf_getline(&line, fp, '\n');
+		fclose(fp);
+		ptr = strstr(line.buf, "\tnot-for-merge\t");
+		if (ptr)
+			strbuf_remove(&line, ptr-line.buf+1, 13);
+		strbuf_addbuf(msg, &line);
+		strbuf_release(&line);
+		return;
+	}
+	strbuf_addf(msg, "%s\t\tcommit '%s'\n",
+		sha1_to_hex(remote_head->sha1), remote);
+}
+
+int git_merge_config(const char *k, const char *v, void *cb)
+{
+	if (branch && !prefixcmp(k, "branch.") &&
+		!prefixcmp(k + 7, branch) &&
+		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+		const char **argv;
+		int argc;
+		char *buf;
+
+		buf = xstrdup(v);
+		argc = split_cmdline(buf, &argv);
+		argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+		memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+		argc++;
+		parse_options(argc, argv, builtin_merge_options,
+			      builtin_merge_usage, 0);
+		free(buf);
+	}
+
+	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+		show_diffstat = git_config_bool(k, v);
+	else if (!strcmp(k, "pull.twohead"))
+		return git_config_string(&pull_twohead, k, v);
+	else if (!strcmp(k, "pull.octopus"))
+		return git_config_string(&pull_octopus, k, v);
+	return git_diff_ui_config(k, v, cb);
+}
+
+static int read_tree_trivial(unsigned char *common, unsigned char *head,
+	unsigned char *one)
+{
+	int i, nr_trees = 0;
+	struct tree *trees[MAX_UNPACK_TREES];
+	struct tree_desc t[MAX_UNPACK_TREES];
+	struct unpack_trees_options opts;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.head_idx = 2;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.verbose_update = 1;
+	opts.trivial_merges_only = 1;
+	opts.merge = 1;
+	trees[nr_trees] = parse_tree_indirect(common);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(head);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(one);
+	if (!trees[nr_trees++])
+		return -1;
+	opts.fn = threeway_merge;
+	cache_tree_free(&active_cache_tree);
+	opts.head_idx = 2;
+	for (i = 0; i < nr_trees; i++) {
+		parse_tree(trees[i]);
+		init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+	}
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+	return 0;
+}
+
+static void write_tree_trivial(unsigned char *sha1)
+{
+	if (write_cache_as_tree(sha1, 0, NULL))
+		die("git write-tree failed to write a tree");
+}
+
+static int try_merge_strategy(char *strategy, struct commit_list *common,
+	const char *head_arg)
+{
+	const char **args;
+	int i = 0, ret;
+	struct commit_list *j;
+	struct strbuf buf;
+
+	args = xmalloc((4 + commit_list_count(common) +
+			commit_list_count(remoteheads)) * sizeof(char *));
+	strbuf_init(&buf, 0);
+	strbuf_addf(&buf, "merge-%s", strategy);
+	args[i++] = buf.buf;
+	for (j = common; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i++] = "--";
+	args[i++] = head_arg;
+	for (j = remoteheads; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i] = NULL;
+	ret = run_command_v_opt(args, RUN_GIT_CMD);
+	strbuf_release(&buf);
+	i = 1;
+	for (j = common; j; j = j->next)
+		free((void *)args[i++]);
+	i += 2;
+	for (j = remoteheads; j; j = j->next)
+		free((void *)args[i++]);
+	free(args);
+	return -ret;
+}
+
+static void count_diff_files(struct diff_queue_struct *q,
+		struct diff_options *opt, void *data)
+{
+	int *count = data;
+
+	(*count) += q->nr;
+}
+
+static int count_unmerged_entries(void)
+{
+	const struct index_state *state = &the_index;
+	int i, ret = 0;
+
+	for (i = 0; i < state->cache_nr; i++)
+		if (ce_stage(state->cache[i]))
+			ret++;
+
+	return ret;
+}
+
+static int checkout_fast_forward(unsigned char *head, unsigned char *remote)
+{
+	struct tree *trees[MAX_UNPACK_TREES];
+	struct unpack_trees_options opts;
+	struct tree_desc t[MAX_UNPACK_TREES];
+	int i, fd, nr_trees = 0;
+	struct dir_struct dir;
+	struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+	if (read_cache_unmerged())
+		die("you need to resolve your current index first");
+
+	fd = hold_locked_index(lock_file, 1);
+
+	memset(&trees, 0, sizeof(trees));
+	memset(&opts, 0, sizeof(opts));
+	memset(&t, 0, sizeof(t));
+	dir.show_ignored = 1;
+	dir.exclude_per_dir = ".gitignore";
+	opts.dir = &dir;
+
+	opts.head_idx = 1;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.verbose_update = 1;
+	opts.merge = 1;
+	opts.fn = twoway_merge;
+
+	trees[nr_trees] = parse_tree_indirect(head);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(remote);
+	if (!trees[nr_trees++])
+		return -1;
+	for (i = 0; i < nr_trees; i++) {
+		parse_tree(trees[i]);
+		init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+	}
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+	if (write_cache(fd, active_cache, active_nr) ||
+		commit_locked_index(lock_file))
+		die("unable to write new index file");
+	return 0;
+}
+
+static void split_merge_strategies(const char *string, struct path_list *list)
+{
+	char *p, *q, *buf;
+
+	if (!string)
+		return;
+
+	list->strdup_paths = 1;
+	buf = xstrdup(string);
+	q = buf;
+	for (;;) {
+		p = strchr(q, ' ');
+		if (!p) {
+			path_list_append(q, list);
+			free(buf);
+			return;
+		} else {
+			*p = '\0';
+			path_list_append(q, list);
+			q = ++p;
+		}
+	}
+}
+
+static void add_strategies(const char *string, enum strategy strategy)
+{
+	struct path_list list;
+	int i;
+
+	memset(&list, 0, sizeof(list));
+	split_merge_strategies(string, &list);
+	if (list.nr) {
+		for (i = 0; i < list.nr; i++) {
+			struct path_list_item *item;
+
+			item = unsorted_path_list_lookup(list.items[i].path,
+				&strategies);
+			if (item)
+				path_list_append_strategy(item);
+		}
+		return;
+	}
+	for (i = 0; i < strategies.nr; i++)
+		if ((enum strategy)strategies.items[i].util & strategy)
+			path_list_append_strategy(&strategies.items[i]);
+}
+
+static int merge_trivial(void)
+{
+	unsigned char result_tree[20], result_commit[20];
+	struct commit_list parent;
+
+	write_tree_trivial(result_tree);
+	printf("Wonderful.\n");
+	parent.item = remoteheads->item;
+	parent.next = NULL;
+	commit_tree(merge_msg.buf, result_tree, &parent, result_commit);
+	finish(result_commit, "In-index merge");
+	drop_save();
+	return 0;
+}
+
+static int finish_automerge(struct commit_list *common,
+	unsigned char *result_tree,
+	struct path_list_item *wt_strategy)
+{
+	struct commit_list *parents = NULL, *j;
+	struct strbuf buf = STRBUF_INIT;
+	unsigned char result_commit[20];
+
+	free_commit_list(common);
+	if (allow_fast_forward) {
+		parents = remoteheads;
+		commit_list_insert(lookup_commit(head), &parents);
+		parents = reduce_heads(parents);
+	} else {
+		struct commit_list **pptr = &parents;
+
+		pptr = &commit_list_insert(lookup_commit(head),
+				pptr)->next;
+		for (j = remoteheads; j; j = j->next)
+			pptr = &commit_list_insert(j->item, pptr)->next;
+	}
+	free_commit_list(remoteheads);
+	strbuf_addch(&merge_msg, '\n');
+	commit_tree(merge_msg.buf, result_tree, parents, result_commit);
+	strbuf_addf(&buf, "Merge made by %s.", wt_strategy->path);
+	finish(result_commit, buf.buf);
+	strbuf_release(&buf);
+	drop_save();
+	return 0;
+}
+
+static int suggest_conflicts(void)
+{
+	FILE *fp;
+	int pos;
+
+	fp = fopen(git_path("MERGE_MSG"), "a");
+	if (!fp)
+		die("Could open %s for writing", git_path("MERGE_MSG"));
+	fprintf(fp, "\nConflicts:\n");
+	for (pos = 0; pos < active_nr; pos++) {
+		struct cache_entry *ce = active_cache[pos];
+
+		if (ce_stage(ce)) {
+			fprintf(fp, "\t%s\n", ce->name);
+			while (pos + 1 < active_nr &&
+					!strcmp(ce->name,
+						active_cache[pos + 1]->name))
+				pos++;
+		}
+	}
+	fclose(fp);
+	rerere();
+	printf("Automatic merge failed; "
+			"fix conflicts and then commit the result.\n");
+	return 1;
+}
+
+static inline unsigned nth_strategy_flags(struct path_list *s, int nth)
+{
+	return (unsigned) s->items[nth].util;
+}
+
+static struct commit *is_old_style_invocation(int argc, const char **argv)
+{
+	struct commit *second_token = NULL;
+	if (argc > 1) {
+		unsigned char second_sha1[20];
+
+		if (get_sha1(argv[1], second_sha1))
+			return NULL;
+		second_token = lookup_commit_reference_gently(second_sha1, 0);
+		if (!second_token)
+			die("'%s' is not a commit", argv[1]);
+		if (hashcmp(second_token->object.sha1, head))
+			return NULL;
+	}
+	return second_token;
+}
+
+static int evaluate_result(void)
+{
+	int cnt = 0;
+	struct rev_info rev;
+
+	if (read_cache() < 0)
+		die("failed to read the cache");
+
+	/* Check how many files differ. */
+	init_revisions(&rev, "");
+	setup_revisions(0, NULL, &rev, NULL);
+	rev.diffopt.output_format |=
+		DIFF_FORMAT_CALLBACK;
+	rev.diffopt.format_callback = count_diff_files;
+	rev.diffopt.format_callback_data = &cnt;
+	run_diff_files(&rev, 0);
+
+	/*
+	 * Check how many unmerged entries are
+	 * there.
+	 */
+	cnt += count_unmerged_entries();
+
+	return cnt;
+}
+
+int cmd_merge(int argc, const char **argv, const char *prefix)
+{
+	unsigned char result_tree[20];
+	struct strbuf buf;
+	const char *head_arg;
+	int flag, head_invalid = 0, i;
+	int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
+	struct commit_list *common = NULL;
+	struct path_list_item *best_strategy = NULL, *wt_strategy = NULL;
+	struct commit_list **remotes = &remoteheads;
+
+	setup_work_tree();
+	if (unmerged_cache())
+		die("You are in the middle of a conflicted merge.");
+
+	/*
+	 * Check if we are _not_ on a detached HEAD, i.e. if there is a
+	 * current branch.
+	 */
+	branch = resolve_ref("HEAD", head, 0, &flag);
+	if (branch && flag & REF_ISSYMREF) {
+		const char *ptr = skip_prefix(branch, "refs/heads/");
+		if (ptr)
+			branch = ptr;
+	} else
+		head_invalid = 1;
+
+	git_config(git_merge_config, NULL);
+
+	/* for color.ui */
+	if (diff_use_color_default == -1)
+		diff_use_color_default = git_use_color_default;
+
+	argc = parse_options(argc, argv, builtin_merge_options,
+			builtin_merge_usage, 0);
+
+	if (squash) {
+		if (!allow_fast_forward)
+			die("You cannot combine --squash with --no-ff.");
+		option_commit = 0;
+	}
+
+	if (!argc)
+		usage_with_options(builtin_merge_usage,
+			builtin_merge_options);
+
+	/*
+	 * This could be traditional "merge <msg> HEAD <commit>..."  and
+	 * the way we can tell it is to see if the second token is HEAD,
+	 * but some people might have misused the interface and used a
+	 * committish that is the same as HEAD there instead.
+	 * Traditional format never would have "-m" so it is an
+	 * additional safety measure to check for it.
+	 */
+	strbuf_init(&buf, 0);
+
+	if (!have_message && is_old_style_invocation(argc, argv)) {
+		strbuf_addstr(&merge_msg, argv[0]);
+		head_arg = argv[1];
+		argv += 2;
+		argc -= 2;
+	} else if (head_invalid) {
+		struct object *remote_head;
+		/*
+		 * If the merged head is a valid one there is no reason
+		 * to forbid "git merge" into a branch yet to be born.
+		 * We do the same for "git pull".
+		 */
+		if (argc != 1)
+			die("Can merge only exactly one commit into "
+				"empty head");
+		remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+		if (!remote_head)
+			die("%s - not something we can merge", argv[0]);
+		update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
+				DIE_ON_ERR);
+		reset_hard(remote_head->sha1, 0);
+		return 0;
+	} else {
+		struct strbuf msg;
+
+		/* We are invoked directly as the first-class UI. */
+		head_arg = "HEAD";
+
+		/*
+		 * All the rest are the commits being merged;
+		 * prepare the standard merge summary message to
+		 * be appended to the given message.  If remote
+		 * is invalid we will die later in the common
+		 * codepath so we discard the error in this
+		 * loop.
+		 */
+		strbuf_init(&msg, 0);
+		for (i = 0; i < argc; i++)
+			merge_name(argv[i], &msg);
+		fmt_merge_msg(option_log, &msg, &merge_msg);
+		if (merge_msg.len)
+			strbuf_setlen(&merge_msg, merge_msg.len-1);
+	}
+
+	if (head_invalid || !argc)
+		usage_with_options(builtin_merge_usage,
+			builtin_merge_options);
+
+	strbuf_addstr(&buf, "merge");
+	for (i = 0; i < argc; i++)
+		strbuf_addf(&buf, " %s", argv[i]);
+	setenv("GIT_REFLOG_ACTION", buf.buf, 0);
+	strbuf_reset(&buf);
+
+	for (i = 0; i < argc; i++) {
+		struct object *o;
+
+		o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
+		if (!o)
+			die("%s - not something we can merge", argv[i]);
+		remotes = &commit_list_insert(lookup_commit(o->sha1),
+			remotes)->next;
+
+		strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
+		setenv(buf.buf, argv[i], 1);
+		strbuf_reset(&buf);
+	}
+
+	if (!use_strategies.nr) {
+		if (!remoteheads->next)
+			add_strategies(pull_twohead, DEFAULT_TWOHEAD);
+		else
+			add_strategies(pull_octopus, DEFAULT_OCTOPUS);
+	}
+
+	for (i = 0; i < use_strategies.nr; i++) {
+		if (nth_strategy_flags(&use_strategies, i) & NO_FAST_FORWARD)
+			allow_fast_forward = 0;
+		if (nth_strategy_flags(&use_strategies, i) & NO_TRIVIAL)
+			allow_trivial = 0;
+	}
+
+	if (!remoteheads->next)
+		common = get_merge_bases(lookup_commit(head),
+				remoteheads->item, 1);
+	else {
+		struct commit_list *list = remoteheads;
+		commit_list_insert(lookup_commit(head), &list);
+		common = get_octopus_merge_bases(list);
+		free(list);
+	}
+
+	update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
+		DIE_ON_ERR);
+
+	if (!common)
+		; /* No common ancestors found. We need a real merge. */
+	else if (!remoteheads->next && !common->next &&
+			common->item == remoteheads->item) {
+		/*
+		 * If head can reach all the merge then we are up to date.
+		 * but first the most common case of merging one remote.
+		 */
+		finish_up_to_date("Already up-to-date.");
+		return 0;
+	} else if (allow_fast_forward && !remoteheads->next &&
+			!common->next &&
+			!hashcmp(common->item->object.sha1, head)) {
+		/* Again the most common case of merging one remote. */
+		struct strbuf msg;
+		struct object *o;
+		char hex[41];
+
+		strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+
+		printf("Updating %s..%s\n",
+			hex,
+			find_unique_abbrev(remoteheads->item->object.sha1,
+			DEFAULT_ABBREV));
+		refresh_cache(REFRESH_QUIET);
+		strbuf_init(&msg, 0);
+		strbuf_addstr(&msg, "Fast forward");
+		if (have_message)
+			strbuf_addstr(&msg,
+				" (no commit created; -m option ignored)");
+		o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
+			0, NULL, OBJ_COMMIT);
+		if (!o)
+			return 1;
+
+		if (checkout_fast_forward(head, remoteheads->item->object.sha1))
+			return 1;
+
+		finish(o->sha1, msg.buf);
+		drop_save();
+		return 0;
+	} else if (!remoteheads->next && common->next)
+		;
+		/*
+		 * We are not doing octopus and not fast forward.  Need
+		 * a real merge.
+		 */
+	else if (!remoteheads->next && !common->next && option_commit) {
+		/*
+		 * We are not doing octopus, not fast forward, and have
+		 * only one common.
+		 */
+		refresh_cache(REFRESH_QUIET);
+		if (allow_trivial) {
+			/* See if it is really trivial. */
+			git_committer_info(IDENT_ERROR_ON_NO_NAME);
+			printf("Trying really trivial in-index merge...\n");
+			if (!read_tree_trivial(common->item->object.sha1,
+					head, remoteheads->item->object.sha1))
+				return merge_trivial();
+			printf("Nope.\n");
+		}
+	} else {
+		/*
+		 * An octopus.  If we can reach all the remote we are up
+		 * to date.
+		 */
+		int up_to_date = 1;
+		struct commit_list *j;
+
+		for (j = remoteheads; j; j = j->next) {
+			struct commit_list *common_one;
+
+			/*
+			 * Here we *have* to calculate the individual
+			 * merge_bases again, otherwise "git merge HEAD^
+			 * HEAD^^" would be missed.
+			 */
+			common_one = get_merge_bases(lookup_commit(head),
+				j->item, 1);
+			if (hashcmp(common_one->item->object.sha1,
+				j->item->object.sha1)) {
+				up_to_date = 0;
+				break;
+			}
+		}
+		if (up_to_date) {
+			finish_up_to_date("Already up-to-date. Yeeah!");
+			return 0;
+		}
+	}
+
+	/* We are going to make a new commit. */
+	git_committer_info(IDENT_ERROR_ON_NO_NAME);
+
+	/*
+	 * At this point, we need a real merge.  No matter what strategy
+	 * we use, it would operate on the index, possibly affecting the
+	 * working tree, and when resolved cleanly, have the desired
+	 * tree in the index -- this means that the index must be in
+	 * sync with the head commit.  The strategies are responsible
+	 * to ensure this.
+	 */
+	if (use_strategies.nr != 1) {
+		/*
+		 * Stash away the local changes so that we can try more
+		 * than one.
+		 */
+		save_state();
+	} else {
+		memcpy(stash, null_sha1, 20);
+	}
+
+	for (i = 0; i < use_strategies.nr; i++) {
+		int ret;
+		if (i) {
+			printf("Rewinding the tree to pristine...\n");
+			restore_state();
+		}
+		if (use_strategies.nr != 1)
+			printf("Trying merge strategy %s...\n",
+				use_strategies.items[i].path);
+		/*
+		 * Remember which strategy left the state in the working
+		 * tree.
+		 */
+		wt_strategy = &use_strategies.items[i];
+
+		ret = try_merge_strategy(use_strategies.items[i].path,
+			common, head_arg);
+		if (!option_commit && !ret) {
+			merge_was_ok = 1;
+			/*
+			 * This is necessary here just to avoid writing
+			 * the tree, but later we will *not* exit with
+			 * status code 1 because merge_was_ok is set.
+			 */
+			ret = 1;
+		}
+
+		if (ret) {
+			/*
+			 * The backend exits with 1 when conflicts are
+			 * left to be resolved, with 2 when it does not
+			 * handle the given merge at all.
+			 */
+			if (ret == 1) {
+				int cnt = evaluate_result();
+
+				if (best_cnt <= 0 || cnt <= best_cnt) {
+					best_strategy =
+						&use_strategies.items[i];
+					best_cnt = cnt;
+				}
+			}
+			if (merge_was_ok)
+				break;
+			else
+				continue;
+		}
+
+		/* Automerge succeeded. */
+		write_tree_trivial(result_tree);
+		automerge_was_ok = 1;
+		break;
+	}
+
+	/*
+	 * If we have a resulting tree, that means the strategy module
+	 * auto resolved the merge cleanly.
+	 */
+	if (automerge_was_ok)
+		return finish_automerge(common, result_tree, wt_strategy);
+
+	/*
+	 * Pick the result from the best strategy and have the user fix
+	 * it up.
+	 */
+	if (!best_strategy) {
+		restore_state();
+		if (use_strategies.nr > 1)
+			fprintf(stderr,
+				"No merge strategy handled the merge.\n");
+		else
+			fprintf(stderr, "Merge with strategy %s failed.\n",
+				use_strategies.items[0].path);
+		return 2;
+	} else if (best_strategy == wt_strategy)
+		; /* We already have its result in the working tree. */
+	else {
+		printf("Rewinding the tree to pristine...\n");
+		restore_state();
+		printf("Using the %s to prepare resolving by hand.\n",
+			best_strategy->path);
+		try_merge_strategy(best_strategy->path, common, head_arg);
+	}
+
+	if (squash)
+		finish(NULL, NULL);
+	else {
+		int fd;
+		struct commit_list *j;
+
+		for (j = remoteheads; j; j = j->next)
+			strbuf_addf(&buf, "%s\n",
+				sha1_to_hex(j->item->object.sha1));
+		fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
+		if (fd < 0)
+			die("Could open %s for writing",
+				git_path("MERGE_HEAD"));
+		if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+			die("Could not write to %s", git_path("MERGE_HEAD"));
+		close(fd);
+		strbuf_addch(&merge_msg, '\n');
+		fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+		if (fd < 0)
+			die("Could open %s for writing", git_path("MERGE_MSG"));
+		if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
+			merge_msg.len)
+			die("Could not write to %s", git_path("MERGE_MSG"));
+		close(fd);
+	}
+
+	if (merge_was_ok) {
+		fprintf(stderr, "Automatic merge went well; "
+			"stopped before committing as requested\n");
+		return 0;
+	} else
+		return suggest_conflicts();
+}
diff --git a/builtin.h b/builtin.h
index 05ee56f..0e605d4 100644
--- a/builtin.h
+++ b/builtin.h
@@ -64,6 +64,7 @@ extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_ls_remote(int argc, const char **argv, const char *prefix);
 extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
 extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
+extern int cmd_merge(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
diff --git a/git-merge.sh b/contrib/examples/git-merge.sh
similarity index 100%
rename from git-merge.sh
rename to contrib/examples/git-merge.sh
diff --git a/git.c b/git.c
index 2fbe96b..770aadd 100644
--- a/git.c
+++ b/git.c
@@ -271,6 +271,7 @@ static void handle_internal_command(int argc, const char **argv)
 		{ "ls-remote", cmd_ls_remote },
 		{ "mailinfo", cmd_mailinfo },
 		{ "mailsplit", cmd_mailsplit },
+		{ "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
 		{ "merge-base", cmd_merge_base, RUN_SETUP },
 		{ "merge-file", cmd_merge_file },
 		{ "merge-ours", cmd_merge_ours, RUN_SETUP },
-- 
1.5.6.1

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH 13/14] git-commit-tree: make it usable from other builtins
  2008-07-01  2:22                                       ` [PATCH 13/14] git-commit-tree: make it usable from other builtins Miklos Vajna
@ 2008-07-01  5:07                                         ` Johannes Schindelin
  2008-07-01  5:50                                         ` Junio C Hamano
  1 sibling, 0 replies; 81+ messages in thread
From: Johannes Schindelin @ 2008-07-01  5:07 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: Junio C Hamano, git, Olivier Marin

Hi,

On Tue, 1 Jul 2008, Miklos Vajna wrote:

> I did it this way to avoid an unnecessary conflict with Dscho's recent 
> patch to commit-tree (ef98c5c).

Oh, sorry!

Funnily enough, I committed and tested this change in my clone of 
builtin-merge before I realized that I am in the wrong directory ;-)

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 13/14] git-commit-tree: make it usable from other builtins
  2008-07-01  2:22                                       ` [PATCH 13/14] git-commit-tree: make it usable from other builtins Miklos Vajna
  2008-07-01  5:07                                         ` Johannes Schindelin
@ 2008-07-01  5:50                                         ` Junio C Hamano
  2008-07-01 12:09                                           ` Miklos Vajna
  1 sibling, 1 reply; 81+ messages in thread
From: Junio C Hamano @ 2008-07-01  5:50 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: git, Johannes Schindelin, Olivier Marin

Miklos Vajna <vmiklos@frugalware.org> writes:

> This patch is against master, you need to pull from me or first rebase
> mv/merge-in-c against current master.

That's a bad practice.  It makes it much harder to review the incremental
changes from the previous round.

I'll cope, though.

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 14/14] Build in merge
  2008-07-01  2:37                                               ` [PATCH 14/14] Build in merge Miklos Vajna
@ 2008-07-01  6:23                                                 ` Junio C Hamano
  2008-07-01 12:50                                                   ` Miklos Vajna
  2008-07-01  7:27                                                 ` [PATCH 14/14] " Junio C Hamano
  1 sibling, 1 reply; 81+ messages in thread
From: Junio C Hamano @ 2008-07-01  6:23 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: git, Johannes Schindelin, Olivier Marin

Miklos Vajna <vmiklos@frugalware.org> writes:

> +static int read_tree_trivial(unsigned char *common, unsigned char *head,
> +	unsigned char *one)
> +{
> +	int i, nr_trees = 0;
> +	struct tree *trees[MAX_UNPACK_TREES];
> +	struct tree_desc t[MAX_UNPACK_TREES];
> +	struct unpack_trees_options opts;
> +
> +	memset(&opts, 0, sizeof(opts));
> +	opts.head_idx = 2;

Do you still need this assignment here?

> +int cmd_merge(int argc, const char **argv, const char *prefix)
> +{
> +	unsigned char result_tree[20];
> +	struct strbuf buf;
> +	const char *head_arg;
> +	int flag, head_invalid = 0, i;
> +	int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
> +	struct commit_list *common = NULL;
> +	struct path_list_item *best_strategy = NULL, *wt_strategy = NULL;
> +	struct commit_list **remotes = &remoteheads;
> +
> +	setup_work_tree();
> +	if (unmerged_cache())
> +		die("You are in the middle of a conflicted merge.");
> +
> +	/*
> +	 * Check if we are _not_ on a detached HEAD, i.e. if there is a
> +	 * current branch.
> +	 */
> +	branch = resolve_ref("HEAD", head, 0, &flag);
> +	if (branch && flag & REF_ISSYMREF) {
> +		const char *ptr = skip_prefix(branch, "refs/heads/");
> +		if (ptr)
> +			branch = ptr;
> +	} else
> +		head_invalid = 1;

Wait a minute...  Are you calling a detached HEAD as head_invalid here?  I
am not too much worried about variable naming, but you later do ...

> +	if (!have_message && is_old_style_invocation(argc, argv)) {
> ...
> +	} else if (head_invalid) {
> +		struct object *remote_head;
> +		/*
> +		 * If the merged head is a valid one there is no reason
> +		 * to forbid "git merge" into a branch yet to be born.
> +		 * We do the same for "git pull".
> +		 */
> +		if (argc != 1)
> +			die("Can merge only exactly one commit into "
> +				"empty head");

Which is about HEAD pointing at a branch that isn't born yet.  They are
two very different concepts.

Either the above "else if (head_invalid)" is wrong, or more likely the
setting of head_invalid we saw earlier is wrong.

Probably what you meant was:

	- "char *branch" points at either "HEAD" (when detached) or
          the name of the branch (e.g. "master" when "refs/heads/master");

	- "unsigned char head[]" stores the commit object name of the
          current HEAD (or 0{40} if the current branch is unborn);

        - set head_invalid to true only when the current branch is unborn.

So perhaps...

	branch = resolve_ref("HEAD", head, 0, &flag);
        if (branch && (flag & REF_ISSYMREF) && !prefixcmp(branch, "refs/heads/"))
		branch += 11;
        head_invalid = is_null_sha1(head);

And probably we can even drop (flag & REF_ISSYMREF) from the above safely.
Do we even care if the head is detached in this program?  I doubt it.

> ...
> +		strbuf_init(&msg, 0);
> +		strbuf_addstr(&msg, "Fast forward");
> +		if (have_message)
> +			strbuf_addstr(&msg,
> +				" (no commit created; -m option ignored)");
> +		o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
> +			0, NULL, OBJ_COMMIT);
> +		if (!o)
> +			return 1;
> +
> +		if (checkout_fast_forward(head, remoteheads->item->object.sha1))
> +			return 1;

Not exiting with 0 status from around here upon error is an improvement,
but does the user see sensible error messages in addition to the exit
status?

Getting warmer ;-)

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 14/14] Build in merge
  2008-07-01  2:37                                               ` [PATCH 14/14] Build in merge Miklos Vajna
  2008-07-01  6:23                                                 ` Junio C Hamano
@ 2008-07-01  7:27                                                 ` Junio C Hamano
  2008-07-01 12:55                                                   ` Miklos Vajna
  1 sibling, 1 reply; 81+ messages in thread
From: Junio C Hamano @ 2008-07-01  7:27 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: git, Johannes Schindelin, Olivier Marin

Miklos Vajna <vmiklos@frugalware.org> writes:

> +	/* See if remote matches <name>~<number>, or <name>^ */
> +	ptr = strrchr(remote, '^');
> +	if (ptr && ptr[1] == '\0') {
> +		for (len = 0, ptr = remote + strlen(remote);
> +				remote < ptr && ptr[-1] == '^';
> +				ptr--)
> +			len++;
> +	}
> +	else {
> +		ptr = strrchr(remote, '~');
> +		if (ptr && ptr[1] != '0' && isdigit(ptr[1])) {
> +			len = ptr-remote;
> +			ptr++;
> +			for (ptr++; *ptr; ptr++)
> +				if (!isdigit(*ptr)) {
> +					len = 0;
> +					break;
> +				}
> +		}
> +	}

I still have problems with the above.  I'd write it this way:

	int len, early;
	...
	/* See if remote matches <name>^^^.. or <name>~<number> */
	for (len = 0, ptr = remote + strlen(remote);
	     remote < ptr && ptr[-1] == '^';
	     ptr--)
		len++;
	if (len)
		early = 1;
	else {
		early = 0;
		ptr = strrchr(remote, '~');
		if (ptr) {
			int seen_nonzero = 0;

			len++; /* count ~ */
			while (*++ptr && isdigit(*ptr)) {
				seen_nonzero |= (*ptr != '0');
				len++;
			}
			if (*ptr)
				len = 0; /* not ...~<number> */
			else if (seen_nonzero)
				early = 1;
			else if (len == 1)
				early = 1; /* "name~" is "name~1"! */
		}
	}
	if (len) {
		struct strbuf truname = STRBUF_INIT;
		strbuf_addstr(&truname, "refs/heads/");
		strbuf_addstr(&truname, remote);
		strbuf_setlen(&truname, len+11);
		if (resolve_ref(truname.buf, buf_sha, 0, 0)) {
			strbuf_addf(msg,
				    "%s\t\tbranch '%s'%s of .\n",
				    sha1_to_hex(remote_head->sha1),
				    truname.buf,
				    (early ? " (early part)" : ""));
			return;
		}
	}


The first loop is obvious.  If the tail end is ^, we set "len" and see if
the remainder is a branch name (and if that is the case we are always
talking about an early part of it of the branch).

Otherwise, we do want to say "early part" if "$name~<number>" is given,
and another special case is "$name~" which is "$name~1" these days.  As
long as number is not zero we would want to say "early part".  Otherwise
we would want to say it is a branch itself, not its early part.

I'll queue the fixed-up result in 'pu', but I have to tend to other topics
before I can actually publish.  Together with the fix to "head_invalid"
confusion I mentioned in another message squashed in to this commit, all
the tests now finally seem to pass on the topic branch.

Oh, by the way, you sent this and the previous round without marking them
as RFC nor WIP, even though they obviously did not even pass the test
suite.  For example, without the head_invalid fix, anything that runs
merge on detached head, most notably "git rebase -i", would not work at
all.

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 13/14] git-commit-tree: make it usable from other builtins
  2008-07-01  5:50                                         ` Junio C Hamano
@ 2008-07-01 12:09                                           ` Miklos Vajna
  0 siblings, 0 replies; 81+ messages in thread
From: Miklos Vajna @ 2008-07-01 12:09 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

[-- Attachment #1: Type: text/plain, Size: 592 bytes --]

On Mon, Jun 30, 2008 at 10:50:49PM -0700, Junio C Hamano <gitster@pobox.com> wrote:
> Miklos Vajna <vmiklos@frugalware.org> writes:
> 
> > This patch is against master, you need to pull from me or first rebase
> > mv/merge-in-c against current master.
> 
> That's a bad practice.  It makes it much harder to review the incremental
> changes from the previous round.

Sorry. I just did this way because in my patch I wanted to let
cmd_commit_tree() use a commit_list and I saw Dscho already did it, and
I saw that the patch is already in master.

> I'll cope, though.

Thanks.

[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 14/14] Build in merge
  2008-07-01  6:23                                                 ` Junio C Hamano
@ 2008-07-01 12:50                                                   ` Miklos Vajna
  2008-07-01 13:18                                                     ` Miklos Vajna
  0 siblings, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-07-01 12:50 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

[-- Attachment #1: Type: text/plain, Size: 4040 bytes --]

On Mon, Jun 30, 2008 at 11:23:17PM -0700, Junio C Hamano <gitster@pobox.com> wrote:
> Miklos Vajna <vmiklos@frugalware.org> writes:
> 
> > +static int read_tree_trivial(unsigned char *common, unsigned char *head,
> > +	unsigned char *one)
> > +{
> > +	int i, nr_trees = 0;
> > +	struct tree *trees[MAX_UNPACK_TREES];
> > +	struct tree_desc t[MAX_UNPACK_TREES];
> > +	struct unpack_trees_options opts;
> > +
> > +	memset(&opts, 0, sizeof(opts));
> > +	opts.head_idx = 2;
> 
> Do you still need this assignment here?

No, it was duplicated.

> > +int cmd_merge(int argc, const char **argv, const char *prefix)
> > +{
> > +	unsigned char result_tree[20];
> > +	struct strbuf buf;
> > +	const char *head_arg;
> > +	int flag, head_invalid = 0, i;
> > +	int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
> > +	struct commit_list *common = NULL;
> > +	struct path_list_item *best_strategy = NULL, *wt_strategy = NULL;
> > +	struct commit_list **remotes = &remoteheads;
> > +
> > +	setup_work_tree();
> > +	if (unmerged_cache())
> > +		die("You are in the middle of a conflicted merge.");
> > +
> > +	/*
> > +	 * Check if we are _not_ on a detached HEAD, i.e. if there is a
> > +	 * current branch.
> > +	 */
> > +	branch = resolve_ref("HEAD", head, 0, &flag);
> > +	if (branch && flag & REF_ISSYMREF) {
> > +		const char *ptr = skip_prefix(branch, "refs/heads/");
> > +		if (ptr)
> > +			branch = ptr;
> > +	} else
> > +		head_invalid = 1;
> 
> Wait a minute...  Are you calling a detached HEAD as head_invalid here?  I
> am not too much worried about variable naming, but you later do ...
> 
> > +	if (!have_message && is_old_style_invocation(argc, argv)) {
> > ...
> > +	} else if (head_invalid) {
> > +		struct object *remote_head;
> > +		/*
> > +		 * If the merged head is a valid one there is no reason
> > +		 * to forbid "git merge" into a branch yet to be born.
> > +		 * We do the same for "git pull".
> > +		 */
> > +		if (argc != 1)
> > +			die("Can merge only exactly one commit into "
> > +				"empty head");
> 
> Which is about HEAD pointing at a branch that isn't born yet.  They are
> two very different concepts.
> 
> Either the above "else if (head_invalid)" is wrong, or more likely the
> setting of head_invalid we saw earlier is wrong.
> 
> Probably what you meant was:
> 
> 	- "char *branch" points at either "HEAD" (when detached) or
>           the name of the branch (e.g. "master" when "refs/heads/master");
> 
> 	- "unsigned char head[]" stores the commit object name of the
>           current HEAD (or 0{40} if the current branch is unborn);
> 
>         - set head_invalid to true only when the current branch is unborn.
> 
> So perhaps...
> 
> 	branch = resolve_ref("HEAD", head, 0, &flag);
>         if (branch && (flag & REF_ISSYMREF) && !prefixcmp(branch, "refs/heads/"))
> 		branch += 11;
>         head_invalid = is_null_sha1(head);
> 
> And probably we can even drop (flag & REF_ISSYMREF) from the above safely.
> Do we even care if the head is detached in this program?  I doubt it.

You are right. I though we care about it, but now I realized such a
comment was only in git-merge.annotated (in Dscho's fork), not in
git-merge.sh. And yes, I mixed up head_invalid for two purposes. Thanks
for clearing the situation.

> > +		strbuf_init(&msg, 0);
> > +		strbuf_addstr(&msg, "Fast forward");
> > +		if (have_message)
> > +			strbuf_addstr(&msg,
> > +				" (no commit created; -m option ignored)");
> > +		o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
> > +			0, NULL, OBJ_COMMIT);
> > +		if (!o)
> > +			return 1;
> > +
> > +		if (checkout_fast_forward(head, remoteheads->item->object.sha1))
> > +			return 1;
> 
> Not exiting with 0 status from around here upon error is an improvement,
> but does the user see sensible error messages in addition to the exit
> status?

Now he/she does. :-)

(I updated my working branch on repo.or.cz, will send a patch soon, as
well.)

[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 14/14] Build in merge
  2008-07-01  7:27                                                 ` [PATCH 14/14] " Junio C Hamano
@ 2008-07-01 12:55                                                   ` Miklos Vajna
  0 siblings, 0 replies; 81+ messages in thread
From: Miklos Vajna @ 2008-07-01 12:55 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

[-- Attachment #1: Type: text/plain, Size: 991 bytes --]

On Tue, Jul 01, 2008 at 12:27:48AM -0700, Junio C Hamano <gitster@pobox.com> wrote:
> I'll queue the fixed-up result in 'pu', but I have to tend to other topics
> before I can actually publish.  Together with the fix to "head_invalid"
> confusion I mentioned in another message squashed in to this commit, all
> the tests now finally seem to pass on the topic branch.
> 
> Oh, by the way, you sent this and the previous round without marking them
> as RFC nor WIP, even though they obviously did not even pass the test
> suite.  For example, without the head_invalid fix, anything that runs
> merge on detached head, most notably "git rebase -i", would not work at
> all.

Thanks for pointing that out. I remember I used to run 'make test'
before sending a patch, but that took a lot of time and then I used to
run only t*merge*.sh, which - it turns out - was a bad idea, since I
haven't noticed breaking t3404.

I'm now running a full 'make test' before I send the patch.

[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH 14/14] Build in merge
  2008-07-01 12:50                                                   ` Miklos Vajna
@ 2008-07-01 13:18                                                     ` Miklos Vajna
  2008-07-01 23:55                                                       ` Junio C Hamano
  2008-07-06  8:50                                                       ` Junio C Hamano
  0 siblings, 2 replies; 81+ messages in thread
From: Miklos Vajna @ 2008-07-01 13:18 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---

Interdiff: git diff 01a7dae..7709df7

Individual commits (there is only one change this time): git log 7709df7

Ah and please don't forget to apply the testsuite fix I sent in
$gmane/86981. (Or is it easier if I resend the whole series?)

 Makefile                                      |    2 +-
 builtin-merge.c                               | 1158 +++++++++++++++++++++++++
 builtin.h                                     |    1 +
 git-merge.sh => contrib/examples/git-merge.sh |    0 
 git.c                                         |    1 +
 5 files changed, 1161 insertions(+), 1 deletions(-)
 create mode 100644 builtin-merge.c
 rename git-merge.sh => contrib/examples/git-merge.sh (100%)

diff --git a/Makefile b/Makefile
index bf77292..fbc53e9 100644
--- a/Makefile
+++ b/Makefile
@@ -240,7 +240,6 @@ SCRIPT_SH += git-lost-found.sh
 SCRIPT_SH += git-merge-octopus.sh
 SCRIPT_SH += git-merge-one-file.sh
 SCRIPT_SH += git-merge-resolve.sh
-SCRIPT_SH += git-merge.sh
 SCRIPT_SH += git-merge-stupid.sh
 SCRIPT_SH += git-mergetool.sh
 SCRIPT_SH += git-parse-remote.sh
@@ -515,6 +514,7 @@ BUILTIN_OBJS += builtin-ls-remote.o
 BUILTIN_OBJS += builtin-ls-tree.o
 BUILTIN_OBJS += builtin-mailinfo.o
 BUILTIN_OBJS += builtin-mailsplit.o
+BUILTIN_OBJS += builtin-merge.o
 BUILTIN_OBJS += builtin-merge-base.o
 BUILTIN_OBJS += builtin-merge-file.o
 BUILTIN_OBJS += builtin-merge-ours.o
diff --git a/builtin-merge.c b/builtin-merge.c
new file mode 100644
index 0000000..b261993
--- /dev/null
+++ b/builtin-merge.c
@@ -0,0 +1,1158 @@
+/*
+ * Builtin "git merge"
+ *
+ * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
+ *
+ * Based on git-merge.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "parse-options.h"
+#include "builtin.h"
+#include "run-command.h"
+#include "path-list.h"
+#include "diff.h"
+#include "refs.h"
+#include "commit.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
+#include "dir.h"
+#include "utf8.h"
+#include "log-tree.h"
+#include "color.h"
+
+enum strategy {
+	DEFAULT_TWOHEAD = 1,
+	DEFAULT_OCTOPUS = 2,
+	NO_FAST_FORWARD = 4,
+	NO_TRIVIAL = 8
+};
+
+static const char * const builtin_merge_usage[] = {
+	"git-merge [options] <remote>...",
+	"git-merge [options] <msg> HEAD <remote>",
+	NULL
+};
+
+static int show_diffstat = 1, option_log, squash;
+static int option_commit = 1, allow_fast_forward = 1;
+static int allow_trivial = 1, have_message;
+static struct strbuf merge_msg;
+static struct commit_list *remoteheads;
+static unsigned char head[20], stash[20];
+static struct path_list use_strategies;
+static const char *branch;
+
+static struct path_list_item strategy_items[] = {
+	{ "recur",      (void *)NO_TRIVIAL },
+	{ "recursive",  (void *)(DEFAULT_TWOHEAD | NO_TRIVIAL) },
+	{ "octopus",    (void *)DEFAULT_OCTOPUS },
+	{ "resolve",    (void *)0 },
+	{ "stupid",     (void *)0 },
+	{ "ours",       (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
+	{ "subtree",    (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
+};
+static struct path_list strategies = { strategy_items,
+	ARRAY_SIZE(strategy_items), 0, 0 };
+
+static const char *pull_twohead, *pull_octopus;
+
+static int option_parse_message(const struct option *opt,
+	const char *arg, int unset)
+{
+	struct strbuf *buf = opt->value;
+
+	if (unset)
+		strbuf_setlen(buf, 0);
+	else {
+		strbuf_addf(buf, "%s\n\n", arg);
+		have_message = 1;
+	}
+	return 0;
+}
+
+static struct path_list_item *unsorted_path_list_lookup(const char *path,
+	struct path_list *list)
+{
+	int i;
+
+	if (!path)
+		return NULL;
+
+	for (i = 0; i < list->nr; i++)
+		if (!strcmp(path, list->items[i].path))
+			return &list->items[i];
+	return NULL;
+}
+
+static inline void path_list_append_strategy(struct path_list_item *item)
+{
+	path_list_append(item->path, &use_strategies)->util = item->util;
+}
+
+static int option_parse_strategy(const struct option *opt,
+	const char *arg, int unset)
+{
+	int i;
+	struct path_list_item *item = unsorted_path_list_lookup(arg, &strategies);
+
+	if (unset)
+		return 0;
+
+	if (item)
+		path_list_append_strategy(item);
+	else {
+		struct strbuf err;
+		strbuf_init(&err, 0);
+		for (i = 0; i < strategies.nr; i++)
+			strbuf_addf(&err, " %s", strategies.items[i].path);
+		fprintf(stderr, "Could not find merge strategy '%s'.\n", arg);
+		fprintf(stderr, "Available strategies are:%s.\n", err.buf);
+		exit(1);
+	}
+	return 0;
+}
+
+static int option_parse_n(const struct option *opt,
+		const char *arg, int unset)
+{
+	show_diffstat = unset;
+	return 0;
+}
+
+static struct option builtin_merge_options[] = {
+	{ OPTION_CALLBACK, 'n', NULL, NULL, NULL,
+		"do not show a diffstat at the end of the merge",
+		PARSE_OPT_NOARG, option_parse_n },
+	OPT_BOOLEAN(0, "stat", &show_diffstat,
+		"show a diffstat at the end of the merge"),
+	OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
+	OPT_BOOLEAN(0, "log", &option_log,
+		"add list of one-line log to merge commit message"),
+	OPT_BOOLEAN(0, "squash", &squash,
+		"create a single commit instead of doing a merge"),
+	OPT_BOOLEAN(0, "commit", &option_commit,
+		"perform a commit if the merge succeeds (default)"),
+	OPT_BOOLEAN(0, "ff", &allow_fast_forward,
+		"allow fast forward (default)"),
+	OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
+		"merge strategy to use", option_parse_strategy),
+	OPT_CALLBACK('m', "message", &merge_msg, "message",
+		"message to be used for the merge commit (if any)",
+		option_parse_message),
+	OPT_END()
+};
+
+/* Cleans up metadata that is uninteresting after a succeeded merge. */
+static void drop_save(void)
+{
+	unlink(git_path("MERGE_HEAD"));
+	unlink(git_path("MERGE_MSG"));
+}
+
+static void save_state(void)
+{
+	int len;
+	struct child_process cp;
+	struct strbuf buffer = STRBUF_INIT;
+	const char *argv[] = {"stash", "create", NULL};
+
+	memset(&cp, 0, sizeof(cp));
+	cp.argv = argv;
+	cp.out = -1;
+	cp.git_cmd = 1;
+
+	if (start_command(&cp))
+		die("could not run stash.");
+	len = strbuf_read(&buffer, cp.out, 1024);
+	close(cp.out);
+
+	if (finish_command(&cp) || len < 0)
+		die("stash failed");
+	else if (!len)
+		return;
+	strbuf_setlen(&buffer, buffer.len-1);
+	if (get_sha1(buffer.buf, stash))
+		die("not a valid object: %s", buffer.buf);
+}
+
+static void reset_hard(unsigned const char *sha1, int verbose)
+{
+	int i = 0;
+	const char *args[6];
+
+	args[i++] = "read-tree";
+	if (verbose)
+		args[i++] = "-v";
+	args[i++] = "--reset";
+	args[i++] = "-u";
+	args[i++] = sha1_to_hex(sha1);
+	args[i] = NULL;
+
+	if (run_command_v_opt(args, RUN_GIT_CMD))
+		die("read-tree failed");
+}
+
+static void restore_state(void)
+{
+	struct strbuf sb;
+	const char *args[] = { "stash", "apply", NULL, NULL };
+
+	if (is_null_sha1(stash))
+		return;
+
+	reset_hard(head, 1);
+
+	strbuf_init(&sb, 0);
+	args[2] = sha1_to_hex(stash);
+
+	/*
+	 * It is OK to ignore error here, for example when there was
+	 * nothing to restore.
+	 */
+	run_command_v_opt(args, RUN_GIT_CMD);
+
+	strbuf_release(&sb);
+	refresh_cache(REFRESH_QUIET);
+}
+
+/* This is called when no merge was necessary. */
+static void finish_up_to_date(const char *msg)
+{
+	printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
+	drop_save();
+}
+
+static void squash_message(void)
+{
+	struct rev_info rev;
+	struct commit *commit;
+	struct strbuf out;
+	struct commit_list *j;
+	int fd;
+
+	printf("Squash commit -- not updating HEAD\n");
+	fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+	if (fd < 0)
+		die("Could not write to %s", git_path("SQUASH_MSG"));
+
+	init_revisions(&rev, NULL);
+	rev.ignore_merges = 1;
+	rev.commit_format = CMIT_FMT_MEDIUM;
+
+	commit = lookup_commit(head);
+	commit->object.flags |= UNINTERESTING;
+	add_pending_object(&rev, &commit->object, NULL);
+
+	for (j = remoteheads; j; j = j->next)
+		add_pending_object(&rev, &j->item->object, NULL);
+
+	setup_revisions(0, NULL, &rev, NULL);
+	if (prepare_revision_walk(&rev))
+		die("revision walk setup failed");
+
+	strbuf_init(&out, 0);
+	strbuf_addstr(&out, "Squashed commit of the following:\n");
+	while ((commit = get_revision(&rev)) != NULL) {
+		strbuf_addch(&out, '\n');
+		strbuf_addf(&out, "commit %s\n",
+			sha1_to_hex(commit->object.sha1));
+		pretty_print_commit(rev.commit_format, commit, &out, rev.abbrev,
+			NULL, NULL, rev.date_mode, 0);
+	}
+	write(fd, out.buf, out.len);
+	close(fd);
+	strbuf_release(&out);
+}
+
+static int run_hook(const char *name)
+{
+	struct child_process hook;
+	const char *argv[3], *env[2];
+	char index[PATH_MAX];
+
+	argv[0] = git_path("hooks/%s", name);
+	if (access(argv[0], X_OK) < 0)
+		return 0;
+
+	snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", get_index_file());
+	env[0] = index;
+	env[1] = NULL;
+
+	if (squash)
+		argv[1] = "1";
+	else
+		argv[1] = "0";
+	argv[2] = NULL;
+
+	memset(&hook, 0, sizeof(hook));
+	hook.argv = argv;
+	hook.no_stdin = 1;
+	hook.stdout_to_stderr = 1;
+	hook.env = env;
+
+	return run_command(&hook);
+}
+
+static void finish(const unsigned char *new_head, const char *msg)
+{
+	struct strbuf reflog_message;
+
+	strbuf_init(&reflog_message, 0);
+	if (!msg)
+		strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
+	else {
+		printf("%s\n", msg);
+		strbuf_addf(&reflog_message, "%s: %s",
+			getenv("GIT_REFLOG_ACTION"), msg);
+	}
+	if (squash) {
+		squash_message();
+	} else {
+		if (!merge_msg.len)
+			printf("No merge message -- not updating HEAD\n");
+		else {
+			const char *argv_gc_auto[] = { "gc", "--auto", NULL };
+			update_ref(reflog_message.buf, "HEAD",
+				new_head, head, 0,
+				DIE_ON_ERR);
+			/*
+			 * We ignore errors in 'gc --auto', since the
+			 * user should see them.
+			 */
+			run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+		}
+	}
+	if (new_head && show_diffstat) {
+		struct diff_options opts;
+		diff_setup(&opts);
+		opts.output_format |=
+			DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+		opts.detect_rename = DIFF_DETECT_RENAME;
+		if (diff_use_color_default > 0)
+			DIFF_OPT_SET(&opts, COLOR_DIFF);
+		if (diff_setup_done(&opts) < 0)
+			die("diff_setup_done failed");
+		diff_tree_sha1(head, new_head, "", &opts);
+		diffcore_std(&opts);
+		diff_flush(&opts);
+	}
+
+	/* Run a post-merge hook */
+	run_hook("post-merge");
+
+	strbuf_release(&reflog_message);
+}
+
+/* Get the name for the merge commit's message. */
+static void merge_name(const char *remote, struct strbuf *msg)
+{
+	struct object *remote_head;
+	unsigned char branch_head[20], buf_sha[20];
+	struct strbuf buf;
+	const char *ptr;
+	int len, early;
+
+	memset(branch_head, 0, sizeof(branch_head));
+	remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+	if (!remote_head)
+		die("'%s' does not point to a commit", remote);
+
+	strbuf_init(&buf, 0);
+	strbuf_addstr(&buf, "refs/heads/");
+	strbuf_addstr(&buf, remote);
+	resolve_ref(buf.buf, branch_head, 0, 0);
+
+	if (!hashcmp(remote_head->sha1, branch_head)) {
+		strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
+			sha1_to_hex(branch_head), remote);
+		return;
+	}
+
+	/* See if remote matches <name>^^^.. or <name>~<number> */
+	for (len = 0, ptr = remote + strlen(remote);
+	     remote < ptr && ptr[-1] == '^';
+	     ptr--)
+		len++;
+	if (len)
+		early = 1;
+	else {
+		early = 0;
+		ptr = strrchr(remote, '~');
+		if (ptr) {
+			int seen_nonzero = 0;
+
+			len++; /* count ~ */
+			while (*++ptr && isdigit(*ptr)) {
+				seen_nonzero |= (*ptr != '0');
+				len++;
+			}
+			if (*ptr)
+				len = 0; /* not ...~<number> */
+			else if (seen_nonzero)
+				early = 1;
+			else if (len == 1)
+				early = 1; /* "name~" is "name~1"! */
+		}
+	}
+	if (len) {
+		struct strbuf truname = STRBUF_INIT;
+		strbuf_addstr(&truname, "refs/heads/");
+		strbuf_addstr(&truname, remote);
+		strbuf_setlen(&truname, len+11);
+		if (resolve_ref(truname.buf, buf_sha, 0, 0)) {
+			strbuf_addf(msg,
+				    "%s\t\tbranch '%s'%s of .\n",
+				    sha1_to_hex(remote_head->sha1),
+				    truname.buf,
+				    (early ? " (early part)" : ""));
+			return;
+		}
+	}
+
+	if (!strcmp(remote, "FETCH_HEAD") &&
+			!access(git_path("FETCH_HEAD"), R_OK)) {
+		FILE *fp;
+		struct strbuf line;
+		char *ptr;
+
+		strbuf_init(&line, 0);
+		fp = fopen(git_path("FETCH_HEAD"), "r");
+		if (!fp)
+			die("could not open %s for reading: %s",
+				git_path("FETCH_HEAD"), strerror(errno));
+		strbuf_getline(&line, fp, '\n');
+		fclose(fp);
+		ptr = strstr(line.buf, "\tnot-for-merge\t");
+		if (ptr)
+			strbuf_remove(&line, ptr-line.buf+1, 13);
+		strbuf_addbuf(msg, &line);
+		strbuf_release(&line);
+		return;
+	}
+	strbuf_addf(msg, "%s\t\tcommit '%s'\n",
+		sha1_to_hex(remote_head->sha1), remote);
+}
+
+int git_merge_config(const char *k, const char *v, void *cb)
+{
+	if (branch && !prefixcmp(k, "branch.") &&
+		!prefixcmp(k + 7, branch) &&
+		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+		const char **argv;
+		int argc;
+		char *buf;
+
+		buf = xstrdup(v);
+		argc = split_cmdline(buf, &argv);
+		argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+		memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+		argc++;
+		parse_options(argc, argv, builtin_merge_options,
+			      builtin_merge_usage, 0);
+		free(buf);
+	}
+
+	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+		show_diffstat = git_config_bool(k, v);
+	else if (!strcmp(k, "pull.twohead"))
+		return git_config_string(&pull_twohead, k, v);
+	else if (!strcmp(k, "pull.octopus"))
+		return git_config_string(&pull_octopus, k, v);
+	return git_diff_ui_config(k, v, cb);
+}
+
+static int read_tree_trivial(unsigned char *common, unsigned char *head,
+	unsigned char *one)
+{
+	int i, nr_trees = 0;
+	struct tree *trees[MAX_UNPACK_TREES];
+	struct tree_desc t[MAX_UNPACK_TREES];
+	struct unpack_trees_options opts;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.head_idx = 2;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.verbose_update = 1;
+	opts.trivial_merges_only = 1;
+	opts.merge = 1;
+	trees[nr_trees] = parse_tree_indirect(common);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(head);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(one);
+	if (!trees[nr_trees++])
+		return -1;
+	opts.fn = threeway_merge;
+	cache_tree_free(&active_cache_tree);
+	for (i = 0; i < nr_trees; i++) {
+		parse_tree(trees[i]);
+		init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+	}
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+	return 0;
+}
+
+static void write_tree_trivial(unsigned char *sha1)
+{
+	if (write_cache_as_tree(sha1, 0, NULL))
+		die("git write-tree failed to write a tree");
+}
+
+static int try_merge_strategy(char *strategy, struct commit_list *common,
+	const char *head_arg)
+{
+	const char **args;
+	int i = 0, ret;
+	struct commit_list *j;
+	struct strbuf buf;
+
+	args = xmalloc((4 + commit_list_count(common) +
+			commit_list_count(remoteheads)) * sizeof(char *));
+	strbuf_init(&buf, 0);
+	strbuf_addf(&buf, "merge-%s", strategy);
+	args[i++] = buf.buf;
+	for (j = common; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i++] = "--";
+	args[i++] = head_arg;
+	for (j = remoteheads; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i] = NULL;
+	ret = run_command_v_opt(args, RUN_GIT_CMD);
+	strbuf_release(&buf);
+	i = 1;
+	for (j = common; j; j = j->next)
+		free((void *)args[i++]);
+	i += 2;
+	for (j = remoteheads; j; j = j->next)
+		free((void *)args[i++]);
+	free(args);
+	return -ret;
+}
+
+static void count_diff_files(struct diff_queue_struct *q,
+		struct diff_options *opt, void *data)
+{
+	int *count = data;
+
+	(*count) += q->nr;
+}
+
+static int count_unmerged_entries(void)
+{
+	const struct index_state *state = &the_index;
+	int i, ret = 0;
+
+	for (i = 0; i < state->cache_nr; i++)
+		if (ce_stage(state->cache[i]))
+			ret++;
+
+	return ret;
+}
+
+static int checkout_fast_forward(unsigned char *head, unsigned char *remote)
+{
+	struct tree *trees[MAX_UNPACK_TREES];
+	struct unpack_trees_options opts;
+	struct tree_desc t[MAX_UNPACK_TREES];
+	int i, fd, nr_trees = 0;
+	struct dir_struct dir;
+	struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+	if (read_cache_unmerged())
+		die("you need to resolve your current index first");
+
+	fd = hold_locked_index(lock_file, 1);
+
+	memset(&trees, 0, sizeof(trees));
+	memset(&opts, 0, sizeof(opts));
+	memset(&t, 0, sizeof(t));
+	dir.show_ignored = 1;
+	dir.exclude_per_dir = ".gitignore";
+	opts.dir = &dir;
+
+	opts.head_idx = 1;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.verbose_update = 1;
+	opts.merge = 1;
+	opts.fn = twoway_merge;
+
+	trees[nr_trees] = parse_tree_indirect(head);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(remote);
+	if (!trees[nr_trees++])
+		return -1;
+	for (i = 0; i < nr_trees; i++) {
+		parse_tree(trees[i]);
+		init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+	}
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+	if (write_cache(fd, active_cache, active_nr) ||
+		commit_locked_index(lock_file))
+		die("unable to write new index file");
+	return 0;
+}
+
+static void split_merge_strategies(const char *string, struct path_list *list)
+{
+	char *p, *q, *buf;
+
+	if (!string)
+		return;
+
+	list->strdup_paths = 1;
+	buf = xstrdup(string);
+	q = buf;
+	for (;;) {
+		p = strchr(q, ' ');
+		if (!p) {
+			path_list_append(q, list);
+			free(buf);
+			return;
+		} else {
+			*p = '\0';
+			path_list_append(q, list);
+			q = ++p;
+		}
+	}
+}
+
+static void add_strategies(const char *string, enum strategy strategy)
+{
+	struct path_list list;
+	int i;
+
+	memset(&list, 0, sizeof(list));
+	split_merge_strategies(string, &list);
+	if (list.nr) {
+		for (i = 0; i < list.nr; i++) {
+			struct path_list_item *item;
+
+			item = unsorted_path_list_lookup(list.items[i].path,
+				&strategies);
+			if (item)
+				path_list_append_strategy(item);
+		}
+		return;
+	}
+	for (i = 0; i < strategies.nr; i++)
+		if ((enum strategy)strategies.items[i].util & strategy)
+			path_list_append_strategy(&strategies.items[i]);
+}
+
+static int merge_trivial(void)
+{
+	unsigned char result_tree[20], result_commit[20];
+	struct commit_list parent;
+
+	write_tree_trivial(result_tree);
+	printf("Wonderful.\n");
+	parent.item = remoteheads->item;
+	parent.next = NULL;
+	commit_tree(merge_msg.buf, result_tree, &parent, result_commit);
+	finish(result_commit, "In-index merge");
+	drop_save();
+	return 0;
+}
+
+static int finish_automerge(struct commit_list *common,
+	unsigned char *result_tree,
+	struct path_list_item *wt_strategy)
+{
+	struct commit_list *parents = NULL, *j;
+	struct strbuf buf = STRBUF_INIT;
+	unsigned char result_commit[20];
+
+	free_commit_list(common);
+	if (allow_fast_forward) {
+		parents = remoteheads;
+		commit_list_insert(lookup_commit(head), &parents);
+		parents = reduce_heads(parents);
+	} else {
+		struct commit_list **pptr = &parents;
+
+		pptr = &commit_list_insert(lookup_commit(head),
+				pptr)->next;
+		for (j = remoteheads; j; j = j->next)
+			pptr = &commit_list_insert(j->item, pptr)->next;
+	}
+	free_commit_list(remoteheads);
+	strbuf_addch(&merge_msg, '\n');
+	commit_tree(merge_msg.buf, result_tree, parents, result_commit);
+	strbuf_addf(&buf, "Merge made by %s.", wt_strategy->path);
+	finish(result_commit, buf.buf);
+	strbuf_release(&buf);
+	drop_save();
+	return 0;
+}
+
+static int suggest_conflicts(void)
+{
+	FILE *fp;
+	int pos;
+
+	fp = fopen(git_path("MERGE_MSG"), "a");
+	if (!fp)
+		die("Could open %s for writing", git_path("MERGE_MSG"));
+	fprintf(fp, "\nConflicts:\n");
+	for (pos = 0; pos < active_nr; pos++) {
+		struct cache_entry *ce = active_cache[pos];
+
+		if (ce_stage(ce)) {
+			fprintf(fp, "\t%s\n", ce->name);
+			while (pos + 1 < active_nr &&
+					!strcmp(ce->name,
+						active_cache[pos + 1]->name))
+				pos++;
+		}
+	}
+	fclose(fp);
+	rerere();
+	printf("Automatic merge failed; "
+			"fix conflicts and then commit the result.\n");
+	return 1;
+}
+
+static inline unsigned nth_strategy_flags(struct path_list *s, int nth)
+{
+	return (unsigned) s->items[nth].util;
+}
+
+static struct commit *is_old_style_invocation(int argc, const char **argv)
+{
+	struct commit *second_token = NULL;
+	if (argc > 1) {
+		unsigned char second_sha1[20];
+
+		if (get_sha1(argv[1], second_sha1))
+			return NULL;
+		second_token = lookup_commit_reference_gently(second_sha1, 0);
+		if (!second_token)
+			die("'%s' is not a commit", argv[1]);
+		if (hashcmp(second_token->object.sha1, head))
+			return NULL;
+	}
+	return second_token;
+}
+
+static int evaluate_result(void)
+{
+	int cnt = 0;
+	struct rev_info rev;
+
+	if (read_cache() < 0)
+		die("failed to read the cache");
+
+	/* Check how many files differ. */
+	init_revisions(&rev, "");
+	setup_revisions(0, NULL, &rev, NULL);
+	rev.diffopt.output_format |=
+		DIFF_FORMAT_CALLBACK;
+	rev.diffopt.format_callback = count_diff_files;
+	rev.diffopt.format_callback_data = &cnt;
+	run_diff_files(&rev, 0);
+
+	/*
+	 * Check how many unmerged entries are
+	 * there.
+	 */
+	cnt += count_unmerged_entries();
+
+	return cnt;
+}
+
+int cmd_merge(int argc, const char **argv, const char *prefix)
+{
+	unsigned char result_tree[20];
+	struct strbuf buf;
+	const char *head_arg;
+	int flag, head_invalid = 0, i;
+	int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
+	struct commit_list *common = NULL;
+	struct path_list_item *best_strategy = NULL, *wt_strategy = NULL;
+	struct commit_list **remotes = &remoteheads;
+
+	setup_work_tree();
+	if (unmerged_cache())
+		die("You are in the middle of a conflicted merge.");
+
+	/*
+	 * Check if we are _not_ on a detached HEAD, i.e. if there is a
+	 * current branch.
+	 */
+	branch = resolve_ref("HEAD", head, 0, &flag);
+	if (branch && !prefixcmp(branch, "refs/heads/"))
+		branch += 11;
+	if (is_null_sha1(head))
+		head_invalid = 1;
+
+	git_config(git_merge_config, NULL);
+
+	/* for color.ui */
+	if (diff_use_color_default == -1)
+		diff_use_color_default = git_use_color_default;
+
+	argc = parse_options(argc, argv, builtin_merge_options,
+			builtin_merge_usage, 0);
+
+	if (squash) {
+		if (!allow_fast_forward)
+			die("You cannot combine --squash with --no-ff.");
+		option_commit = 0;
+	}
+
+	if (!argc)
+		usage_with_options(builtin_merge_usage,
+			builtin_merge_options);
+
+	/*
+	 * This could be traditional "merge <msg> HEAD <commit>..."  and
+	 * the way we can tell it is to see if the second token is HEAD,
+	 * but some people might have misused the interface and used a
+	 * committish that is the same as HEAD there instead.
+	 * Traditional format never would have "-m" so it is an
+	 * additional safety measure to check for it.
+	 */
+	strbuf_init(&buf, 0);
+
+	if (!have_message && is_old_style_invocation(argc, argv)) {
+		strbuf_addstr(&merge_msg, argv[0]);
+		head_arg = argv[1];
+		argv += 2;
+		argc -= 2;
+	} else if (head_invalid) {
+		struct object *remote_head;
+		/*
+		 * If the merged head is a valid one there is no reason
+		 * to forbid "git merge" into a branch yet to be born.
+		 * We do the same for "git pull".
+		 */
+		if (argc != 1)
+			die("Can merge only exactly one commit into "
+				"empty head");
+		remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+		if (!remote_head)
+			die("%s - not something we can merge", argv[0]);
+		update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
+				DIE_ON_ERR);
+		reset_hard(remote_head->sha1, 0);
+		return 0;
+	} else {
+		struct strbuf msg;
+
+		/* We are invoked directly as the first-class UI. */
+		head_arg = "HEAD";
+
+		/*
+		 * All the rest are the commits being merged;
+		 * prepare the standard merge summary message to
+		 * be appended to the given message.  If remote
+		 * is invalid we will die later in the common
+		 * codepath so we discard the error in this
+		 * loop.
+		 */
+		strbuf_init(&msg, 0);
+		for (i = 0; i < argc; i++)
+			merge_name(argv[i], &msg);
+		fmt_merge_msg(option_log, &msg, &merge_msg);
+		if (merge_msg.len)
+			strbuf_setlen(&merge_msg, merge_msg.len-1);
+	}
+
+	if (head_invalid || !argc)
+		usage_with_options(builtin_merge_usage,
+			builtin_merge_options);
+
+	strbuf_addstr(&buf, "merge");
+	for (i = 0; i < argc; i++)
+		strbuf_addf(&buf, " %s", argv[i]);
+	setenv("GIT_REFLOG_ACTION", buf.buf, 0);
+	strbuf_reset(&buf);
+
+	for (i = 0; i < argc; i++) {
+		struct object *o;
+
+		o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
+		if (!o)
+			die("%s - not something we can merge", argv[i]);
+		remotes = &commit_list_insert(lookup_commit(o->sha1),
+			remotes)->next;
+
+		strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
+		setenv(buf.buf, argv[i], 1);
+		strbuf_reset(&buf);
+	}
+
+	if (!use_strategies.nr) {
+		if (!remoteheads->next)
+			add_strategies(pull_twohead, DEFAULT_TWOHEAD);
+		else
+			add_strategies(pull_octopus, DEFAULT_OCTOPUS);
+	}
+
+	for (i = 0; i < use_strategies.nr; i++) {
+		if (nth_strategy_flags(&use_strategies, i) & NO_FAST_FORWARD)
+			allow_fast_forward = 0;
+		if (nth_strategy_flags(&use_strategies, i) & NO_TRIVIAL)
+			allow_trivial = 0;
+	}
+
+	if (!remoteheads->next)
+		common = get_merge_bases(lookup_commit(head),
+				remoteheads->item, 1);
+	else {
+		struct commit_list *list = remoteheads;
+		commit_list_insert(lookup_commit(head), &list);
+		common = get_octopus_merge_bases(list);
+		free(list);
+	}
+
+	update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
+		DIE_ON_ERR);
+
+	if (!common)
+		; /* No common ancestors found. We need a real merge. */
+	else if (!remoteheads->next && !common->next &&
+			common->item == remoteheads->item) {
+		/*
+		 * If head can reach all the merge then we are up to date.
+		 * but first the most common case of merging one remote.
+		 */
+		finish_up_to_date("Already up-to-date.");
+		return 0;
+	} else if (allow_fast_forward && !remoteheads->next &&
+			!common->next &&
+			!hashcmp(common->item->object.sha1, head)) {
+		/* Again the most common case of merging one remote. */
+		struct strbuf msg;
+		struct object *o;
+		char hex[41];
+
+		strcpy(hex, find_unique_abbrev(remoteheads->item->object.sha1,
+			DEFAULT_ABBREV));
+
+		printf("Updating %s..%s\n",
+			find_unique_abbrev(head, DEFAULT_ABBREV),
+			hex);
+		refresh_cache(REFRESH_QUIET);
+		strbuf_init(&msg, 0);
+		strbuf_addstr(&msg, "Fast forward");
+		if (have_message)
+			strbuf_addstr(&msg,
+				" (no commit created; -m option ignored)");
+		o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
+			0, NULL, OBJ_COMMIT);
+		if (!o) {
+			error("%s is not a commit", hex);
+			return 1;
+		}
+
+		if (checkout_fast_forward(head, remoteheads->item->object.sha1)) {
+			error("failed to fast-forward to %s", hex);
+			return 1;
+		}
+
+		finish(o->sha1, msg.buf);
+		drop_save();
+		return 0;
+	} else if (!remoteheads->next && common->next)
+		;
+		/*
+		 * We are not doing octopus and not fast forward.  Need
+		 * a real merge.
+		 */
+	else if (!remoteheads->next && !common->next && option_commit) {
+		/*
+		 * We are not doing octopus, not fast forward, and have
+		 * only one common.
+		 */
+		refresh_cache(REFRESH_QUIET);
+		if (allow_trivial) {
+			/* See if it is really trivial. */
+			git_committer_info(IDENT_ERROR_ON_NO_NAME);
+			printf("Trying really trivial in-index merge...\n");
+			if (!read_tree_trivial(common->item->object.sha1,
+					head, remoteheads->item->object.sha1))
+				return merge_trivial();
+			printf("Nope.\n");
+		}
+	} else {
+		/*
+		 * An octopus.  If we can reach all the remote we are up
+		 * to date.
+		 */
+		int up_to_date = 1;
+		struct commit_list *j;
+
+		for (j = remoteheads; j; j = j->next) {
+			struct commit_list *common_one;
+
+			/*
+			 * Here we *have* to calculate the individual
+			 * merge_bases again, otherwise "git merge HEAD^
+			 * HEAD^^" would be missed.
+			 */
+			common_one = get_merge_bases(lookup_commit(head),
+				j->item, 1);
+			if (hashcmp(common_one->item->object.sha1,
+				j->item->object.sha1)) {
+				up_to_date = 0;
+				break;
+			}
+		}
+		if (up_to_date) {
+			finish_up_to_date("Already up-to-date. Yeeah!");
+			return 0;
+		}
+	}
+
+	/* We are going to make a new commit. */
+	git_committer_info(IDENT_ERROR_ON_NO_NAME);
+
+	/*
+	 * At this point, we need a real merge.  No matter what strategy
+	 * we use, it would operate on the index, possibly affecting the
+	 * working tree, and when resolved cleanly, have the desired
+	 * tree in the index -- this means that the index must be in
+	 * sync with the head commit.  The strategies are responsible
+	 * to ensure this.
+	 */
+	if (use_strategies.nr != 1) {
+		/*
+		 * Stash away the local changes so that we can try more
+		 * than one.
+		 */
+		save_state();
+	} else {
+		memcpy(stash, null_sha1, 20);
+	}
+
+	for (i = 0; i < use_strategies.nr; i++) {
+		int ret;
+		if (i) {
+			printf("Rewinding the tree to pristine...\n");
+			restore_state();
+		}
+		if (use_strategies.nr != 1)
+			printf("Trying merge strategy %s...\n",
+				use_strategies.items[i].path);
+		/*
+		 * Remember which strategy left the state in the working
+		 * tree.
+		 */
+		wt_strategy = &use_strategies.items[i];
+
+		ret = try_merge_strategy(use_strategies.items[i].path,
+			common, head_arg);
+		if (!option_commit && !ret) {
+			merge_was_ok = 1;
+			/*
+			 * This is necessary here just to avoid writing
+			 * the tree, but later we will *not* exit with
+			 * status code 1 because merge_was_ok is set.
+			 */
+			ret = 1;
+		}
+
+		if (ret) {
+			/*
+			 * The backend exits with 1 when conflicts are
+			 * left to be resolved, with 2 when it does not
+			 * handle the given merge at all.
+			 */
+			if (ret == 1) {
+				int cnt = evaluate_result();
+
+				if (best_cnt <= 0 || cnt <= best_cnt) {
+					best_strategy =
+						&use_strategies.items[i];
+					best_cnt = cnt;
+				}
+			}
+			if (merge_was_ok)
+				break;
+			else
+				continue;
+		}
+
+		/* Automerge succeeded. */
+		write_tree_trivial(result_tree);
+		automerge_was_ok = 1;
+		break;
+	}
+
+	/*
+	 * If we have a resulting tree, that means the strategy module
+	 * auto resolved the merge cleanly.
+	 */
+	if (automerge_was_ok)
+		return finish_automerge(common, result_tree, wt_strategy);
+
+	/*
+	 * Pick the result from the best strategy and have the user fix
+	 * it up.
+	 */
+	if (!best_strategy) {
+		restore_state();
+		if (use_strategies.nr > 1)
+			fprintf(stderr,
+				"No merge strategy handled the merge.\n");
+		else
+			fprintf(stderr, "Merge with strategy %s failed.\n",
+				use_strategies.items[0].path);
+		return 2;
+	} else if (best_strategy == wt_strategy)
+		; /* We already have its result in the working tree. */
+	else {
+		printf("Rewinding the tree to pristine...\n");
+		restore_state();
+		printf("Using the %s to prepare resolving by hand.\n",
+			best_strategy->path);
+		try_merge_strategy(best_strategy->path, common, head_arg);
+	}
+
+	if (squash)
+		finish(NULL, NULL);
+	else {
+		int fd;
+		struct commit_list *j;
+
+		for (j = remoteheads; j; j = j->next)
+			strbuf_addf(&buf, "%s\n",
+				sha1_to_hex(j->item->object.sha1));
+		fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
+		if (fd < 0)
+			die("Could open %s for writing",
+				git_path("MERGE_HEAD"));
+		if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+			die("Could not write to %s", git_path("MERGE_HEAD"));
+		close(fd);
+		strbuf_addch(&merge_msg, '\n');
+		fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+		if (fd < 0)
+			die("Could open %s for writing", git_path("MERGE_MSG"));
+		if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
+			merge_msg.len)
+			die("Could not write to %s", git_path("MERGE_MSG"));
+		close(fd);
+	}
+
+	if (merge_was_ok) {
+		fprintf(stderr, "Automatic merge went well; "
+			"stopped before committing as requested\n");
+		return 0;
+	} else
+		return suggest_conflicts();
+}
diff --git a/builtin.h b/builtin.h
index 05ee56f..0e605d4 100644
--- a/builtin.h
+++ b/builtin.h
@@ -64,6 +64,7 @@ extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_ls_remote(int argc, const char **argv, const char *prefix);
 extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
 extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
+extern int cmd_merge(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
diff --git a/git-merge.sh b/contrib/examples/git-merge.sh
similarity index 100%
rename from git-merge.sh
rename to contrib/examples/git-merge.sh
diff --git a/git.c b/git.c
index 2fbe96b..770aadd 100644
--- a/git.c
+++ b/git.c
@@ -271,6 +271,7 @@ static void handle_internal_command(int argc, const char **argv)
 		{ "ls-remote", cmd_ls_remote },
 		{ "mailinfo", cmd_mailinfo },
 		{ "mailsplit", cmd_mailsplit },
+		{ "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
 		{ "merge-base", cmd_merge_base, RUN_SETUP },
 		{ "merge-file", cmd_merge_file },
 		{ "merge-ours", cmd_merge_ours, RUN_SETUP },
-- 
1.5.6.1

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH 14/14] Build in merge
  2008-07-01 13:18                                                     ` Miklos Vajna
@ 2008-07-01 23:55                                                       ` Junio C Hamano
  2008-07-02  7:43                                                         ` Miklos Vajna
  2008-07-06  8:50                                                       ` Junio C Hamano
  1 sibling, 1 reply; 81+ messages in thread
From: Junio C Hamano @ 2008-07-01 23:55 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: git, Johannes Schindelin, Olivier Marin

Miklos Vajna <vmiklos@frugalware.org> writes:

> Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
> Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
> ---
>
> Interdiff: git diff 01a7dae..7709df7
>
> Individual commits (there is only one change this time): git log 7709df7
>
> Ah and please don't forget to apply the testsuite fix I sent in
> $gmane/86981. (Or is it easier if I resend the whole series?)

Are you sure about that?  I've been deliberately fixing that patch.

As far as I understand it, your series:

 - starts out building infrastructure;
 - adds tests;
 - finally replaces git-merge with the new implementation.

and the test in question comes when git-merge built from that version is
still the scripted version with limitation.

So if you build and test after applying individual steps (you sure do
that, don't you?), the test has to start with expecting failure (from the
scripted git-merge that has the octopus limitation) and then later changed
to expect success when git-merge is replaced.

Have you looked at the series I fixed up before pushing out in 'pu' last
night, specifically 5948e2a and 01a7dae?  If you actually built and tested
5948e2a, you'd notice that the test in that commit *must* expect failure.

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 14/14] Build in merge
  2008-07-01 23:55                                                       ` Junio C Hamano
@ 2008-07-02  7:43                                                         ` Miklos Vajna
  0 siblings, 0 replies; 81+ messages in thread
From: Miklos Vajna @ 2008-07-02  7:43 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

[-- Attachment #1: Type: text/plain, Size: 366 bytes --]

On Tue, Jul 01, 2008 at 04:55:21PM -0700, Junio C Hamano <gitster@pobox.com> wrote:
> Have you looked at the series I fixed up before pushing out in 'pu' last
> night, specifically 5948e2a and 01a7dae?  If you actually built and tested
> 5948e2a, you'd notice that the test in that commit *must* expect failure.

Aah. Now I see the point, thanks for explaining! :-)

[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 04/15] Add new test to ensure git-merge handles pull.twohead and pull.octopus
  2008-06-27 16:21       ` [PATCH 04/15] Add new test to ensure git-merge handles pull.twohead and pull.octopus Miklos Vajna
  2008-06-27 16:21         ` [PATCH 05/15] Move read_cache_unmerged() to read-cache.c Miklos Vajna
  2008-06-29 13:30         ` [PATCH 04/15] Add new test to ensure git-merge handles pull.twohead and pull.octopus Olivier Marin
@ 2008-07-04 16:34         ` Mike Ralphson
  2008-07-05  0:26           ` Miklos Vajna
  2 siblings, 1 reply; 81+ messages in thread
From: Mike Ralphson @ 2008-07-04 16:34 UTC (permalink / raw)
  To: Miklos Vajna, Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

2008/6/27 Miklos Vajna <vmiklos@frugalware.org>:
>
> Test if the given strategies are used and test the case when multiple
> strategies are configured using a space separated list.
>
> Also test if the best strategy is picked if none is specified.  This is
> done by adding a simple test case where recursive detects a rename, but
> resolve does not, and verify that finally merge will pick up the
> previous.
>
> Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
> ---
>  t/t7601-merge-pull-config.sh |  129 ++++++++++++++++++++++++++++++++++++++++++

> +
> +conflict_count()
> +{
> +       eval $1=`{
> +               git diff-files --name-only
> +               git ls-files --unmerged
> +       } | wc -l`
> +}
> +

This here causes the test to fail on AIX (and likely other OS, such as
apparently OSX) where wc -l outputs whitespace. See
http://article.gmane.org/gmane.comp.version-control.git/80450

Here we want the line count not just a return value, so is the
following acceptable?

diff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh
index 32585f8..9b6097d 100755
--- a/t/t7601-merge-pull-config.sh
+++ b/t/t7601-merge-pull-config.sh
@@ -73,7 +73,7 @@ conflict_count()
        eval $1=`{
                git diff-files --name-only
                git ls-files --unmerged
-       } | wc -l`
+       } | wc -l | tr -d \ `
 }

 # c4 - c5

Signed-off-by: Mike Ralphson <mike@abacus.co.uk>

Anyway, I thought we preferred $() to backticks?

I do apologise for not being around for the earlier comedy breakage on
AIX, my sysadmins decided to 'improve' our firewall rules which cut my
automated builds off just after the final rc for 1.5.6, then I was on
vacation miles from any computers.

Mike

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH 04/15] Add new test to ensure git-merge handles pull.twohead and pull.octopus
  2008-07-04 16:34         ` [PATCH 04/15] " Mike Ralphson
@ 2008-07-05  0:26           ` Miklos Vajna
  2008-07-05  0:32             ` [PATCH] Fix t7601-merge-pull-config.sh on AIX Miklos Vajna
  0 siblings, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-07-05  0:26 UTC (permalink / raw)
  To: Mike Ralphson; +Cc: Junio C Hamano, git, Johannes Schindelin, Olivier Marin

[-- Attachment #1: Type: text/plain, Size: 1204 bytes --]

On Fri, Jul 04, 2008 at 05:34:05PM +0100, Mike Ralphson <mike.ralphson@gmail.com> wrote:
> > +conflict_count()
> > +{
> > +       eval $1=`{
> > +               git diff-files --name-only
> > +               git ls-files --unmerged
> > +       } | wc -l`
> > +}
> > +
> 
> This here causes the test to fail on AIX (and likely other OS, such as
> apparently OSX) where wc -l outputs whitespace. See
> http://article.gmane.org/gmane.comp.version-control.git/80450
> 
> Here we want the line count not just a return value, so is the
> following acceptable?
> 
> diff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh
> index 32585f8..9b6097d 100755
> --- a/t/t7601-merge-pull-config.sh
> +++ b/t/t7601-merge-pull-config.sh
> @@ -73,7 +73,7 @@ conflict_count()
>         eval $1=`{
>                 git diff-files --name-only
>                 git ls-files --unmerged
> -       } | wc -l`
> +       } | wc -l | tr -d \ `
>  }

At least it does not break the test for me on Linux. But haven't this
cause you problems in git-merge.sh?  I copied this code chunk from
there.

> Anyway, I thought we preferred $() to backticks?

See above, but sure, we can.

[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH] Fix t7601-merge-pull-config.sh on AIX
  2008-07-05  0:26           ` Miklos Vajna
@ 2008-07-05  0:32             ` Miklos Vajna
  2008-07-05  1:49               ` Junio C Hamano
  0 siblings, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-07-05  0:32 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Schindelin, Olivier Marin, git, Mike Ralphson

The test failed on AIX (and likely other OS, such as apparently OSX)
where wc -l outputs whitespace.

Signed-off-by: Mike Ralphson <mike@abacus.co.uk>
Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---

Here is the same, with backticks avoided, and with a proper commit
message.

 t/t7601-merge-pull-config.sh |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh
index 32585f8..12f71ad 100755
--- a/t/t7601-merge-pull-config.sh
+++ b/t/t7601-merge-pull-config.sh
@@ -70,10 +70,10 @@ test_expect_success 'merge c1 with c2 and c3 (recursive and octopus in pull.octo
 
 conflict_count()
 {
-	eval $1=`{
+	eval $1=$({
 		git diff-files --name-only
 		git ls-files --unmerged
-	} | wc -l`
+	} | wc -l | tr -d \ )
 }
 
 # c4 - c5
-- 
1.5.6.1

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH] Fix t7601-merge-pull-config.sh on AIX
  2008-07-05  0:32             ` [PATCH] Fix t7601-merge-pull-config.sh on AIX Miklos Vajna
@ 2008-07-05  1:49               ` Junio C Hamano
  0 siblings, 0 replies; 81+ messages in thread
From: Junio C Hamano @ 2008-07-05  1:49 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: Johannes Schindelin, Olivier Marin, git, Mike Ralphson

Miklos Vajna <vmiklos@frugalware.org> writes:

> diff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh
> index 32585f8..12f71ad 100755
> --- a/t/t7601-merge-pull-config.sh
> +++ b/t/t7601-merge-pull-config.sh
> @@ -70,10 +70,10 @@ test_expect_success 'merge c1 with c2 and c3 (recursive and octopus in pull.octo
>  
>  conflict_count()
>  {
> -	eval $1=`{
> +	eval $1=$({
>  		git diff-files --name-only
>  		git ls-files --unmerged
> -	} | wc -l`
> +	} | wc -l | tr -d \ )
>  }

In any case, this feels like an unnecessary use of eval.  The call site
you have look like this:

	conflict_count resolve_count

but it is more natural if you are programming in shell to call it like:

	resolve_count=$(count_conflicts)

and it is more natural to write count_conflicts like this:

	count_conflicts () {
		{
                	git diff-files --name-only --diff-filter=U
                        git ls-files --unmerged
                } | wc -l
        }

But I am puzzled about the alledged *breakage* -- look at your call
sites.

        reset --hard
	.. do a merge ..
        conflict_count count_one

	reset --hard
        .. do another merge ..
        conlict_count count_two

	reset --hard
        .. do yet another merge ..
        conlict_count count_three

	test "$count_three" = "$count_two"

At any point, you do not do numerical comparison, and I do not think extra
whitespace from other "wc" implementations matter, as long as they are
consistent.

If you are going to do numerical comparison in later versions, you can
just drop the dq around parameters of test:

	test $count_three = $count_two

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 14/14] Build in merge
  2008-07-01 13:18                                                     ` Miklos Vajna
  2008-07-01 23:55                                                       ` Junio C Hamano
@ 2008-07-06  8:50                                                       ` Junio C Hamano
  2008-07-06  9:43                                                         ` Junio C Hamano
                                                                           ` (2 more replies)
  1 sibling, 3 replies; 81+ messages in thread
From: Junio C Hamano @ 2008-07-06  8:50 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: git, Johannes Schindelin, Olivier Marin

Miklos Vajna <vmiklos@frugalware.org> writes:

> diff --git a/builtin-merge.c b/builtin-merge.c
> new file mode 100644
> index 0000000..b261993
> --- /dev/null
> +++ b/builtin-merge.c
> @@ -0,0 +1,1158 @@
> +/*
> + * Builtin "git merge"
> + *
> + * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
> + *
> + * Based on git-merge.sh by Junio C Hamano.
> + */
> +
> +#include "cache.h"
> +#include "parse-options.h"
> +#include "builtin.h"
> +#include "run-command.h"
> +#include "path-list.h"
> +#include "diff.h"
> +#include "refs.h"
> +#include "commit.h"
> +#include "diffcore.h"
> +#include "revision.h"
> +#include "unpack-trees.h"
> +#include "cache-tree.h"
> +#include "dir.h"
> +#include "utf8.h"
> +#include "log-tree.h"
> +#include "color.h"
> +
> +enum strategy {
> +	DEFAULT_TWOHEAD = 1,
> +	DEFAULT_OCTOPUS = 2,
> +	NO_FAST_FORWARD = 4,
> +	NO_TRIVIAL = 8
> +};

Usually "enum foo" consists of possible values of "foo".  But this is
not a list of strategies.  These are possible attributes to strategies.

> +static const char * const builtin_merge_usage[] = {
> +	"git-merge [options] <remote>...",
> +	"git-merge [options] <msg> HEAD <remote>",
> +	NULL
> +};
> +
> +static int show_diffstat = 1, option_log, squash;
> +static int option_commit = 1, allow_fast_forward = 1;
> +static int allow_trivial = 1, have_message;
> +static struct strbuf merge_msg;
> +static struct commit_list *remoteheads;
> +static unsigned char head[20], stash[20];
> +static struct path_list use_strategies;
> +static const char *branch;
> +
> +static struct path_list_item strategy_items[] = {
> +	{ "recur",      (void *)NO_TRIVIAL },
> +	{ "recursive",  (void *)(DEFAULT_TWOHEAD | NO_TRIVIAL) },
> +	{ "octopus",    (void *)DEFAULT_OCTOPUS },
> +	{ "resolve",    (void *)0 },
> +	{ "stupid",     (void *)0 },
> +	{ "ours",       (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
> +	{ "subtree",    (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
> +};
> +static struct path_list strategies = { strategy_items,
> +	ARRAY_SIZE(strategy_items), 0, 0 };

This declaration is funnily line-wrapped.

        static struct path_list strategies = {
                strategy_items, ARRAY_SIZE(strategy_items), 0, 0,
        };

But more problematic is that a path_list is inherently a dynamic data
structure (you can add and it reallocs), and this use of relying on the
knowledge that you happen to never add anything (nor subtract anything)
from the list is a mere hack.  If on the other hand you (and more
importantly other people who touch this implementation later) will never
add or remove items from this "strategies" array, you should make sure at
the interface level that nobody can -- one way to do so is not to abuse
path_list for something like this.

Come to think of it, wasn't the reason why the earlier "Why do you need
such distracting casts all over the place?" issue came up in another patch
because of this kind of (ab)use of path_list, which is an inappropriate
data structure for the job?

You would perhaps define:

	#define DEFAULT_TWOHEAD (1<<0)
	#define DEFAULT_OCTOPUS (1<<1)
	#define NO_FAST_FORWARD (1<<2)
	#define NO_TRIVIAL	(1<<3)

	static struct strategy {
        	char *name;
                unsigned attr;
	} all_strategy[] = {
                { "octopus",    DEFAULT_OCTOPUS },
                { "ours",       (NO_FAST_FORWARD | NO_TRIVIAL) },
                { "recur",      NO_TRIVIAL },
                { "recursive",  (DEFAULT_TWOHEAD | NO_TRIVIAL) },
                { "resolve",    0 },
                { "stupid",     0 },
                { "subtree",    (NO_FAST_FORWARD | NO_TRIVIAL) },
        };

And "unsorted_path_list_lookup()" can now become much more natural,
perhaps:

	static struct strategy *get_strategy(const char *name);

which has a more natural function signature and much better name.

Then, you would keep an array of pointers into all_strategy[] array to
represent the list of "-s strategy" given by the user:

	static struct strategy *use_strategy;
	static int use_strategy_alloc, use_strategy_nr;

and have a function that use s the standard ALLOC_GROW() and friends to
grow this.  The function will be named and written more naturally
(i.e. path_list_append_strategy() can go) --- this does not have anything
to do with path_list, but it is about "merge strategy".

But 99.9% of the time you would not have more than one elements ;-).

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 14/14] Build in merge
  2008-07-06  8:50                                                       ` Junio C Hamano
@ 2008-07-06  9:43                                                         ` Junio C Hamano
  2008-07-07 17:17                                                           ` Miklos Vajna
  2008-07-06 12:38                                                         ` [PATCH 14/14] " Johannes Schindelin
  2008-07-07 17:24                                                         ` [PATCH] " Miklos Vajna
  2 siblings, 1 reply; 81+ messages in thread
From: Junio C Hamano @ 2008-07-06  9:43 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: git, Johannes Schindelin, Olivier Marin

Junio C Hamano <gitster@pobox.com> writes:

> You would perhaps define:
>
> 	#define DEFAULT_TWOHEAD (1<<0)
> 	#define DEFAULT_OCTOPUS (1<<1)
> 	#define NO_FAST_FORWARD (1<<2)
> 	#define NO_TRIVIAL	(1<<3)
>
> 	static struct strategy {
>         	char *name;
>                 unsigned attr;
> 	} all_strategy[] = {
>                 { "octopus",    DEFAULT_OCTOPUS },
>                 { "ours",       (NO_FAST_FORWARD | NO_TRIVIAL) },
>                 { "recur",      NO_TRIVIAL },
>                 { "recursive",  (DEFAULT_TWOHEAD | NO_TRIVIAL) },
>                 { "resolve",    0 },
>                 { "stupid",     0 },
>                 { "subtree",    (NO_FAST_FORWARD | NO_TRIVIAL) },
>         };
>
> And "unsorted_path_list_lookup()" can now become much more natural,
> perhaps:
>
> 	static struct strategy *get_strategy(const char *name);
>
> which has a more natural function signature and much better name.
>
> Then, you would keep an array of pointers into all_strategy[] array to
> represent the list of "-s strategy" given by the user:
>
> 	static struct strategy *use_strategy;
> 	static int use_strategy_alloc, use_strategy_nr;

Sorry, I have an obvious typo here.  "use_strategy" will be dynamic array
of pointers into all_strategy[] so its definition would be:

	static struct strategy **use_strategy;

> and have a function that use s the standard ALLOC_GROW() and friends to
> grow this.  The function will be named and written more naturally
> (i.e. path_list_append_strategy() can go) --- this does not have anything
> to do with path_list, but it is about "merge strategy".

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 14/14] Build in merge
  2008-07-06  8:50                                                       ` Junio C Hamano
  2008-07-06  9:43                                                         ` Junio C Hamano
@ 2008-07-06 12:38                                                         ` Johannes Schindelin
  2008-07-06 19:39                                                           ` Junio C Hamano
  2008-07-07 17:24                                                         ` [PATCH] " Miklos Vajna
  2 siblings, 1 reply; 81+ messages in thread
From: Johannes Schindelin @ 2008-07-06 12:38 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Miklos Vajna, git, Olivier Marin

Hi,

On Sun, 6 Jul 2008, Junio C Hamano wrote:

> Miklos Vajna <vmiklos@frugalware.org> writes:
> 
> > diff --git a/builtin-merge.c b/builtin-merge.c
> > new file mode 100644
> > index 0000000..b261993
> > --- /dev/null
> > +++ b/builtin-merge.c
> > @@ -0,0 +1,1158 @@
> > +/*
> > + * Builtin "git merge"
> > + *
> > + * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
> > + *
> > + * Based on git-merge.sh by Junio C Hamano.
> > + */
> > +
> > +#include "cache.h"
> > +#include "parse-options.h"
> > +#include "builtin.h"
> > +#include "run-command.h"
> > +#include "path-list.h"
> > +#include "diff.h"
> > +#include "refs.h"
> > +#include "commit.h"
> > +#include "diffcore.h"
> > +#include "revision.h"
> > +#include "unpack-trees.h"
> > +#include "cache-tree.h"
> > +#include "dir.h"
> > +#include "utf8.h"
> > +#include "log-tree.h"
> > +#include "color.h"
> > +
> > +enum strategy {
> > +	DEFAULT_TWOHEAD = 1,
> > +	DEFAULT_OCTOPUS = 2,
> > +	NO_FAST_FORWARD = 4,
> > +	NO_TRIVIAL = 8
> > +};
> 
> Usually "enum foo" consists of possible values of "foo".  But this is
> not a list of strategies.  These are possible attributes to strategies.

My fault.  I avoid ugly #defines, so I suggested using an enum.

> > +static const char * const builtin_merge_usage[] = {
> > +	"git-merge [options] <remote>...",
> > +	"git-merge [options] <msg> HEAD <remote>",
> > +	NULL
> > +};
> > +
> > +static int show_diffstat = 1, option_log, squash;
> > +static int option_commit = 1, allow_fast_forward = 1;
> > +static int allow_trivial = 1, have_message;
> > +static struct strbuf merge_msg;
> > +static struct commit_list *remoteheads;
> > +static unsigned char head[20], stash[20];
> > +static struct path_list use_strategies;
> > +static const char *branch;
> > +
> > +static struct path_list_item strategy_items[] = {
> > +	{ "recur",      (void *)NO_TRIVIAL },
> > +	{ "recursive",  (void *)(DEFAULT_TWOHEAD | NO_TRIVIAL) },
> > +	{ "octopus",    (void *)DEFAULT_OCTOPUS },
> > +	{ "resolve",    (void *)0 },
> > +	{ "stupid",     (void *)0 },
> > +	{ "ours",       (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
> > +	{ "subtree",    (void *)(NO_FAST_FORWARD | NO_TRIVIAL) },
> > +};
> > +static struct path_list strategies = { strategy_items,
> > +	ARRAY_SIZE(strategy_items), 0, 0 };
> 
> This declaration is funnily line-wrapped.
> 
>         static struct path_list strategies = {
>                 strategy_items, ARRAY_SIZE(strategy_items), 0, 0,
>         };
> 
> But more problematic is that a path_list is inherently a dynamic data 
> structure (you can add and it reallocs), and this use of relying on the 
> knowledge that you happen to never add anything (nor subtract anything) 
> from the list is a mere hack.  If on the other hand you (and more 
> importantly other people who touch this implementation later) will never 
> add or remove items from this "strategies" array, you should make sure 
> at the interface level that nobody can -- one way to do so is not to 
> abuse path_list for something like this.
> 
> Come to think of it, wasn't the reason why the earlier "Why do you need 
> such distracting casts all over the place?" issue came up in another 
> patch because of this kind of (ab)use of path_list, which is an 
> inappropriate data structure for the job?

Well, it is not really an abuse if you think of path_list as a 
string_list, which it really is, and which should have happened a long 
time ago, but I gave up.

Anyway, the use of string_list here was originally to make lookup slightly 
more convenient, using an API, instead of reinventing the wheel over and 
over and over again, as can be seen nicely in some parts of Git's source 
code.

Given that we want to be able to add other strategies, I would have rather 
suggested fixing string_list to heed "alloc == 0" and _not_ realloc() in 
that case.

But given that you seem so sick and tired of string_list, and rather have 
a code duplication, I will not argue to that end anymore.

Tired,
Dscho

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 14/14] Build in merge
  2008-07-06 12:38                                                         ` [PATCH 14/14] " Johannes Schindelin
@ 2008-07-06 19:39                                                           ` Junio C Hamano
  0 siblings, 0 replies; 81+ messages in thread
From: Junio C Hamano @ 2008-07-06 19:39 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Miklos Vajna, git, Olivier Marin

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> But given that you seem so sick and tired of string_list, and rather have 
> a code duplication, I will not argue to that end anymore.

You are probably very confused if you think I am saying I'd rather have
duplication.

Look at "unsorted_path_list_lookup()" in builtin-merge.c and think again.
Look at "path_list_append_strategy()" in builtin-merge.c and think again.

If you are adding the same amount of code _anyway_, why not write using
more appropriate data structure for the job?

The path_list has its uses.  It's wonderful when you have existing
structures that you would need to keep in core anyway and being able to
look them up via string keys.  But that does not mean it is (nor should be
"improved" to be) a good match for other forms of (ab)uses.

The way it was used by initializing with pointer to a static array
location is clearly a misuse.  When using ->util field to store things
other than pointers to preexisting structures, the use of the API becomes
clunky and we discussed this issue about another patch.  That's all I was
saying.

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 14/14] Build in merge
  2008-07-06  9:43                                                         ` Junio C Hamano
@ 2008-07-07 17:17                                                           ` Miklos Vajna
  2008-07-07 18:15                                                             ` Junio C Hamano
  0 siblings, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-07-07 17:17 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

[-- Attachment #1: Type: text/plain, Size: 1193 bytes --]

On Sun, Jul 06, 2008 at 02:43:41AM -0700, Junio C Hamano <gitster@pobox.com> wrote:
> > Then, you would keep an array of pointers into all_strategy[] array to
> > represent the list of "-s strategy" given by the user:
> >
> > 	static struct strategy *use_strategy;
> > 	static int use_strategy_alloc, use_strategy_nr;
> 
> Sorry, I have an obvious typo here.  "use_strategy" will be dynamic array
> of pointers into all_strategy[] so its definition would be:
> 
> 	static struct strategy **use_strategy;

I think there are two possibilities here:

1) Append custom strategies to all_strategy and have only pointers in
use_strategy.

This is what you suggest in your second mail.

2) Copy the names and attributes from all_strategy to use_strategies and
append custom strategies there.

This is what I do at the moment.

I think it's better not to modify all_strategy, it serves as a
reference, for example later this would allow us to check if the used
merge strategy is a predefined or a custom one.

Or is there any strong reason introducing all_strategy_alloc,
all_strategy_nr and using ALLOC_GROW() with all_strategy instead of with
use_strategy?

Thanks.

[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH] Build in merge
  2008-07-06  8:50                                                       ` Junio C Hamano
  2008-07-06  9:43                                                         ` Junio C Hamano
  2008-07-06 12:38                                                         ` [PATCH 14/14] " Johannes Schindelin
@ 2008-07-07 17:24                                                         ` Miklos Vajna
  2008-07-07 17:35                                                           ` Miklos Vajna
  2 siblings, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-07-07 17:24 UTC (permalink / raw)
  To: To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---

On Sun, Jul 06, 2008 at 01:50:05AM -0700, Junio C Hamano <gitster@pobox.com> wrote:
> But more problematic is that a path_list is inherently a dynamic data
> structure (you can add and it reallocs), and this use of relying on
> the
> knowledge that you happen to never add anything (nor subtract
> anything)
> from the list is a mere hack.  If on the other hand you (and more
> importantly other people who touch this implementation later) will
> never
> add or remove items from this "strategies" array, you should make sure
> at
> the interface level that nobody can -- one way to do so is not to
> abuse
> path_list for something like this.
>
> Come to think of it, wasn't the reason why the earlier "Why do you
> need
> such distracting casts all over the place?" issue came up in another
> patch
> because of this kind of (ab)use of path_list, which is an
> inappropriate
> data structure for the job?

Here is an updated version without using path_list at all.

 Makefile                                      |    2 +-
 builtin-merge.c                               | 1153 +++++++++++++++++++++++++
 builtin.h                                     |    1 +
 git-merge.sh => contrib/examples/git-merge.sh |    0
 git.c                                         |    1 +
 t/t7602-merge-octopus-many.sh                 |    2 +-
 6 files changed, 1157 insertions(+), 2 deletions(-)
 create mode 100644 builtin-merge.c
 rename git-merge.sh => contrib/examples/git-merge.sh (100%)

diff --git a/Makefile b/Makefile
index bf77292..fbc53e9 100644
--- a/Makefile
+++ b/Makefile
@@ -240,7 +240,6 @@ SCRIPT_SH += git-lost-found.sh
 SCRIPT_SH += git-merge-octopus.sh
 SCRIPT_SH += git-merge-one-file.sh
 SCRIPT_SH += git-merge-resolve.sh
-SCRIPT_SH += git-merge.sh
 SCRIPT_SH += git-merge-stupid.sh
 SCRIPT_SH += git-mergetool.sh
 SCRIPT_SH += git-parse-remote.sh
@@ -515,6 +514,7 @@ BUILTIN_OBJS += builtin-ls-remote.o
 BUILTIN_OBJS += builtin-ls-tree.o
 BUILTIN_OBJS += builtin-mailinfo.o
 BUILTIN_OBJS += builtin-mailsplit.o
+BUILTIN_OBJS += builtin-merge.o
 BUILTIN_OBJS += builtin-merge-base.o
 BUILTIN_OBJS += builtin-merge-file.o
 BUILTIN_OBJS += builtin-merge-ours.o
diff --git a/builtin-merge.c b/builtin-merge.c
new file mode 100644
index 0000000..7312997
--- /dev/null
+++ b/builtin-merge.c
@@ -0,0 +1,1153 @@
+/*
+ * Builtin "git merge"
+ *
+ * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
+ *
+ * Based on git-merge.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "parse-options.h"
+#include "builtin.h"
+#include "run-command.h"
+#include "diff.h"
+#include "refs.h"
+#include "commit.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
+#include "dir.h"
+#include "utf8.h"
+#include "log-tree.h"
+#include "color.h"
+
+#define DEFAULT_TWOHEAD (1<<0)
+#define DEFAULT_OCTOPUS (1<<1)
+#define NO_FAST_FORWARD (1<<2)
+#define NO_TRIVIAL      (1<<3)
+
+struct strategy {
+	const char *name;
+	unsigned attr;
+};
+
+static const char * const builtin_merge_usage[] = {
+	"git-merge [options] <remote>...",
+	"git-merge [options] <msg> HEAD <remote>",
+	NULL
+};
+
+static int show_diffstat = 1, option_log, squash;
+static int option_commit = 1, allow_fast_forward = 1;
+static int allow_trivial = 1, have_message;
+static struct strbuf merge_msg;
+static struct commit_list *remoteheads;
+static unsigned char head[20], stash[20];
+static struct strategy *use_strategies;
+static size_t use_strategies_nr, use_strategies_alloc;
+static const char *branch;
+
+static struct strategy all_strategy[] = {
+	{ "recur",      NO_TRIVIAL },
+	{ "recursive",  DEFAULT_TWOHEAD | NO_TRIVIAL },
+	{ "octopus",    DEFAULT_OCTOPUS },
+	{ "resolve",    0 },
+	{ "stupid",     0 },
+	{ "ours",       NO_FAST_FORWARD | NO_TRIVIAL },
+	{ "subtree",    NO_FAST_FORWARD | NO_TRIVIAL },
+};
+
+static const char *pull_twohead, *pull_octopus;
+
+static int option_parse_message(const struct option *opt,
+	const char *arg, int unset)
+{
+	struct strbuf *buf = opt->value;
+
+	if (unset)
+		strbuf_setlen(buf, 0);
+	else {
+		strbuf_addf(buf, "%s\n\n", arg);
+		have_message = 1;
+	}
+	return 0;
+}
+
+static int strategy_lookup(const char *path)
+{
+	int i;
+
+	if (!path)
+		return -1;
+
+	for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+		if (!strcmp(path, all_strategy[i].name))
+			return all_strategy[i].attr;
+	return -1;
+}
+
+static inline void append_strategy(const char *name, unsigned attr)
+{
+	ALLOC_GROW(use_strategies, use_strategies_nr + 1, use_strategies_alloc);
+	use_strategies[use_strategies_nr].name = name;
+	use_strategies[use_strategies_nr++].attr = attr;
+}
+
+static int option_parse_strategy(const struct option *opt,
+	const char *arg, int unset)
+{
+	int i, attr;
+
+	if (unset)
+		return 0;
+
+	attr = strategy_lookup(arg);
+
+	if (attr >= 0)
+		append_strategy(arg, attr);
+	else {
+		struct strbuf err;
+		strbuf_init(&err, 0);
+		for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+			strbuf_addf(&err, " %s", all_strategy[i].name);
+		fprintf(stderr, "Could not find merge strategy '%s'.\n", arg);
+		fprintf(stderr, "Available strategies are:%s.\n", err.buf);
+		exit(1);
+	}
+	return 0;
+}
+
+static int option_parse_n(const struct option *opt,
+		const char *arg, int unset)
+{
+	show_diffstat = unset;
+	return 0;
+}
+
+static struct option builtin_merge_options[] = {
+	{ OPTION_CALLBACK, 'n', NULL, NULL, NULL,
+		"do not show a diffstat at the end of the merge",
+		PARSE_OPT_NOARG, option_parse_n },
+	OPT_BOOLEAN(0, "stat", &show_diffstat,
+		"show a diffstat at the end of the merge"),
+	OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
+	OPT_BOOLEAN(0, "log", &option_log,
+		"add list of one-line log to merge commit message"),
+	OPT_BOOLEAN(0, "squash", &squash,
+		"create a single commit instead of doing a merge"),
+	OPT_BOOLEAN(0, "commit", &option_commit,
+		"perform a commit if the merge succeeds (default)"),
+	OPT_BOOLEAN(0, "ff", &allow_fast_forward,
+		"allow fast forward (default)"),
+	OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
+		"merge strategy to use", option_parse_strategy),
+	OPT_CALLBACK('m', "message", &merge_msg, "message",
+		"message to be used for the merge commit (if any)",
+		option_parse_message),
+	OPT_END()
+};
+
+/* Cleans up metadata that is uninteresting after a succeeded merge. */
+static void drop_save(void)
+{
+	unlink(git_path("MERGE_HEAD"));
+	unlink(git_path("MERGE_MSG"));
+}
+
+static void save_state(void)
+{
+	int len;
+	struct child_process cp;
+	struct strbuf buffer = STRBUF_INIT;
+	const char *argv[] = {"stash", "create", NULL};
+
+	memset(&cp, 0, sizeof(cp));
+	cp.argv = argv;
+	cp.out = -1;
+	cp.git_cmd = 1;
+
+	if (start_command(&cp))
+		die("could not run stash.");
+	len = strbuf_read(&buffer, cp.out, 1024);
+	close(cp.out);
+
+	if (finish_command(&cp) || len < 0)
+		die("stash failed");
+	else if (!len)
+		return;
+	strbuf_setlen(&buffer, buffer.len-1);
+	if (get_sha1(buffer.buf, stash))
+		die("not a valid object: %s", buffer.buf);
+}
+
+static void reset_hard(unsigned const char *sha1, int verbose)
+{
+	int i = 0;
+	const char *args[6];
+
+	args[i++] = "read-tree";
+	if (verbose)
+		args[i++] = "-v";
+	args[i++] = "--reset";
+	args[i++] = "-u";
+	args[i++] = sha1_to_hex(sha1);
+	args[i] = NULL;
+
+	if (run_command_v_opt(args, RUN_GIT_CMD))
+		die("read-tree failed");
+}
+
+static void restore_state(void)
+{
+	struct strbuf sb;
+	const char *args[] = { "stash", "apply", NULL, NULL };
+
+	if (is_null_sha1(stash))
+		return;
+
+	reset_hard(head, 1);
+
+	strbuf_init(&sb, 0);
+	args[2] = sha1_to_hex(stash);
+
+	/*
+	 * It is OK to ignore error here, for example when there was
+	 * nothing to restore.
+	 */
+	run_command_v_opt(args, RUN_GIT_CMD);
+
+	strbuf_release(&sb);
+	refresh_cache(REFRESH_QUIET);
+}
+
+/* This is called when no merge was necessary. */
+static void finish_up_to_date(const char *msg)
+{
+	printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
+	drop_save();
+}
+
+static void squash_message(void)
+{
+	struct rev_info rev;
+	struct commit *commit;
+	struct strbuf out;
+	struct commit_list *j;
+	int fd;
+
+	printf("Squash commit -- not updating HEAD\n");
+	fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+	if (fd < 0)
+		die("Could not write to %s", git_path("SQUASH_MSG"));
+
+	init_revisions(&rev, NULL);
+	rev.ignore_merges = 1;
+	rev.commit_format = CMIT_FMT_MEDIUM;
+
+	commit = lookup_commit(head);
+	commit->object.flags |= UNINTERESTING;
+	add_pending_object(&rev, &commit->object, NULL);
+
+	for (j = remoteheads; j; j = j->next)
+		add_pending_object(&rev, &j->item->object, NULL);
+
+	setup_revisions(0, NULL, &rev, NULL);
+	if (prepare_revision_walk(&rev))
+		die("revision walk setup failed");
+
+	strbuf_init(&out, 0);
+	strbuf_addstr(&out, "Squashed commit of the following:\n");
+	while ((commit = get_revision(&rev)) != NULL) {
+		strbuf_addch(&out, '\n');
+		strbuf_addf(&out, "commit %s\n",
+			sha1_to_hex(commit->object.sha1));
+		pretty_print_commit(rev.commit_format, commit, &out, rev.abbrev,
+			NULL, NULL, rev.date_mode, 0);
+	}
+	write(fd, out.buf, out.len);
+	close(fd);
+	strbuf_release(&out);
+}
+
+static int run_hook(const char *name)
+{
+	struct child_process hook;
+	const char *argv[3], *env[2];
+	char index[PATH_MAX];
+
+	argv[0] = git_path("hooks/%s", name);
+	if (access(argv[0], X_OK) < 0)
+		return 0;
+
+	snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", get_index_file());
+	env[0] = index;
+	env[1] = NULL;
+
+	if (squash)
+		argv[1] = "1";
+	else
+		argv[1] = "0";
+	argv[2] = NULL;
+
+	memset(&hook, 0, sizeof(hook));
+	hook.argv = argv;
+	hook.no_stdin = 1;
+	hook.stdout_to_stderr = 1;
+	hook.env = env;
+
+	return run_command(&hook);
+}
+
+static void finish(const unsigned char *new_head, const char *msg)
+{
+	struct strbuf reflog_message;
+
+	strbuf_init(&reflog_message, 0);
+	if (!msg)
+		strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
+	else {
+		printf("%s\n", msg);
+		strbuf_addf(&reflog_message, "%s: %s",
+			getenv("GIT_REFLOG_ACTION"), msg);
+	}
+	if (squash) {
+		squash_message();
+	} else {
+		if (!merge_msg.len)
+			printf("No merge message -- not updating HEAD\n");
+		else {
+			const char *argv_gc_auto[] = { "gc", "--auto", NULL };
+			update_ref(reflog_message.buf, "HEAD",
+				new_head, head, 0,
+				DIE_ON_ERR);
+			/*
+			 * We ignore errors in 'gc --auto', since the
+			 * user should see them.
+			 */
+			run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+		}
+	}
+	if (new_head && show_diffstat) {
+		struct diff_options opts;
+		diff_setup(&opts);
+		opts.output_format |=
+			DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+		opts.detect_rename = DIFF_DETECT_RENAME;
+		if (diff_use_color_default > 0)
+			DIFF_OPT_SET(&opts, COLOR_DIFF);
+		if (diff_setup_done(&opts) < 0)
+			die("diff_setup_done failed");
+		diff_tree_sha1(head, new_head, "", &opts);
+		diffcore_std(&opts);
+		diff_flush(&opts);
+	}
+
+	/* Run a post-merge hook */
+	run_hook("post-merge");
+
+	strbuf_release(&reflog_message);
+}
+
+/* Get the name for the merge commit's message. */
+static void merge_name(const char *remote, struct strbuf *msg)
+{
+	struct object *remote_head;
+	unsigned char branch_head[20], buf_sha[20];
+	struct strbuf buf;
+	const char *ptr;
+	int len, early;
+
+	memset(branch_head, 0, sizeof(branch_head));
+	remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+	if (!remote_head)
+		die("'%s' does not point to a commit", remote);
+
+	strbuf_init(&buf, 0);
+	strbuf_addstr(&buf, "refs/heads/");
+	strbuf_addstr(&buf, remote);
+	resolve_ref(buf.buf, branch_head, 0, 0);
+
+	if (!hashcmp(remote_head->sha1, branch_head)) {
+		strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
+			sha1_to_hex(branch_head), remote);
+		return;
+	}
+
+	/* See if remote matches <name>^^^.. or <name>~<number> */
+	for (len = 0, ptr = remote + strlen(remote);
+	     remote < ptr && ptr[-1] == '^';
+	     ptr--)
+		len++;
+	if (len)
+		early = 1;
+	else {
+		early = 0;
+		ptr = strrchr(remote, '~');
+		if (ptr) {
+			int seen_nonzero = 0;
+
+			len++; /* count ~ */
+			while (*++ptr && isdigit(*ptr)) {
+				seen_nonzero |= (*ptr != '0');
+				len++;
+			}
+			if (*ptr)
+				len = 0; /* not ...~<number> */
+			else if (seen_nonzero)
+				early = 1;
+			else if (len == 1)
+				early = 1; /* "name~" is "name~1"! */
+		}
+	}
+	if (len) {
+		struct strbuf truname = STRBUF_INIT;
+		strbuf_addstr(&truname, "refs/heads/");
+		strbuf_addstr(&truname, remote);
+		strbuf_setlen(&truname, len+11);
+		if (resolve_ref(truname.buf, buf_sha, 0, 0)) {
+			strbuf_addf(msg,
+				    "%s\t\tbranch '%s'%s of .\n",
+				    sha1_to_hex(remote_head->sha1),
+				    truname.buf,
+				    (early ? " (early part)" : ""));
+			return;
+		}
+	}
+
+	if (!strcmp(remote, "FETCH_HEAD") &&
+			!access(git_path("FETCH_HEAD"), R_OK)) {
+		FILE *fp;
+		struct strbuf line;
+		char *ptr;
+
+		strbuf_init(&line, 0);
+		fp = fopen(git_path("FETCH_HEAD"), "r");
+		if (!fp)
+			die("could not open %s for reading: %s",
+				git_path("FETCH_HEAD"), strerror(errno));
+		strbuf_getline(&line, fp, '\n');
+		fclose(fp);
+		ptr = strstr(line.buf, "\tnot-for-merge\t");
+		if (ptr)
+			strbuf_remove(&line, ptr-line.buf+1, 13);
+		strbuf_addbuf(msg, &line);
+		strbuf_release(&line);
+		return;
+	}
+	strbuf_addf(msg, "%s\t\tcommit '%s'\n",
+		sha1_to_hex(remote_head->sha1), remote);
+}
+
+int git_merge_config(const char *k, const char *v, void *cb)
+{
+	if (branch && !prefixcmp(k, "branch.") &&
+		!prefixcmp(k + 7, branch) &&
+		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+		const char **argv;
+		int argc;
+		char *buf;
+
+		buf = xstrdup(v);
+		argc = split_cmdline(buf, &argv);
+		argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+		memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+		argc++;
+		parse_options(argc, argv, builtin_merge_options,
+			      builtin_merge_usage, 0);
+		free(buf);
+	}
+
+	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+		show_diffstat = git_config_bool(k, v);
+	else if (!strcmp(k, "pull.twohead"))
+		return git_config_string(&pull_twohead, k, v);
+	else if (!strcmp(k, "pull.octopus"))
+		return git_config_string(&pull_octopus, k, v);
+	return git_diff_ui_config(k, v, cb);
+}
+
+static int read_tree_trivial(unsigned char *common, unsigned char *head,
+	unsigned char *one)
+{
+	int i, nr_trees = 0;
+	struct tree *trees[MAX_UNPACK_TREES];
+	struct tree_desc t[MAX_UNPACK_TREES];
+	struct unpack_trees_options opts;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.head_idx = 2;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.verbose_update = 1;
+	opts.trivial_merges_only = 1;
+	opts.merge = 1;
+	trees[nr_trees] = parse_tree_indirect(common);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(head);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(one);
+	if (!trees[nr_trees++])
+		return -1;
+	opts.fn = threeway_merge;
+	cache_tree_free(&active_cache_tree);
+	for (i = 0; i < nr_trees; i++) {
+		parse_tree(trees[i]);
+		init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+	}
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+	return 0;
+}
+
+static void write_tree_trivial(unsigned char *sha1)
+{
+	if (write_cache_as_tree(sha1, 0, NULL))
+		die("git write-tree failed to write a tree");
+}
+
+static int try_merge_strategy(const char *strategy, struct commit_list *common,
+	const char *head_arg)
+{
+	const char **args;
+	int i = 0, ret;
+	struct commit_list *j;
+	struct strbuf buf;
+
+	args = xmalloc((4 + commit_list_count(common) +
+			commit_list_count(remoteheads)) * sizeof(char *));
+	strbuf_init(&buf, 0);
+	strbuf_addf(&buf, "merge-%s", strategy);
+	args[i++] = buf.buf;
+	for (j = common; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i++] = "--";
+	args[i++] = head_arg;
+	for (j = remoteheads; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i] = NULL;
+	ret = run_command_v_opt(args, RUN_GIT_CMD);
+	strbuf_release(&buf);
+	i = 1;
+	for (j = common; j; j = j->next)
+		free((void *)args[i++]);
+	i += 2;
+	for (j = remoteheads; j; j = j->next)
+		free((void *)args[i++]);
+	free(args);
+	return -ret;
+}
+
+static void count_diff_files(struct diff_queue_struct *q,
+		struct diff_options *opt, void *data)
+{
+	int *count = data;
+
+	(*count) += q->nr;
+}
+
+static int count_unmerged_entries(void)
+{
+	const struct index_state *state = &the_index;
+	int i, ret = 0;
+
+	for (i = 0; i < state->cache_nr; i++)
+		if (ce_stage(state->cache[i]))
+			ret++;
+
+	return ret;
+}
+
+static int checkout_fast_forward(unsigned char *head, unsigned char *remote)
+{
+	struct tree *trees[MAX_UNPACK_TREES];
+	struct unpack_trees_options opts;
+	struct tree_desc t[MAX_UNPACK_TREES];
+	int i, fd, nr_trees = 0;
+	struct dir_struct dir;
+	struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+	if (read_cache_unmerged())
+		die("you need to resolve your current index first");
+
+	fd = hold_locked_index(lock_file, 1);
+
+	memset(&trees, 0, sizeof(trees));
+	memset(&opts, 0, sizeof(opts));
+	memset(&t, 0, sizeof(t));
+	dir.show_ignored = 1;
+	dir.exclude_per_dir = ".gitignore";
+	opts.dir = &dir;
+
+	opts.head_idx = 1;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.verbose_update = 1;
+	opts.merge = 1;
+	opts.fn = twoway_merge;
+
+	trees[nr_trees] = parse_tree_indirect(head);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(remote);
+	if (!trees[nr_trees++])
+		return -1;
+	for (i = 0; i < nr_trees; i++) {
+		parse_tree(trees[i]);
+		init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+	}
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+	if (write_cache(fd, active_cache, active_nr) ||
+		commit_locked_index(lock_file))
+		die("unable to write new index file");
+	return 0;
+}
+
+static void split_merge_strategies(const char *string, struct strategy **list,
+		int *nr, int *alloc)
+{
+	char *p, *q, *buf;
+
+	if (!string)
+		return;
+
+	buf = xstrdup(string);
+	q = buf;
+	for (;;) {
+		p = strchr(q, ' ');
+		if (!p) {
+			ALLOC_GROW(*list, *nr + 1, *alloc);
+			(*list)[(*nr)++].name = xstrdup(q);
+			free(buf);
+			return;
+		} else {
+			*p = '\0';
+			ALLOC_GROW(*list, *nr + 1, *alloc);
+			(*list)[(*nr)++].name = xstrdup(q);
+			q = ++p;
+		}
+	}
+}
+
+static void add_strategies(const char *string, unsigned attr)
+{
+	struct strategy *list = NULL;
+	int list_alloc = 0, list_nr = 0, i;
+
+	memset(&list, 0, sizeof(list));
+	split_merge_strategies(string, &list, &list_nr, &list_alloc);
+	if (list != NULL) {
+		for (i = 0; i < list_nr; i++) {
+			int attr;
+
+			attr = strategy_lookup(list[i].name);
+			if (attr >= 0)
+				append_strategy(list[i].name, attr);
+		}
+		return;
+	}
+	for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+		if (all_strategy[i].attr & attr)
+			append_strategy(all_strategy[i].name,
+				all_strategy[i].attr);
+}
+
+static int merge_trivial(void)
+{
+	unsigned char result_tree[20], result_commit[20];
+	struct commit_list parent;
+
+	write_tree_trivial(result_tree);
+	printf("Wonderful.\n");
+	parent.item = remoteheads->item;
+	parent.next = NULL;
+	commit_tree(merge_msg.buf, result_tree, &parent, result_commit);
+	finish(result_commit, "In-index merge");
+	drop_save();
+	return 0;
+}
+
+static int finish_automerge(struct commit_list *common,
+	unsigned char *result_tree,
+	const char *wt_strategy)
+{
+	struct commit_list *parents = NULL, *j;
+	struct strbuf buf = STRBUF_INIT;
+	unsigned char result_commit[20];
+
+	free_commit_list(common);
+	if (allow_fast_forward) {
+		parents = remoteheads;
+		commit_list_insert(lookup_commit(head), &parents);
+		parents = reduce_heads(parents);
+	} else {
+		struct commit_list **pptr = &parents;
+
+		pptr = &commit_list_insert(lookup_commit(head),
+				pptr)->next;
+		for (j = remoteheads; j; j = j->next)
+			pptr = &commit_list_insert(j->item, pptr)->next;
+	}
+	free_commit_list(remoteheads);
+	strbuf_addch(&merge_msg, '\n');
+	commit_tree(merge_msg.buf, result_tree, parents, result_commit);
+	strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
+	finish(result_commit, buf.buf);
+	strbuf_release(&buf);
+	drop_save();
+	return 0;
+}
+
+static int suggest_conflicts(void)
+{
+	FILE *fp;
+	int pos;
+
+	fp = fopen(git_path("MERGE_MSG"), "a");
+	if (!fp)
+		die("Could open %s for writing", git_path("MERGE_MSG"));
+	fprintf(fp, "\nConflicts:\n");
+	for (pos = 0; pos < active_nr; pos++) {
+		struct cache_entry *ce = active_cache[pos];
+
+		if (ce_stage(ce)) {
+			fprintf(fp, "\t%s\n", ce->name);
+			while (pos + 1 < active_nr &&
+					!strcmp(ce->name,
+						active_cache[pos + 1]->name))
+				pos++;
+		}
+	}
+	fclose(fp);
+	rerere();
+	printf("Automatic merge failed; "
+			"fix conflicts and then commit the result.\n");
+	return 1;
+}
+
+static struct commit *is_old_style_invocation(int argc, const char **argv)
+{
+	struct commit *second_token = NULL;
+	if (argc > 1) {
+		unsigned char second_sha1[20];
+
+		if (get_sha1(argv[1], second_sha1))
+			return NULL;
+		second_token = lookup_commit_reference_gently(second_sha1, 0);
+		if (!second_token)
+			die("'%s' is not a commit", argv[1]);
+		if (hashcmp(second_token->object.sha1, head))
+			return NULL;
+	}
+	return second_token;
+}
+
+static int evaluate_result(void)
+{
+	int cnt = 0;
+	struct rev_info rev;
+
+	if (read_cache() < 0)
+		die("failed to read the cache");
+
+	/* Check how many files differ. */
+	init_revisions(&rev, "");
+	setup_revisions(0, NULL, &rev, NULL);
+	rev.diffopt.output_format |=
+		DIFF_FORMAT_CALLBACK;
+	rev.diffopt.format_callback = count_diff_files;
+	rev.diffopt.format_callback_data = &cnt;
+	run_diff_files(&rev, 0);
+
+	/*
+	 * Check how many unmerged entries are
+	 * there.
+	 */
+	cnt += count_unmerged_entries();
+
+	return cnt;
+}
+
+int cmd_merge(int argc, const char **argv, const char *prefix)
+{
+	unsigned char result_tree[20];
+	struct strbuf buf;
+	const char *head_arg;
+	int flag, head_invalid = 0, i;
+	int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
+	struct commit_list *common = NULL;
+	const char *best_strategy = NULL, *wt_strategy = NULL;
+	struct commit_list **remotes = &remoteheads;
+
+	setup_work_tree();
+	if (unmerged_cache())
+		die("You are in the middle of a conflicted merge.");
+
+	/*
+	 * Check if we are _not_ on a detached HEAD, i.e. if there is a
+	 * current branch.
+	 */
+	branch = resolve_ref("HEAD", head, 0, &flag);
+	if (branch && !prefixcmp(branch, "refs/heads/"))
+		branch += 11;
+	if (is_null_sha1(head))
+		head_invalid = 1;
+
+	git_config(git_merge_config, NULL);
+
+	/* for color.ui */
+	if (diff_use_color_default == -1)
+		diff_use_color_default = git_use_color_default;
+
+	argc = parse_options(argc, argv, builtin_merge_options,
+			builtin_merge_usage, 0);
+
+	if (squash) {
+		if (!allow_fast_forward)
+			die("You cannot combine --squash with --no-ff.");
+		option_commit = 0;
+	}
+
+	if (!argc)
+		usage_with_options(builtin_merge_usage,
+			builtin_merge_options);
+
+	/*
+	 * This could be traditional "merge <msg> HEAD <commit>..."  and
+	 * the way we can tell it is to see if the second token is HEAD,
+	 * but some people might have misused the interface and used a
+	 * committish that is the same as HEAD there instead.
+	 * Traditional format never would have "-m" so it is an
+	 * additional safety measure to check for it.
+	 */
+	strbuf_init(&buf, 0);
+
+	if (!have_message && is_old_style_invocation(argc, argv)) {
+		strbuf_addstr(&merge_msg, argv[0]);
+		head_arg = argv[1];
+		argv += 2;
+		argc -= 2;
+	} else if (head_invalid) {
+		struct object *remote_head;
+		/*
+		 * If the merged head is a valid one there is no reason
+		 * to forbid "git merge" into a branch yet to be born.
+		 * We do the same for "git pull".
+		 */
+		if (argc != 1)
+			die("Can merge only exactly one commit into "
+				"empty head");
+		remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+		if (!remote_head)
+			die("%s - not something we can merge", argv[0]);
+		update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
+				DIE_ON_ERR);
+		reset_hard(remote_head->sha1, 0);
+		return 0;
+	} else {
+		struct strbuf msg;
+
+		/* We are invoked directly as the first-class UI. */
+		head_arg = "HEAD";
+
+		/*
+		 * All the rest are the commits being merged;
+		 * prepare the standard merge summary message to
+		 * be appended to the given message.  If remote
+		 * is invalid we will die later in the common
+		 * codepath so we discard the error in this
+		 * loop.
+		 */
+		strbuf_init(&msg, 0);
+		for (i = 0; i < argc; i++)
+			merge_name(argv[i], &msg);
+		fmt_merge_msg(option_log, &msg, &merge_msg);
+		if (merge_msg.len)
+			strbuf_setlen(&merge_msg, merge_msg.len-1);
+	}
+
+	if (head_invalid || !argc)
+		usage_with_options(builtin_merge_usage,
+			builtin_merge_options);
+
+	strbuf_addstr(&buf, "merge");
+	for (i = 0; i < argc; i++)
+		strbuf_addf(&buf, " %s", argv[i]);
+	setenv("GIT_REFLOG_ACTION", buf.buf, 0);
+	strbuf_reset(&buf);
+
+	for (i = 0; i < argc; i++) {
+		struct object *o;
+
+		o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
+		if (!o)
+			die("%s - not something we can merge", argv[i]);
+		remotes = &commit_list_insert(lookup_commit(o->sha1),
+			remotes)->next;
+
+		strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
+		setenv(buf.buf, argv[i], 1);
+		strbuf_reset(&buf);
+	}
+
+	if (!use_strategies) {
+		if (!remoteheads->next)
+			add_strategies(pull_twohead, DEFAULT_TWOHEAD);
+		else
+			add_strategies(pull_octopus, DEFAULT_OCTOPUS);
+	}
+
+	for (i = 0; i < use_strategies_nr; i++) {
+		if (use_strategies[i].attr & NO_FAST_FORWARD)
+			allow_fast_forward = 0;
+		if (use_strategies[i].attr & NO_TRIVIAL)
+			allow_trivial = 0;
+	}
+
+	if (!remoteheads->next)
+		common = get_merge_bases(lookup_commit(head),
+				remoteheads->item, 1);
+	else {
+		struct commit_list *list = remoteheads;
+		commit_list_insert(lookup_commit(head), &list);
+		common = get_octopus_merge_bases(list);
+		free(list);
+	}
+
+	update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
+		DIE_ON_ERR);
+
+	if (!common)
+		; /* No common ancestors found. We need a real merge. */
+	else if (!remoteheads->next && !common->next &&
+			common->item == remoteheads->item) {
+		/*
+		 * If head can reach all the merge then we are up to date.
+		 * but first the most common case of merging one remote.
+		 */
+		finish_up_to_date("Already up-to-date.");
+		return 0;
+	} else if (allow_fast_forward && !remoteheads->next &&
+			!common->next &&
+			!hashcmp(common->item->object.sha1, head)) {
+		/* Again the most common case of merging one remote. */
+		struct strbuf msg;
+		struct object *o;
+		char hex[41];
+
+		strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+
+		printf("Updating %s..%s\n",
+			hex,
+			find_unique_abbrev(remoteheads->item->object.sha1,
+			DEFAULT_ABBREV));
+		refresh_cache(REFRESH_QUIET);
+		strbuf_init(&msg, 0);
+		strbuf_addstr(&msg, "Fast forward");
+		if (have_message)
+			strbuf_addstr(&msg,
+				" (no commit created; -m option ignored)");
+		o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
+			0, NULL, OBJ_COMMIT);
+		if (!o)
+			return 1;
+
+		if (checkout_fast_forward(head, remoteheads->item->object.sha1))
+			return 1;
+
+		finish(o->sha1, msg.buf);
+		drop_save();
+		return 0;
+	} else if (!remoteheads->next && common->next)
+		;
+		/*
+		 * We are not doing octopus and not fast forward.  Need
+		 * a real merge.
+		 */
+	else if (!remoteheads->next && !common->next && option_commit) {
+		/*
+		 * We are not doing octopus, not fast forward, and have
+		 * only one common.
+		 */
+		refresh_cache(REFRESH_QUIET);
+		if (allow_trivial) {
+			/* See if it is really trivial. */
+			git_committer_info(IDENT_ERROR_ON_NO_NAME);
+			printf("Trying really trivial in-index merge...\n");
+			if (!read_tree_trivial(common->item->object.sha1,
+					head, remoteheads->item->object.sha1))
+				return merge_trivial();
+			printf("Nope.\n");
+		}
+	} else {
+		/*
+		 * An octopus.  If we can reach all the remote we are up
+		 * to date.
+		 */
+		int up_to_date = 1;
+		struct commit_list *j;
+
+		for (j = remoteheads; j; j = j->next) {
+			struct commit_list *common_one;
+
+			/*
+			 * Here we *have* to calculate the individual
+			 * merge_bases again, otherwise "git merge HEAD^
+			 * HEAD^^" would be missed.
+			 */
+			common_one = get_merge_bases(lookup_commit(head),
+				j->item, 1);
+			if (hashcmp(common_one->item->object.sha1,
+				j->item->object.sha1)) {
+				up_to_date = 0;
+				break;
+			}
+		}
+		if (up_to_date) {
+			finish_up_to_date("Already up-to-date. Yeeah!");
+			return 0;
+		}
+	}
+
+	/* We are going to make a new commit. */
+	git_committer_info(IDENT_ERROR_ON_NO_NAME);
+
+	/*
+	 * At this point, we need a real merge.  No matter what strategy
+	 * we use, it would operate on the index, possibly affecting the
+	 * working tree, and when resolved cleanly, have the desired
+	 * tree in the index -- this means that the index must be in
+	 * sync with the head commit.  The strategies are responsible
+	 * to ensure this.
+	 */
+	if (use_strategies_nr != 1) {
+		/*
+		 * Stash away the local changes so that we can try more
+		 * than one.
+		 */
+		save_state();
+	} else {
+		memcpy(stash, null_sha1, 20);
+	}
+
+	for (i = 0; i < use_strategies_nr; i++) {
+		int ret;
+		if (i) {
+			printf("Rewinding the tree to pristine...\n");
+			restore_state();
+		}
+		if (use_strategies_nr != 1)
+			printf("Trying merge strategy %s...\n",
+				use_strategies[i].name);
+		/*
+		 * Remember which strategy left the state in the working
+		 * tree.
+		 */
+		wt_strategy = use_strategies[i].name;
+
+		ret = try_merge_strategy(use_strategies[i].name,
+			common, head_arg);
+		if (!option_commit && !ret) {
+			merge_was_ok = 1;
+			/*
+			 * This is necessary here just to avoid writing
+			 * the tree, but later we will *not* exit with
+			 * status code 1 because merge_was_ok is set.
+			 */
+			ret = 1;
+		}
+
+		if (ret) {
+			/*
+			 * The backend exits with 1 when conflicts are
+			 * left to be resolved, with 2 when it does not
+			 * handle the given merge at all.
+			 */
+			if (ret == 1) {
+				int cnt = evaluate_result();
+
+				if (best_cnt <= 0 || cnt <= best_cnt) {
+					best_strategy = use_strategies[i].name;
+					best_cnt = cnt;
+				}
+			}
+			if (merge_was_ok)
+				break;
+			else
+				continue;
+		}
+
+		/* Automerge succeeded. */
+		write_tree_trivial(result_tree);
+		automerge_was_ok = 1;
+		break;
+	}
+
+	/*
+	 * If we have a resulting tree, that means the strategy module
+	 * auto resolved the merge cleanly.
+	 */
+	if (automerge_was_ok)
+		return finish_automerge(common, result_tree, wt_strategy);
+
+	/*
+	 * Pick the result from the best strategy and have the user fix
+	 * it up.
+	 */
+	if (!best_strategy) {
+		restore_state();
+		if (use_strategies_nr > 1)
+			fprintf(stderr,
+				"No merge strategy handled the merge.\n");
+		else
+			fprintf(stderr, "Merge with strategy %s failed.\n",
+				use_strategies[0].name);
+		return 2;
+	} else if (best_strategy == wt_strategy)
+		; /* We already have its result in the working tree. */
+	else {
+		printf("Rewinding the tree to pristine...\n");
+		restore_state();
+		printf("Using the %s to prepare resolving by hand.\n",
+			best_strategy);
+		try_merge_strategy(best_strategy, common, head_arg);
+	}
+
+	if (squash)
+		finish(NULL, NULL);
+	else {
+		int fd;
+		struct commit_list *j;
+
+		for (j = remoteheads; j; j = j->next)
+			strbuf_addf(&buf, "%s\n",
+				sha1_to_hex(j->item->object.sha1));
+		fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
+		if (fd < 0)
+			die("Could open %s for writing",
+				git_path("MERGE_HEAD"));
+		if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+			die("Could not write to %s", git_path("MERGE_HEAD"));
+		close(fd);
+		strbuf_addch(&merge_msg, '\n');
+		fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+		if (fd < 0)
+			die("Could open %s for writing", git_path("MERGE_MSG"));
+		if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
+			merge_msg.len)
+			die("Could not write to %s", git_path("MERGE_MSG"));
+		close(fd);
+	}
+
+	if (merge_was_ok) {
+		fprintf(stderr, "Automatic merge went well; "
+			"stopped before committing as requested\n");
+		return 0;
+	} else
+		return suggest_conflicts();
+}
diff --git a/builtin.h b/builtin.h
index 05ee56f..0e605d4 100644
--- a/builtin.h
+++ b/builtin.h
@@ -64,6 +64,7 @@ extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_ls_remote(int argc, const char **argv, const char *prefix);
 extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
 extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
+extern int cmd_merge(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
diff --git a/git-merge.sh b/contrib/examples/git-merge.sh
similarity index 100%
rename from git-merge.sh
rename to contrib/examples/git-merge.sh
diff --git a/git.c b/git.c
index 2fbe96b..770aadd 100644
--- a/git.c
+++ b/git.c
@@ -271,6 +271,7 @@ static void handle_internal_command(int argc, const char **argv)
 		{ "ls-remote", cmd_ls_remote },
 		{ "mailinfo", cmd_mailinfo },
 		{ "mailsplit", cmd_mailsplit },
+		{ "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
 		{ "merge-base", cmd_merge_base, RUN_SETUP },
 		{ "merge-file", cmd_merge_file },
 		{ "merge-ours", cmd_merge_ours, RUN_SETUP },
diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh
index f3a4bb2..fcb8285 100755
--- a/t/t7602-merge-octopus-many.sh
+++ b/t/t7602-merge-octopus-many.sh
@@ -23,7 +23,7 @@ test_expect_success 'setup' '
 	done
 '
 
-test_expect_failure 'merge c1 with c2, c3, c4, ... c29' '
+test_expect_success 'merge c1 with c2, c3, c4, ... c29' '
 	git reset --hard c1 &&
 	i=2 &&
 	refs="" &&
-- 
1.5.6.1.322.ge904b.dirty

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH] Build in merge
  2008-07-07 17:24                                                         ` [PATCH] " Miklos Vajna
@ 2008-07-07 17:35                                                           ` Miklos Vajna
  0 siblings, 0 replies; 81+ messages in thread
From: Miklos Vajna @ 2008-07-07 17:35 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

[-- Attachment #1: Type: text/plain, Size: 307 bytes --]

On Mon, Jul 07, 2008 at 07:24:20PM +0200, Miklos Vajna <vmiklos@frugalware.org> wrote:
> Here is an updated version without using path_list at all.

Sorry forgot the interdiff and the log:

- interdiff: git diff 2ed1884..10d5724

- log: git log c255d12 (1 commit)

$ git grep -c path.list builtin-merge.c
0

[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH 14/14] Build in merge
  2008-07-07 17:17                                                           ` Miklos Vajna
@ 2008-07-07 18:15                                                             ` Junio C Hamano
  2008-07-07 23:42                                                               ` [PATCH] " Miklos Vajna
  0 siblings, 1 reply; 81+ messages in thread
From: Junio C Hamano @ 2008-07-07 18:15 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: git, Johannes Schindelin, Olivier Marin

Miklos Vajna <vmiklos@frugalware.org> writes:

> I think it's better not to modify all_strategy, it serves as a
> reference, for example later this would allow us to check if the used
> merge strategy is a predefined or a custom one.

I do not get you on this point.  Which one is nicer?

 (1) Have two lists, perhaps all_* and user_*.  The logic that finds a
     strategy searches in two lists.  The logic that checks if a given
     strategy is built-in checks if it is on all_* list.

 (2) Have a single list, but add a boolean "unsigned is_builtin:1" to each
     element of it.  The logic that finds a strategy looks in this single
     list.  The logic that checks if a given strategy is built-in looks at
     the strategy instance and it has the bit already.

You seem to be advocating (1) but I do not understand why...

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH] Build in merge
  2008-07-07 18:15                                                             ` Junio C Hamano
@ 2008-07-07 23:42                                                               ` Miklos Vajna
  2008-07-08  0:32                                                                 ` Junio C Hamano
  0 siblings, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-07-07 23:42 UTC (permalink / raw)
  To: To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---

On Mon, Jul 07, 2008 at 11:15:09AM -0700, Junio C Hamano <gitster@pobox.com> wrote:
> I do not get you on this point.  Which one is nicer?
>
>  (1) Have two lists, perhaps all_* and user_*.  The logic that finds a
>      strategy searches in two lists.  The logic that checks if a given
>      strategy is built-in checks if it is on all_* list.
>
>  (2) Have a single list, but add a boolean "unsigned is_builtin:1" to
>  each
>      element of it.  The logic that finds a strategy looks in this
>      single
>      list.  The logic that checks if a given strategy is built-in
>      looks at
>      the strategy instance and it has the bit already.
>
> You seem to be advocating (1) but I do not understand why...

Ah, OK. For now, I just added an "unsigned enabled:1;". Later we can add
an "unsigned is_buildin:1;" as well, but currently we die with earlier
with a "Could not find merge strategy" error message, so is_builtin
would be always true.

So here is a version, this time without the use_strategies list.

Interdiff: git diff 638c51f..1e74660

 Makefile                                      |    2 +-
 builtin-merge.c                               | 1142 +++++++++++++++++++++++++
 builtin.h                                     |    1 +
 git-merge.sh => contrib/examples/git-merge.sh |    0
 git.c                                         |    1 +
 t/t7602-merge-octopus-many.sh                 |    2 +-
 6 files changed, 1146 insertions(+), 2 deletions(-)
 create mode 100644 builtin-merge.c
 rename git-merge.sh => contrib/examples/git-merge.sh (100%)

diff --git a/Makefile b/Makefile
index bf77292..fbc53e9 100644
--- a/Makefile
+++ b/Makefile
@@ -240,7 +240,6 @@ SCRIPT_SH += git-lost-found.sh
 SCRIPT_SH += git-merge-octopus.sh
 SCRIPT_SH += git-merge-one-file.sh
 SCRIPT_SH += git-merge-resolve.sh
-SCRIPT_SH += git-merge.sh
 SCRIPT_SH += git-merge-stupid.sh
 SCRIPT_SH += git-mergetool.sh
 SCRIPT_SH += git-parse-remote.sh
@@ -515,6 +514,7 @@ BUILTIN_OBJS += builtin-ls-remote.o
 BUILTIN_OBJS += builtin-ls-tree.o
 BUILTIN_OBJS += builtin-mailinfo.o
 BUILTIN_OBJS += builtin-mailsplit.o
+BUILTIN_OBJS += builtin-merge.o
 BUILTIN_OBJS += builtin-merge-base.o
 BUILTIN_OBJS += builtin-merge-file.o
 BUILTIN_OBJS += builtin-merge-ours.o
diff --git a/builtin-merge.c b/builtin-merge.c
new file mode 100644
index 0000000..6d801dd
--- /dev/null
+++ b/builtin-merge.c
@@ -0,0 +1,1142 @@
+/*
+ * Builtin "git merge"
+ *
+ * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
+ *
+ * Based on git-merge.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "parse-options.h"
+#include "builtin.h"
+#include "run-command.h"
+#include "diff.h"
+#include "refs.h"
+#include "commit.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
+#include "dir.h"
+#include "utf8.h"
+#include "log-tree.h"
+#include "color.h"
+
+#define DEFAULT_TWOHEAD (1<<0)
+#define DEFAULT_OCTOPUS (1<<1)
+#define NO_FAST_FORWARD (1<<2)
+#define NO_TRIVIAL      (1<<3)
+
+struct strategy {
+	const char *name;
+	unsigned attr;
+	unsigned enabled:1;
+};
+
+static const char * const builtin_merge_usage[] = {
+	"git-merge [options] <remote>...",
+	"git-merge [options] <msg> HEAD <remote>",
+	NULL
+};
+
+static int show_diffstat = 1, option_log, squash;
+static int option_commit = 1, allow_fast_forward = 1;
+static int allow_trivial = 1, have_message;
+static struct strbuf merge_msg;
+static struct commit_list *remoteheads;
+static unsigned char head[20], stash[20];
+static const char *branch;
+
+static struct strategy all_strategy[] = {
+	{ "recur",      NO_TRIVIAL, 0 },
+	{ "recursive",  DEFAULT_TWOHEAD | NO_TRIVIAL, 0 },
+	{ "octopus",    DEFAULT_OCTOPUS, 0 },
+	{ "resolve",    0, 0 },
+	{ "stupid",     0, 0 },
+	{ "ours",       NO_FAST_FORWARD | NO_TRIVIAL, 0 },
+	{ "subtree",    NO_FAST_FORWARD | NO_TRIVIAL, 0 },
+};
+/*
+ * This contains the number of enabled variables, so you don't have to
+ * scan through all_strategies all the time.
+ */
+static int enabled_strategies;
+
+static const char *pull_twohead, *pull_octopus;
+
+static int option_parse_message(const struct option *opt,
+	const char *arg, int unset)
+{
+	struct strbuf *buf = opt->value;
+
+	if (unset)
+		strbuf_setlen(buf, 0);
+	else {
+		strbuf_addf(buf, "%s\n\n", arg);
+		have_message = 1;
+	}
+	return 0;
+}
+
+static void enable_strategy(const char *name)
+{
+	int i;
+	struct strbuf err;
+
+	enabled_strategies++;
+	for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+		if (!strcmp(all_strategy[i].name, name)) {
+			all_strategy[i].enabled = 1;
+			return;
+		}
+
+	strbuf_init(&err, 0);
+	for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+		strbuf_addf(&err, " %s", all_strategy[i].name);
+	fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
+	fprintf(stderr, "Available strategies are:%s.\n", err.buf);
+	exit(1);
+}
+
+static int option_parse_strategy(const struct option *opt,
+	const char *arg, int unset)
+{
+	if (unset)
+		return 0;
+
+	enable_strategy(arg);
+	return 0;
+}
+
+static int option_parse_n(const struct option *opt,
+		const char *arg, int unset)
+{
+	show_diffstat = unset;
+	return 0;
+}
+
+static struct option builtin_merge_options[] = {
+	{ OPTION_CALLBACK, 'n', NULL, NULL, NULL,
+		"do not show a diffstat at the end of the merge",
+		PARSE_OPT_NOARG, option_parse_n },
+	OPT_BOOLEAN(0, "stat", &show_diffstat,
+		"show a diffstat at the end of the merge"),
+	OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
+	OPT_BOOLEAN(0, "log", &option_log,
+		"add list of one-line log to merge commit message"),
+	OPT_BOOLEAN(0, "squash", &squash,
+		"create a single commit instead of doing a merge"),
+	OPT_BOOLEAN(0, "commit", &option_commit,
+		"perform a commit if the merge succeeds (default)"),
+	OPT_BOOLEAN(0, "ff", &allow_fast_forward,
+		"allow fast forward (default)"),
+	OPT_CALLBACK('s', "strategy", &all_strategy, "strategy",
+		"merge strategy to use", option_parse_strategy),
+	OPT_CALLBACK('m', "message", &merge_msg, "message",
+		"message to be used for the merge commit (if any)",
+		option_parse_message),
+	OPT_END()
+};
+
+/* Cleans up metadata that is uninteresting after a succeeded merge. */
+static void drop_save(void)
+{
+	unlink(git_path("MERGE_HEAD"));
+	unlink(git_path("MERGE_MSG"));
+}
+
+static void save_state(void)
+{
+	int len;
+	struct child_process cp;
+	struct strbuf buffer = STRBUF_INIT;
+	const char *argv[] = {"stash", "create", NULL};
+
+	memset(&cp, 0, sizeof(cp));
+	cp.argv = argv;
+	cp.out = -1;
+	cp.git_cmd = 1;
+
+	if (start_command(&cp))
+		die("could not run stash.");
+	len = strbuf_read(&buffer, cp.out, 1024);
+	close(cp.out);
+
+	if (finish_command(&cp) || len < 0)
+		die("stash failed");
+	else if (!len)
+		return;
+	strbuf_setlen(&buffer, buffer.len-1);
+	if (get_sha1(buffer.buf, stash))
+		die("not a valid object: %s", buffer.buf);
+}
+
+static void reset_hard(unsigned const char *sha1, int verbose)
+{
+	int i = 0;
+	const char *args[6];
+
+	args[i++] = "read-tree";
+	if (verbose)
+		args[i++] = "-v";
+	args[i++] = "--reset";
+	args[i++] = "-u";
+	args[i++] = sha1_to_hex(sha1);
+	args[i] = NULL;
+
+	if (run_command_v_opt(args, RUN_GIT_CMD))
+		die("read-tree failed");
+}
+
+static void restore_state(void)
+{
+	struct strbuf sb;
+	const char *args[] = { "stash", "apply", NULL, NULL };
+
+	if (is_null_sha1(stash))
+		return;
+
+	reset_hard(head, 1);
+
+	strbuf_init(&sb, 0);
+	args[2] = sha1_to_hex(stash);
+
+	/*
+	 * It is OK to ignore error here, for example when there was
+	 * nothing to restore.
+	 */
+	run_command_v_opt(args, RUN_GIT_CMD);
+
+	strbuf_release(&sb);
+	refresh_cache(REFRESH_QUIET);
+}
+
+/* This is called when no merge was necessary. */
+static void finish_up_to_date(const char *msg)
+{
+	printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
+	drop_save();
+}
+
+static void squash_message(void)
+{
+	struct rev_info rev;
+	struct commit *commit;
+	struct strbuf out;
+	struct commit_list *j;
+	int fd;
+
+	printf("Squash commit -- not updating HEAD\n");
+	fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+	if (fd < 0)
+		die("Could not write to %s", git_path("SQUASH_MSG"));
+
+	init_revisions(&rev, NULL);
+	rev.ignore_merges = 1;
+	rev.commit_format = CMIT_FMT_MEDIUM;
+
+	commit = lookup_commit(head);
+	commit->object.flags |= UNINTERESTING;
+	add_pending_object(&rev, &commit->object, NULL);
+
+	for (j = remoteheads; j; j = j->next)
+		add_pending_object(&rev, &j->item->object, NULL);
+
+	setup_revisions(0, NULL, &rev, NULL);
+	if (prepare_revision_walk(&rev))
+		die("revision walk setup failed");
+
+	strbuf_init(&out, 0);
+	strbuf_addstr(&out, "Squashed commit of the following:\n");
+	while ((commit = get_revision(&rev)) != NULL) {
+		strbuf_addch(&out, '\n');
+		strbuf_addf(&out, "commit %s\n",
+			sha1_to_hex(commit->object.sha1));
+		pretty_print_commit(rev.commit_format, commit, &out, rev.abbrev,
+			NULL, NULL, rev.date_mode, 0);
+	}
+	write(fd, out.buf, out.len);
+	close(fd);
+	strbuf_release(&out);
+}
+
+static int run_hook(const char *name)
+{
+	struct child_process hook;
+	const char *argv[3], *env[2];
+	char index[PATH_MAX];
+
+	argv[0] = git_path("hooks/%s", name);
+	if (access(argv[0], X_OK) < 0)
+		return 0;
+
+	snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", get_index_file());
+	env[0] = index;
+	env[1] = NULL;
+
+	if (squash)
+		argv[1] = "1";
+	else
+		argv[1] = "0";
+	argv[2] = NULL;
+
+	memset(&hook, 0, sizeof(hook));
+	hook.argv = argv;
+	hook.no_stdin = 1;
+	hook.stdout_to_stderr = 1;
+	hook.env = env;
+
+	return run_command(&hook);
+}
+
+static void finish(const unsigned char *new_head, const char *msg)
+{
+	struct strbuf reflog_message;
+
+	strbuf_init(&reflog_message, 0);
+	if (!msg)
+		strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
+	else {
+		printf("%s\n", msg);
+		strbuf_addf(&reflog_message, "%s: %s",
+			getenv("GIT_REFLOG_ACTION"), msg);
+	}
+	if (squash) {
+		squash_message();
+	} else {
+		if (!merge_msg.len)
+			printf("No merge message -- not updating HEAD\n");
+		else {
+			const char *argv_gc_auto[] = { "gc", "--auto", NULL };
+			update_ref(reflog_message.buf, "HEAD",
+				new_head, head, 0,
+				DIE_ON_ERR);
+			/*
+			 * We ignore errors in 'gc --auto', since the
+			 * user should see them.
+			 */
+			run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+		}
+	}
+	if (new_head && show_diffstat) {
+		struct diff_options opts;
+		diff_setup(&opts);
+		opts.output_format |=
+			DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+		opts.detect_rename = DIFF_DETECT_RENAME;
+		if (diff_use_color_default > 0)
+			DIFF_OPT_SET(&opts, COLOR_DIFF);
+		if (diff_setup_done(&opts) < 0)
+			die("diff_setup_done failed");
+		diff_tree_sha1(head, new_head, "", &opts);
+		diffcore_std(&opts);
+		diff_flush(&opts);
+	}
+
+	/* Run a post-merge hook */
+	run_hook("post-merge");
+
+	strbuf_release(&reflog_message);
+}
+
+/* Get the name for the merge commit's message. */
+static void merge_name(const char *remote, struct strbuf *msg)
+{
+	struct object *remote_head;
+	unsigned char branch_head[20], buf_sha[20];
+	struct strbuf buf;
+	const char *ptr;
+	int len, early;
+
+	memset(branch_head, 0, sizeof(branch_head));
+	remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+	if (!remote_head)
+		die("'%s' does not point to a commit", remote);
+
+	strbuf_init(&buf, 0);
+	strbuf_addstr(&buf, "refs/heads/");
+	strbuf_addstr(&buf, remote);
+	resolve_ref(buf.buf, branch_head, 0, 0);
+
+	if (!hashcmp(remote_head->sha1, branch_head)) {
+		strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
+			sha1_to_hex(branch_head), remote);
+		return;
+	}
+
+	/* See if remote matches <name>^^^.. or <name>~<number> */
+	for (len = 0, ptr = remote + strlen(remote);
+	     remote < ptr && ptr[-1] == '^';
+	     ptr--)
+		len++;
+	if (len)
+		early = 1;
+	else {
+		early = 0;
+		ptr = strrchr(remote, '~');
+		if (ptr) {
+			int seen_nonzero = 0;
+
+			len++; /* count ~ */
+			while (*++ptr && isdigit(*ptr)) {
+				seen_nonzero |= (*ptr != '0');
+				len++;
+			}
+			if (*ptr)
+				len = 0; /* not ...~<number> */
+			else if (seen_nonzero)
+				early = 1;
+			else if (len == 1)
+				early = 1; /* "name~" is "name~1"! */
+		}
+	}
+	if (len) {
+		struct strbuf truname = STRBUF_INIT;
+		strbuf_addstr(&truname, "refs/heads/");
+		strbuf_addstr(&truname, remote);
+		strbuf_setlen(&truname, len+11);
+		if (resolve_ref(truname.buf, buf_sha, 0, 0)) {
+			strbuf_addf(msg,
+				    "%s\t\tbranch '%s'%s of .\n",
+				    sha1_to_hex(remote_head->sha1),
+				    truname.buf,
+				    (early ? " (early part)" : ""));
+			return;
+		}
+	}
+
+	if (!strcmp(remote, "FETCH_HEAD") &&
+			!access(git_path("FETCH_HEAD"), R_OK)) {
+		FILE *fp;
+		struct strbuf line;
+		char *ptr;
+
+		strbuf_init(&line, 0);
+		fp = fopen(git_path("FETCH_HEAD"), "r");
+		if (!fp)
+			die("could not open %s for reading: %s",
+				git_path("FETCH_HEAD"), strerror(errno));
+		strbuf_getline(&line, fp, '\n');
+		fclose(fp);
+		ptr = strstr(line.buf, "\tnot-for-merge\t");
+		if (ptr)
+			strbuf_remove(&line, ptr-line.buf+1, 13);
+		strbuf_addbuf(msg, &line);
+		strbuf_release(&line);
+		return;
+	}
+	strbuf_addf(msg, "%s\t\tcommit '%s'\n",
+		sha1_to_hex(remote_head->sha1), remote);
+}
+
+int git_merge_config(const char *k, const char *v, void *cb)
+{
+	if (branch && !prefixcmp(k, "branch.") &&
+		!prefixcmp(k + 7, branch) &&
+		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+		const char **argv;
+		int argc;
+		char *buf;
+
+		buf = xstrdup(v);
+		argc = split_cmdline(buf, &argv);
+		argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+		memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+		argc++;
+		parse_options(argc, argv, builtin_merge_options,
+			      builtin_merge_usage, 0);
+		free(buf);
+	}
+
+	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+		show_diffstat = git_config_bool(k, v);
+	else if (!strcmp(k, "pull.twohead"))
+		return git_config_string(&pull_twohead, k, v);
+	else if (!strcmp(k, "pull.octopus"))
+		return git_config_string(&pull_octopus, k, v);
+	return git_diff_ui_config(k, v, cb);
+}
+
+static int read_tree_trivial(unsigned char *common, unsigned char *head,
+	unsigned char *one)
+{
+	int i, nr_trees = 0;
+	struct tree *trees[MAX_UNPACK_TREES];
+	struct tree_desc t[MAX_UNPACK_TREES];
+	struct unpack_trees_options opts;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.head_idx = 2;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.verbose_update = 1;
+	opts.trivial_merges_only = 1;
+	opts.merge = 1;
+	trees[nr_trees] = parse_tree_indirect(common);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(head);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(one);
+	if (!trees[nr_trees++])
+		return -1;
+	opts.fn = threeway_merge;
+	cache_tree_free(&active_cache_tree);
+	for (i = 0; i < nr_trees; i++) {
+		parse_tree(trees[i]);
+		init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+	}
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+	return 0;
+}
+
+static void write_tree_trivial(unsigned char *sha1)
+{
+	if (write_cache_as_tree(sha1, 0, NULL))
+		die("git write-tree failed to write a tree");
+}
+
+static int try_merge_strategy(const char *strategy, struct commit_list *common,
+	const char *head_arg)
+{
+	const char **args;
+	int i = 0, ret;
+	struct commit_list *j;
+	struct strbuf buf;
+
+	args = xmalloc((4 + commit_list_count(common) +
+			commit_list_count(remoteheads)) * sizeof(char *));
+	strbuf_init(&buf, 0);
+	strbuf_addf(&buf, "merge-%s", strategy);
+	args[i++] = buf.buf;
+	for (j = common; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i++] = "--";
+	args[i++] = head_arg;
+	for (j = remoteheads; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i] = NULL;
+	ret = run_command_v_opt(args, RUN_GIT_CMD);
+	strbuf_release(&buf);
+	i = 1;
+	for (j = common; j; j = j->next)
+		free((void *)args[i++]);
+	i += 2;
+	for (j = remoteheads; j; j = j->next)
+		free((void *)args[i++]);
+	free(args);
+	return -ret;
+}
+
+static void count_diff_files(struct diff_queue_struct *q,
+		struct diff_options *opt, void *data)
+{
+	int *count = data;
+
+	(*count) += q->nr;
+}
+
+static int count_unmerged_entries(void)
+{
+	const struct index_state *state = &the_index;
+	int i, ret = 0;
+
+	for (i = 0; i < state->cache_nr; i++)
+		if (ce_stage(state->cache[i]))
+			ret++;
+
+	return ret;
+}
+
+static int checkout_fast_forward(unsigned char *head, unsigned char *remote)
+{
+	struct tree *trees[MAX_UNPACK_TREES];
+	struct unpack_trees_options opts;
+	struct tree_desc t[MAX_UNPACK_TREES];
+	int i, fd, nr_trees = 0;
+	struct dir_struct dir;
+	struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+	if (read_cache_unmerged())
+		die("you need to resolve your current index first");
+
+	fd = hold_locked_index(lock_file, 1);
+
+	memset(&trees, 0, sizeof(trees));
+	memset(&opts, 0, sizeof(opts));
+	memset(&t, 0, sizeof(t));
+	dir.show_ignored = 1;
+	dir.exclude_per_dir = ".gitignore";
+	opts.dir = &dir;
+
+	opts.head_idx = 1;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.verbose_update = 1;
+	opts.merge = 1;
+	opts.fn = twoway_merge;
+
+	trees[nr_trees] = parse_tree_indirect(head);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(remote);
+	if (!trees[nr_trees++])
+		return -1;
+	for (i = 0; i < nr_trees; i++) {
+		parse_tree(trees[i]);
+		init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+	}
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+	if (write_cache(fd, active_cache, active_nr) ||
+		commit_locked_index(lock_file))
+		die("unable to write new index file");
+	return 0;
+}
+
+static void split_merge_strategies(const char *string, struct strategy **list,
+		int *nr, int *alloc)
+{
+	char *p, *q, *buf;
+
+	if (!string)
+		return;
+
+	buf = xstrdup(string);
+	q = buf;
+	for (;;) {
+		p = strchr(q, ' ');
+		if (!p) {
+			ALLOC_GROW(*list, *nr + 1, *alloc);
+			(*list)[(*nr)++].name = xstrdup(q);
+			free(buf);
+			return;
+		} else {
+			*p = '\0';
+			ALLOC_GROW(*list, *nr + 1, *alloc);
+			(*list)[(*nr)++].name = xstrdup(q);
+			q = ++p;
+		}
+	}
+}
+
+static void add_strategies(const char *string, unsigned attr)
+{
+	struct strategy *list = NULL;
+	int list_alloc = 0, list_nr = 0, i;
+
+	memset(&list, 0, sizeof(list));
+	split_merge_strategies(string, &list, &list_nr, &list_alloc);
+	if (list != NULL) {
+		for (i = 0; i < list_nr; i++)
+			enable_strategy(list[i].name);
+		return;
+	}
+	for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+		if (all_strategy[i].attr & attr)
+			enable_strategy(all_strategy[i].name);
+}
+
+static int merge_trivial(void)
+{
+	unsigned char result_tree[20], result_commit[20];
+	struct commit_list parent;
+
+	write_tree_trivial(result_tree);
+	printf("Wonderful.\n");
+	parent.item = remoteheads->item;
+	parent.next = NULL;
+	commit_tree(merge_msg.buf, result_tree, &parent, result_commit);
+	finish(result_commit, "In-index merge");
+	drop_save();
+	return 0;
+}
+
+static int finish_automerge(struct commit_list *common,
+	unsigned char *result_tree,
+	const char *wt_strategy)
+{
+	struct commit_list *parents = NULL, *j;
+	struct strbuf buf = STRBUF_INIT;
+	unsigned char result_commit[20];
+
+	free_commit_list(common);
+	if (allow_fast_forward) {
+		parents = remoteheads;
+		commit_list_insert(lookup_commit(head), &parents);
+		parents = reduce_heads(parents);
+	} else {
+		struct commit_list **pptr = &parents;
+
+		pptr = &commit_list_insert(lookup_commit(head),
+				pptr)->next;
+		for (j = remoteheads; j; j = j->next)
+			pptr = &commit_list_insert(j->item, pptr)->next;
+	}
+	free_commit_list(remoteheads);
+	strbuf_addch(&merge_msg, '\n');
+	commit_tree(merge_msg.buf, result_tree, parents, result_commit);
+	strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
+	finish(result_commit, buf.buf);
+	strbuf_release(&buf);
+	drop_save();
+	return 0;
+}
+
+static int suggest_conflicts(void)
+{
+	FILE *fp;
+	int pos;
+
+	fp = fopen(git_path("MERGE_MSG"), "a");
+	if (!fp)
+		die("Could open %s for writing", git_path("MERGE_MSG"));
+	fprintf(fp, "\nConflicts:\n");
+	for (pos = 0; pos < active_nr; pos++) {
+		struct cache_entry *ce = active_cache[pos];
+
+		if (ce_stage(ce)) {
+			fprintf(fp, "\t%s\n", ce->name);
+			while (pos + 1 < active_nr &&
+					!strcmp(ce->name,
+						active_cache[pos + 1]->name))
+				pos++;
+		}
+	}
+	fclose(fp);
+	rerere();
+	printf("Automatic merge failed; "
+			"fix conflicts and then commit the result.\n");
+	return 1;
+}
+
+static struct commit *is_old_style_invocation(int argc, const char **argv)
+{
+	struct commit *second_token = NULL;
+	if (argc > 1) {
+		unsigned char second_sha1[20];
+
+		if (get_sha1(argv[1], second_sha1))
+			return NULL;
+		second_token = lookup_commit_reference_gently(second_sha1, 0);
+		if (!second_token)
+			die("'%s' is not a commit", argv[1]);
+		if (hashcmp(second_token->object.sha1, head))
+			return NULL;
+	}
+	return second_token;
+}
+
+static int evaluate_result(void)
+{
+	int cnt = 0;
+	struct rev_info rev;
+
+	if (read_cache() < 0)
+		die("failed to read the cache");
+
+	/* Check how many files differ. */
+	init_revisions(&rev, "");
+	setup_revisions(0, NULL, &rev, NULL);
+	rev.diffopt.output_format |=
+		DIFF_FORMAT_CALLBACK;
+	rev.diffopt.format_callback = count_diff_files;
+	rev.diffopt.format_callback_data = &cnt;
+	run_diff_files(&rev, 0);
+
+	/*
+	 * Check how many unmerged entries are
+	 * there.
+	 */
+	cnt += count_unmerged_entries();
+
+	return cnt;
+}
+
+int cmd_merge(int argc, const char **argv, const char *prefix)
+{
+	unsigned char result_tree[20];
+	struct strbuf buf;
+	const char *head_arg;
+	int flag, head_invalid = 0, i, first_strategy = 1;
+	int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
+	struct commit_list *common = NULL;
+	const char *best_strategy = NULL, *wt_strategy = NULL;
+	struct commit_list **remotes = &remoteheads;
+
+	setup_work_tree();
+	if (unmerged_cache())
+		die("You are in the middle of a conflicted merge.");
+
+	/*
+	 * Check if we are _not_ on a detached HEAD, i.e. if there is a
+	 * current branch.
+	 */
+	branch = resolve_ref("HEAD", head, 0, &flag);
+	if (branch && !prefixcmp(branch, "refs/heads/"))
+		branch += 11;
+	if (is_null_sha1(head))
+		head_invalid = 1;
+
+	git_config(git_merge_config, NULL);
+
+	/* for color.ui */
+	if (diff_use_color_default == -1)
+		diff_use_color_default = git_use_color_default;
+
+	argc = parse_options(argc, argv, builtin_merge_options,
+			builtin_merge_usage, 0);
+
+	if (squash) {
+		if (!allow_fast_forward)
+			die("You cannot combine --squash with --no-ff.");
+		option_commit = 0;
+	}
+
+	if (!argc)
+		usage_with_options(builtin_merge_usage,
+			builtin_merge_options);
+
+	/*
+	 * This could be traditional "merge <msg> HEAD <commit>..."  and
+	 * the way we can tell it is to see if the second token is HEAD,
+	 * but some people might have misused the interface and used a
+	 * committish that is the same as HEAD there instead.
+	 * Traditional format never would have "-m" so it is an
+	 * additional safety measure to check for it.
+	 */
+	strbuf_init(&buf, 0);
+
+	if (!have_message && is_old_style_invocation(argc, argv)) {
+		strbuf_addstr(&merge_msg, argv[0]);
+		head_arg = argv[1];
+		argv += 2;
+		argc -= 2;
+	} else if (head_invalid) {
+		struct object *remote_head;
+		/*
+		 * If the merged head is a valid one there is no reason
+		 * to forbid "git merge" into a branch yet to be born.
+		 * We do the same for "git pull".
+		 */
+		if (argc != 1)
+			die("Can merge only exactly one commit into "
+				"empty head");
+		remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+		if (!remote_head)
+			die("%s - not something we can merge", argv[0]);
+		update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
+				DIE_ON_ERR);
+		reset_hard(remote_head->sha1, 0);
+		return 0;
+	} else {
+		struct strbuf msg;
+
+		/* We are invoked directly as the first-class UI. */
+		head_arg = "HEAD";
+
+		/*
+		 * All the rest are the commits being merged;
+		 * prepare the standard merge summary message to
+		 * be appended to the given message.  If remote
+		 * is invalid we will die later in the common
+		 * codepath so we discard the error in this
+		 * loop.
+		 */
+		strbuf_init(&msg, 0);
+		for (i = 0; i < argc; i++)
+			merge_name(argv[i], &msg);
+		fmt_merge_msg(option_log, &msg, &merge_msg);
+		if (merge_msg.len)
+			strbuf_setlen(&merge_msg, merge_msg.len-1);
+	}
+
+	if (head_invalid || !argc)
+		usage_with_options(builtin_merge_usage,
+			builtin_merge_options);
+
+	strbuf_addstr(&buf, "merge");
+	for (i = 0; i < argc; i++)
+		strbuf_addf(&buf, " %s", argv[i]);
+	setenv("GIT_REFLOG_ACTION", buf.buf, 0);
+	strbuf_reset(&buf);
+
+	for (i = 0; i < argc; i++) {
+		struct object *o;
+
+		o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
+		if (!o)
+			die("%s - not something we can merge", argv[i]);
+		remotes = &commit_list_insert(lookup_commit(o->sha1),
+			remotes)->next;
+
+		strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
+		setenv(buf.buf, argv[i], 1);
+		strbuf_reset(&buf);
+	}
+
+	if (!enabled_strategies) {
+		if (!remoteheads->next)
+			add_strategies(pull_twohead, DEFAULT_TWOHEAD);
+		else
+			add_strategies(pull_octopus, DEFAULT_OCTOPUS);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(all_strategy); i++) {
+		if (!all_strategy[i].enabled)
+			continue;
+		if (all_strategy[i].attr & NO_FAST_FORWARD)
+			allow_fast_forward = 0;
+		if (all_strategy[i].attr & NO_TRIVIAL)
+			allow_trivial = 0;
+	}
+
+	if (!remoteheads->next)
+		common = get_merge_bases(lookup_commit(head),
+				remoteheads->item, 1);
+	else {
+		struct commit_list *list = remoteheads;
+		commit_list_insert(lookup_commit(head), &list);
+		common = get_octopus_merge_bases(list);
+		free(list);
+	}
+
+	update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
+		DIE_ON_ERR);
+
+	if (!common)
+		; /* No common ancestors found. We need a real merge. */
+	else if (!remoteheads->next && !common->next &&
+			common->item == remoteheads->item) {
+		/*
+		 * If head can reach all the merge then we are up to date.
+		 * but first the most common case of merging one remote.
+		 */
+		finish_up_to_date("Already up-to-date.");
+		return 0;
+	} else if (allow_fast_forward && !remoteheads->next &&
+			!common->next &&
+			!hashcmp(common->item->object.sha1, head)) {
+		/* Again the most common case of merging one remote. */
+		struct strbuf msg;
+		struct object *o;
+		char hex[41];
+
+		strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+
+		printf("Updating %s..%s\n",
+			hex,
+			find_unique_abbrev(remoteheads->item->object.sha1,
+			DEFAULT_ABBREV));
+		refresh_cache(REFRESH_QUIET);
+		strbuf_init(&msg, 0);
+		strbuf_addstr(&msg, "Fast forward");
+		if (have_message)
+			strbuf_addstr(&msg,
+				" (no commit created; -m option ignored)");
+		o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
+			0, NULL, OBJ_COMMIT);
+		if (!o)
+			return 1;
+
+		if (checkout_fast_forward(head, remoteheads->item->object.sha1))
+			return 1;
+
+		finish(o->sha1, msg.buf);
+		drop_save();
+		return 0;
+	} else if (!remoteheads->next && common->next)
+		;
+		/*
+		 * We are not doing octopus and not fast forward.  Need
+		 * a real merge.
+		 */
+	else if (!remoteheads->next && !common->next && option_commit) {
+		/*
+		 * We are not doing octopus, not fast forward, and have
+		 * only one common.
+		 */
+		refresh_cache(REFRESH_QUIET);
+		if (allow_trivial) {
+			/* See if it is really trivial. */
+			git_committer_info(IDENT_ERROR_ON_NO_NAME);
+			printf("Trying really trivial in-index merge...\n");
+			if (!read_tree_trivial(common->item->object.sha1,
+					head, remoteheads->item->object.sha1))
+				return merge_trivial();
+			printf("Nope.\n");
+		}
+	} else {
+		/*
+		 * An octopus.  If we can reach all the remote we are up
+		 * to date.
+		 */
+		int up_to_date = 1;
+		struct commit_list *j;
+
+		for (j = remoteheads; j; j = j->next) {
+			struct commit_list *common_one;
+
+			/*
+			 * Here we *have* to calculate the individual
+			 * merge_bases again, otherwise "git merge HEAD^
+			 * HEAD^^" would be missed.
+			 */
+			common_one = get_merge_bases(lookup_commit(head),
+				j->item, 1);
+			if (hashcmp(common_one->item->object.sha1,
+				j->item->object.sha1)) {
+				up_to_date = 0;
+				break;
+			}
+		}
+		if (up_to_date) {
+			finish_up_to_date("Already up-to-date. Yeeah!");
+			return 0;
+		}
+	}
+
+	/* We are going to make a new commit. */
+	git_committer_info(IDENT_ERROR_ON_NO_NAME);
+
+	/*
+	 * At this point, we need a real merge.  No matter what strategy
+	 * we use, it would operate on the index, possibly affecting the
+	 * working tree, and when resolved cleanly, have the desired
+	 * tree in the index -- this means that the index must be in
+	 * sync with the head commit.  The strategies are responsible
+	 * to ensure this.
+	 */
+	if (enabled_strategies != 1) {
+		/*
+		 * Stash away the local changes so that we can try more
+		 * than one.
+		 */
+		save_state();
+	} else {
+		memcpy(stash, null_sha1, 20);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(all_strategy); i++) {
+		int ret;
+		if (!all_strategy[i].enabled)
+			continue;
+		if (first_strategy) {
+			first_strategy = 0;
+			printf("Rewinding the tree to pristine...\n");
+			restore_state();
+		}
+		if (enabled_strategies != 1)
+			printf("Trying merge strategy %s...\n",
+				all_strategy[i].name);
+		/*
+		 * Remember which strategy left the state in the working
+		 * tree.
+		 */
+		wt_strategy = all_strategy[i].name;
+
+		ret = try_merge_strategy(all_strategy[i].name,
+			common, head_arg);
+		if (!option_commit && !ret) {
+			merge_was_ok = 1;
+			/*
+			 * This is necessary here just to avoid writing
+			 * the tree, but later we will *not* exit with
+			 * status code 1 because merge_was_ok is set.
+			 */
+			ret = 1;
+		}
+
+		if (ret) {
+			/*
+			 * The backend exits with 1 when conflicts are
+			 * left to be resolved, with 2 when it does not
+			 * handle the given merge at all.
+			 */
+			if (ret == 1) {
+				int cnt = evaluate_result();
+
+				if (best_cnt <= 0 || cnt <= best_cnt) {
+					best_strategy = all_strategy[i].name;
+					best_cnt = cnt;
+				}
+			}
+			if (merge_was_ok)
+				break;
+			else
+				continue;
+		}
+
+		/* Automerge succeeded. */
+		write_tree_trivial(result_tree);
+		automerge_was_ok = 1;
+		break;
+	}
+
+	/*
+	 * If we have a resulting tree, that means the strategy module
+	 * auto resolved the merge cleanly.
+	 */
+	if (automerge_was_ok)
+		return finish_automerge(common, result_tree, wt_strategy);
+
+	/*
+	 * Pick the result from the best strategy and have the user fix
+	 * it up.
+	 */
+	if (!best_strategy) {
+		restore_state();
+		if (enabled_strategies > 1)
+			fprintf(stderr,
+				"No merge strategy handled the merge.\n");
+		else
+			fprintf(stderr, "Merge with strategy %s failed.\n",
+				wt_strategy);
+		return 2;
+	} else if (best_strategy == wt_strategy)
+		; /* We already have its result in the working tree. */
+	else {
+		printf("Rewinding the tree to pristine...\n");
+		restore_state();
+		printf("Using the %s to prepare resolving by hand.\n",
+			best_strategy);
+		try_merge_strategy(best_strategy, common, head_arg);
+	}
+
+	if (squash)
+		finish(NULL, NULL);
+	else {
+		int fd;
+		struct commit_list *j;
+
+		for (j = remoteheads; j; j = j->next)
+			strbuf_addf(&buf, "%s\n",
+				sha1_to_hex(j->item->object.sha1));
+		fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
+		if (fd < 0)
+			die("Could open %s for writing",
+				git_path("MERGE_HEAD"));
+		if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+			die("Could not write to %s", git_path("MERGE_HEAD"));
+		close(fd);
+		strbuf_addch(&merge_msg, '\n');
+		fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+		if (fd < 0)
+			die("Could open %s for writing", git_path("MERGE_MSG"));
+		if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
+			merge_msg.len)
+			die("Could not write to %s", git_path("MERGE_MSG"));
+		close(fd);
+	}
+
+	if (merge_was_ok) {
+		fprintf(stderr, "Automatic merge went well; "
+			"stopped before committing as requested\n");
+		return 0;
+	} else
+		return suggest_conflicts();
+}
diff --git a/builtin.h b/builtin.h
index 05ee56f..0e605d4 100644
--- a/builtin.h
+++ b/builtin.h
@@ -64,6 +64,7 @@ extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_ls_remote(int argc, const char **argv, const char *prefix);
 extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
 extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
+extern int cmd_merge(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
diff --git a/git-merge.sh b/contrib/examples/git-merge.sh
similarity index 100%
rename from git-merge.sh
rename to contrib/examples/git-merge.sh
diff --git a/git.c b/git.c
index 2fbe96b..770aadd 100644
--- a/git.c
+++ b/git.c
@@ -271,6 +271,7 @@ static void handle_internal_command(int argc, const char **argv)
 		{ "ls-remote", cmd_ls_remote },
 		{ "mailinfo", cmd_mailinfo },
 		{ "mailsplit", cmd_mailsplit },
+		{ "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
 		{ "merge-base", cmd_merge_base, RUN_SETUP },
 		{ "merge-file", cmd_merge_file },
 		{ "merge-ours", cmd_merge_ours, RUN_SETUP },
diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh
index f3a4bb2..fcb8285 100755
--- a/t/t7602-merge-octopus-many.sh
+++ b/t/t7602-merge-octopus-many.sh
@@ -23,7 +23,7 @@ test_expect_success 'setup' '
 	done
 '
 
-test_expect_failure 'merge c1 with c2, c3, c4, ... c29' '
+test_expect_success 'merge c1 with c2, c3, c4, ... c29' '
 	git reset --hard c1 &&
 	i=2 &&
 	refs="" &&
-- 
1.5.6.1.322.ge904b.dirty

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH] Build in merge
  2008-07-07 23:42                                                               ` [PATCH] " Miklos Vajna
@ 2008-07-08  0:32                                                                 ` Junio C Hamano
  2008-07-08  0:53                                                                   ` Junio C Hamano
  2008-07-08  1:00                                                                   ` Miklos Vajna
  0 siblings, 2 replies; 81+ messages in thread
From: Junio C Hamano @ 2008-07-08  0:32 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: To: Junio C Hamano, git, Johannes Schindelin, Olivier Marin

Miklos Vajna <vmiklos@frugalware.org> writes:

> Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
> Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
> ---
>
> On Mon, Jul 07, 2008 at 11:15:09AM -0700, Junio C Hamano <gitster@pobox.com> wrote:
>> I do not get you on this point.  Which one is nicer?
>>
>>  (1) Have two lists, perhaps all_* and user_*.  The logic that finds a
>>      strategy searches in two lists.  The logic that checks if a given
>>      strategy is built-in checks if it is on all_* list.
>>
>>  (2) Have a single list, but add a boolean "unsigned is_builtin:1" to
>>  each
>>      element of it.  The logic that finds a strategy looks in this
>>      single
>>      list.  The logic that checks if a given strategy is built-in
>>      looks at
>>      the strategy instance and it has the bit already.
>>
>> You seem to be advocating (1) but I do not understand why...
>
> Ah, OK. For now, I just added an "unsigned enabled:1;". Later we can add
> an "unsigned is_buildin:1;" as well, but currently we die with earlier
> with a "Could not find merge strategy" error message, so is_builtin
> would be always true.
>
> So here is a version, this time without the use_strategies list.

That is not what I meant.  I am afraid perhaps I misunderstood what you
were talking about.

When/if you allow user defined new strategies, then you have a choice:

 (1) find "git-merge-*" in path, add them to the single all_strategies[]
     list (but you will do the ALLOC_GROW() business so you would need to
     use the one you currently have as static form to prime the real list),
     and look for "foo" strategy when "-s foo" is given from that single
     list, or

 (2) find "git-merge-*" in path, add them to a separate user_strategies[]
     list, and look for "foo" strategy when "-s foo" is given from the
     user_strategies[] list and all_strategies[] list (all_strategies[]
     should perhaps be renamed to builtin_strategies[] if you go that
     route).

The comparison I gave was between the above two.  But the change you are
talking about is completely different, isn't it?

The part that records which strategies were specified from the command
line *in what order* via "-s foo" switches should remain list of pointers
into "struct strategy", which is called "struct strategy **use_strategies"
in the code and corresponds to the $use_strategies variable in the
scripted version.  The order of these is important, as that defines in
which order the strategies are tried [*1*].  If you go route (1), these
pointers will all be pointing at elements in all_strategies[]; with route
(2) they may be pointing at either all_strageties[] element or
user_strategies[] element.

If you are never going to say "available strategies are these" after you
start supporting user-defined strategy, then you do not necessarily need
to do the "find 'git-merge-*' in path, add them to ..." step above, in
which case it would be Ok not to scan the path and add them to
all_strategies[] (in route (1)) nor user_strategies[] (in route (2)).
Instead, you would just create a new "struct strategy" instance lazily
when the user gave "-s foo" and "foo" is not one of the built-in strategy.
You would put that at the tail of "struct strategy **use_strategy" array,
and iterate over use_strategy in the order they are given on the command
line.


[Footnote]

*1* Personally, I find the importance of this dubious in practice, as I
said earlier, I do not think it would work well to try different
strategies and pick the best one --- evaluating which result is the *best*
is difficult.  If you want to stay compatible with the scripted version,
however, you cannot just mark entries in all_strategies[] with boolean and
iterate over them in the order that all_strageties[] define them.  You
need to try them in the order the user specified.

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH] Build in merge
  2008-07-08  0:32                                                                 ` Junio C Hamano
@ 2008-07-08  0:53                                                                   ` Junio C Hamano
  2008-07-08  1:18                                                                     ` Miklos Vajna
  2008-07-08  1:00                                                                   ` Miklos Vajna
  1 sibling, 1 reply; 81+ messages in thread
From: Junio C Hamano @ 2008-07-08  0:53 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: To: Junio C Hamano, git, Johannes Schindelin, Olivier Marin

Junio C Hamano <gitster@pobox.com> writes:

> That is not what I meant.  I am afraid perhaps I misunderstood what you
> were talking about.
> ...
> The comparison I gave was between the above two.  But the change you are
> talking about is completely different, isn't it?
>
> The part that records which strategies were specified from the command
> line *in what order* via "-s foo" switches should remain list of pointers
> into "struct strategy", which is called "struct strategy **use_strategies"
> in the code and corresponds to the $use_strategies variable in the
> scripted version.

Here is what I meant to suggest; sorry for confusing you with an initial
typo of having only one pointer in front of use_strategies "array of
pointers".  Applies on top of your previous round.

--

 builtin-merge.c |   58 +++++++++++++++++++++++++++---------------------------
 1 files changed, 29 insertions(+), 29 deletions(-)

diff --git a/builtin-merge.c b/builtin-merge.c
index 7312997..b2e702a 100644
--- a/builtin-merge.c
+++ b/builtin-merge.c
@@ -44,7 +44,7 @@ static int allow_trivial = 1, have_message;
 static struct strbuf merge_msg;
 static struct commit_list *remoteheads;
 static unsigned char head[20], stash[20];
-static struct strategy *use_strategies;
+static struct strategy **use_strategies;
 static size_t use_strategies_nr, use_strategies_alloc;
 static const char *branch;
 
@@ -74,44 +74,44 @@ static int option_parse_message(const struct option *opt,
 	return 0;
 }
 
-static int strategy_lookup(const char *path)
+static struct strategy *get_strategy(const char *name)
 {
 	int i;
 
-	if (!path)
-		return -1;
+	if (!name)
+		return NULL;
 
 	for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
-		if (!strcmp(path, all_strategy[i].name))
-			return all_strategy[i].attr;
-	return -1;
+		if (!strcmp(name, all_strategy[i].name))
+			return &all_strategy[i];
+	return NULL;
 }
 
-static inline void append_strategy(const char *name, unsigned attr)
+static void append_strategy(struct strategy *s)
 {
 	ALLOC_GROW(use_strategies, use_strategies_nr + 1, use_strategies_alloc);
-	use_strategies[use_strategies_nr].name = name;
-	use_strategies[use_strategies_nr++].attr = attr;
+	use_strategies[use_strategies_nr++] = s;
 }
 
 static int option_parse_strategy(const struct option *opt,
-	const char *arg, int unset)
+				 const char *name, int unset)
 {
-	int i, attr;
+	int i;
+	struct strategy *s;
 
 	if (unset)
 		return 0;
 
-	attr = strategy_lookup(arg);
+	s = get_strategy(name);
 
-	if (attr >= 0)
-		append_strategy(arg, attr);
+	if (s)
+		append_strategy(s);
 	else {
 		struct strbuf err;
 		strbuf_init(&err, 0);
 		for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
 			strbuf_addf(&err, " %s", all_strategy[i].name);
-		fprintf(stderr, "Could not find merge strategy '%s'.\n", arg);
+		fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
 		fprintf(stderr, "Available strategies are:%s.\n", err.buf);
 		exit(1);
 	}
@@ -643,18 +643,18 @@ static void add_strategies(const char *string, unsigned attr)
 	split_merge_strategies(string, &list, &list_nr, &list_alloc);
 	if (list != NULL) {
 		for (i = 0; i < list_nr; i++) {
-			int attr;
+			struct strategy *s;
 
-			attr = strategy_lookup(list[i].name);
-			if (attr >= 0)
-				append_strategy(list[i].name, attr);
+			s = get_strategy(list[i].name);
+			if (s)
+				append_strategy(s);
 		}
 		return;
 	}
 	for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
 		if (all_strategy[i].attr & attr)
-			append_strategy(all_strategy[i].name,
-				all_strategy[i].attr);
+			append_strategy(&all_strategy[i]);
+
 }
 
 static int merge_trivial(void)
@@ -903,9 +903,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 	}
 
 	for (i = 0; i < use_strategies_nr; i++) {
-		if (use_strategies[i].attr & NO_FAST_FORWARD)
+		if (use_strategies[i]->attr & NO_FAST_FORWARD)
 			allow_fast_forward = 0;
-		if (use_strategies[i].attr & NO_TRIVIAL)
+		if (use_strategies[i]->attr & NO_TRIVIAL)
 			allow_trivial = 0;
 	}
 
@@ -1043,14 +1043,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		}
 		if (use_strategies_nr != 1)
 			printf("Trying merge strategy %s...\n",
-				use_strategies[i].name);
+				use_strategies[i]->name);
 		/*
 		 * Remember which strategy left the state in the working
 		 * tree.
 		 */
-		wt_strategy = use_strategies[i].name;
+		wt_strategy = use_strategies[i]->name;
 
-		ret = try_merge_strategy(use_strategies[i].name,
+		ret = try_merge_strategy(use_strategies[i]->name,
 			common, head_arg);
 		if (!option_commit && !ret) {
 			merge_was_ok = 1;
@@ -1072,7 +1072,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 				int cnt = evaluate_result();
 
 				if (best_cnt <= 0 || cnt <= best_cnt) {
-					best_strategy = use_strategies[i].name;
+					best_strategy = use_strategies[i]->name;
 					best_cnt = cnt;
 				}
 			}
@@ -1106,7 +1106,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 				"No merge strategy handled the merge.\n");
 		else
 			fprintf(stderr, "Merge with strategy %s failed.\n",
-				use_strategies[0].name);
+				use_strategies[0]->name);
 		return 2;
 	} else if (best_strategy == wt_strategy)
 		; /* We already have its result in the working tree. */

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH] Build in merge
  2008-07-08  0:32                                                                 ` Junio C Hamano
  2008-07-08  0:53                                                                   ` Junio C Hamano
@ 2008-07-08  1:00                                                                   ` Miklos Vajna
  2008-07-08  1:05                                                                     ` Junio C Hamano
  1 sibling, 1 reply; 81+ messages in thread
From: Miklos Vajna @ 2008-07-08  1:00 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

[-- Attachment #1: Type: text/plain, Size: 3544 bytes --]

On Mon, Jul 07, 2008 at 05:32:50PM -0700, Junio C Hamano <gitster@pobox.com> wrote:
> > So here is a version, this time without the use_strategies list.
> 
> That is not what I meant.  I am afraid perhaps I misunderstood what you
> were talking about.
> 
> When/if you allow user defined new strategies, then you have a choice:
> 
>  (1) find "git-merge-*" in path, add them to the single all_strategies[]
>      list (but you will do the ALLOC_GROW() business so you would need to
>      use the one you currently have as static form to prime the real list),
>      and look for "foo" strategy when "-s foo" is given from that single
>      list, or
> 
>  (2) find "git-merge-*" in path, add them to a separate user_strategies[]
>      list, and look for "foo" strategy when "-s foo" is given from the
>      user_strategies[] list and all_strategies[] list (all_strategies[]
>      should perhaps be renamed to builtin_strategies[] if you go that
>      route).

OK, I see now. Actually I think the primary problem with a custom
strategy is that we do not know its flags, IOW if it handles an octopus,
etc. So I think there are two questions here:

1) How to tell git-merge the flags of a custom strategy? Or: is it
necessary at all?

I could imagine the following situations:

1) A project has code in a repo, some documentation and po files.

The first two can be merged with builtin strategies, the later probably
needs a custom merge driver.

So, in most cases recursive is fine, but sometimes the maintainer wants
to say 'git pull -s po'.

In this case the flags of 'po' does not really matter.

2) Someone is not happy with the current recursive strategy and writes
from scratch a new one. He/she puts it to pull.twohead, so it will be
tried before recursive.

To sum up: I am not sure what would be the benefit of being able to
specify flags for strategies. However, if we want so, it would be good
to discuss how it should be done.

> The part that records which strategies were specified from the command
> line *in what order* via "-s foo" switches should remain list of pointers
> into "struct strategy", which is called "struct strategy **use_strategies"
> in the code and corresponds to the $use_strategies variable in the
> scripted version.  The order of these is important, as that defines in
> which order the strategies are tried [*1*].  If you go route (1), these
> pointers will all be pointing at elements in all_strategies[]; with route
> (2) they may be pointing at either all_strageties[] element or
> user_strategies[] element.

I see the problem, I lost the order at the moment.

> If you are never going to say "available strategies are these" after you
> start supporting user-defined strategy, then you do not necessarily need
> to do the "find 'git-merge-*' in path, add them to ..." step above, in
> which case it would be Ok not to scan the path and add them to
> all_strategies[] (in route (1)) nor user_strategies[] (in route (2)).
> Instead, you would just create a new "struct strategy" instance lazily
> when the user gave "-s foo" and "foo" is not one of the built-in strategy.
> You would put that at the tail of "struct strategy **use_strategy" array,
> and iterate over use_strategy in the order they are given on the command
> line.

I guess it would be nice to adjust the error message and scanning path,
but that's something - I guess - that should be done after the rewrite
is complete and there are no more issues with it.

[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

* Re: [PATCH] Build in merge
  2008-07-08  1:00                                                                   ` Miklos Vajna
@ 2008-07-08  1:05                                                                     ` Junio C Hamano
  2008-07-08  1:41                                                                       ` Miklos Vajna
  0 siblings, 1 reply; 81+ messages in thread
From: Junio C Hamano @ 2008-07-08  1:05 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: git, Johannes Schindelin, Olivier Marin

Miklos Vajna <vmiklos@frugalware.org> writes:

> I could imagine the following situations:
>
> 1) A project has code in a repo, some documentation and po files.
>
> The first two can be merged with builtin strategies, the later probably
> needs a custom merge driver.

Per-path merge is probably better handled with custom ll-merge driver
anyway.  See gitattributes(5).

> 2) Someone is not happy with the current recursive strategy and writes
> from scratch a new one. He/she puts it to pull.twohead, so it will be
> tried before recursive.

That is fine.

> To sum up: I am not sure what would be the benefit of being able to
> specify flags for strategies. However, if we want so, it would be good
> to discuss how it should be done.

It wasn't *me* ;-) who wanted to add these "flags".

I think it does not matter what "my-strategy" does unless "-s my-strategy"
(or pull.twohead) is explicitly given by the user, and at that time,
DEFAULT_* options should not matter.  It probably is Ok to allow fast
forward and trivial merges to them.  We'll see.

>> The part that records which strategies were specified from the command
>> line *in what order* via "-s foo" switches should remain list of pointers
>> into "struct strategy", which is called "struct strategy **use_strategies"
>> in the code and corresponds to the $use_strategies variable in the
>> scripted version.  The order of these is important, as that defines in
>> which order the strategies are tried [*1*].  If you go route (1), these
>> pointers will all be pointing at elements in all_strategies[]; with route
>> (2) they may be pointing at either all_strageties[] element or
>> user_strategies[] element.
>
> I see the problem, I lost the order at the moment.

That's Ok.  See the other patch on top of your previous one.

^ permalink raw reply	[flat|nested] 81+ messages in thread

* [PATCH] Build in merge
  2008-07-08  0:53                                                                   ` Junio C Hamano
@ 2008-07-08  1:18                                                                     ` Miklos Vajna
  0 siblings, 0 replies; 81+ messages in thread
From: Miklos Vajna @ 2008-07-08  1:18 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---

On Mon, Jul 07, 2008 at 05:53:23PM -0700, Junio C Hamano <gitster@pobox.com> wrote:
> Here is what I meant to suggest; sorry for confusing you with an
> initial
> typo of having only one pointer in front of use_strategies "array of
> pointers".  Applies on top of your previous round.

Funny enough, I just did almost the same before reading your mail,
except that I did not do the suggested strategy_lookup() ->
get_strategy() rename and such.

Here is a squashed patch. (Previous round + your patch.)

 Makefile                                      |    2 +-
 builtin-merge.c                               | 1153 +++++++++++++++++++++++++
 builtin.h                                     |    1 +
 git-merge.sh => contrib/examples/git-merge.sh |    0
 git.c                                         |    1 +
 t/t7602-merge-octopus-many.sh                 |    2 +-
 6 files changed, 1157 insertions(+), 2 deletions(-)
 create mode 100644 builtin-merge.c
 rename git-merge.sh => contrib/examples/git-merge.sh (100%)

diff --git a/Makefile b/Makefile
index bf77292..fbc53e9 100644
--- a/Makefile
+++ b/Makefile
@@ -240,7 +240,6 @@ SCRIPT_SH += git-lost-found.sh
 SCRIPT_SH += git-merge-octopus.sh
 SCRIPT_SH += git-merge-one-file.sh
 SCRIPT_SH += git-merge-resolve.sh
-SCRIPT_SH += git-merge.sh
 SCRIPT_SH += git-merge-stupid.sh
 SCRIPT_SH += git-mergetool.sh
 SCRIPT_SH += git-parse-remote.sh
@@ -515,6 +514,7 @@ BUILTIN_OBJS += builtin-ls-remote.o
 BUILTIN_OBJS += builtin-ls-tree.o
 BUILTIN_OBJS += builtin-mailinfo.o
 BUILTIN_OBJS += builtin-mailsplit.o
+BUILTIN_OBJS += builtin-merge.o
 BUILTIN_OBJS += builtin-merge-base.o
 BUILTIN_OBJS += builtin-merge-file.o
 BUILTIN_OBJS += builtin-merge-ours.o
diff --git a/builtin-merge.c b/builtin-merge.c
new file mode 100644
index 0000000..d6bd144
--- /dev/null
+++ b/builtin-merge.c
@@ -0,0 +1,1153 @@
+/*
+ * Builtin "git merge"
+ *
+ * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
+ *
+ * Based on git-merge.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "parse-options.h"
+#include "builtin.h"
+#include "run-command.h"
+#include "diff.h"
+#include "refs.h"
+#include "commit.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
+#include "dir.h"
+#include "utf8.h"
+#include "log-tree.h"
+#include "color.h"
+
+#define DEFAULT_TWOHEAD (1<<0)
+#define DEFAULT_OCTOPUS (1<<1)
+#define NO_FAST_FORWARD (1<<2)
+#define NO_TRIVIAL      (1<<3)
+
+struct strategy {
+	const char *name;
+	unsigned attr;
+};
+
+static const char * const builtin_merge_usage[] = {
+	"git-merge [options] <remote>...",
+	"git-merge [options] <msg> HEAD <remote>",
+	NULL
+};
+
+static int show_diffstat = 1, option_log, squash;
+static int option_commit = 1, allow_fast_forward = 1;
+static int allow_trivial = 1, have_message;
+static struct strbuf merge_msg;
+static struct commit_list *remoteheads;
+static unsigned char head[20], stash[20];
+static struct strategy **use_strategies;
+static size_t use_strategies_nr, use_strategies_alloc;
+static const char *branch;
+
+static struct strategy all_strategy[] = {
+	{ "recur",      NO_TRIVIAL },
+	{ "recursive",  DEFAULT_TWOHEAD | NO_TRIVIAL },
+	{ "octopus",    DEFAULT_OCTOPUS },
+	{ "resolve",    0 },
+	{ "stupid",     0 },
+	{ "ours",       NO_FAST_FORWARD | NO_TRIVIAL },
+	{ "subtree",    NO_FAST_FORWARD | NO_TRIVIAL },
+};
+
+static const char *pull_twohead, *pull_octopus;
+
+static int option_parse_message(const struct option *opt,
+	const char *arg, int unset)
+{
+	struct strbuf *buf = opt->value;
+
+	if (unset)
+		strbuf_setlen(buf, 0);
+	else {
+		strbuf_addf(buf, "%s\n\n", arg);
+		have_message = 1;
+	}
+	return 0;
+}
+
+static struct strategy *get_strategy(const char *name)
+{
+	int i;
+
+	if (!name)
+		return NULL;
+
+	for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+		if (!strcmp(name, all_strategy[i].name))
+			return &all_strategy[i];
+	return NULL;
+}
+
+static void append_strategy(struct strategy *s)
+{
+	ALLOC_GROW(use_strategies, use_strategies_nr + 1, use_strategies_alloc);
+	use_strategies[use_strategies_nr++] = s;
+}
+
+static int option_parse_strategy(const struct option *opt,
+				 const char *name, int unset)
+{
+	int i;
+	struct strategy *s;
+
+	if (unset)
+		return 0;
+
+	s = get_strategy(name);
+
+	if (s)
+		append_strategy(s);
+	else {
+		struct strbuf err;
+		strbuf_init(&err, 0);
+		for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+			strbuf_addf(&err, " %s", all_strategy[i].name);
+		fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
+		fprintf(stderr, "Available strategies are:%s.\n", err.buf);
+		exit(1);
+	}
+	return 0;
+}
+
+static int option_parse_n(const struct option *opt,
+		const char *arg, int unset)
+{
+	show_diffstat = unset;
+	return 0;
+}
+
+static struct option builtin_merge_options[] = {
+	{ OPTION_CALLBACK, 'n', NULL, NULL, NULL,
+		"do not show a diffstat at the end of the merge",
+		PARSE_OPT_NOARG, option_parse_n },
+	OPT_BOOLEAN(0, "stat", &show_diffstat,
+		"show a diffstat at the end of the merge"),
+	OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
+	OPT_BOOLEAN(0, "log", &option_log,
+		"add list of one-line log to merge commit message"),
+	OPT_BOOLEAN(0, "squash", &squash,
+		"create a single commit instead of doing a merge"),
+	OPT_BOOLEAN(0, "commit", &option_commit,
+		"perform a commit if the merge succeeds (default)"),
+	OPT_BOOLEAN(0, "ff", &allow_fast_forward,
+		"allow fast forward (default)"),
+	OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
+		"merge strategy to use", option_parse_strategy),
+	OPT_CALLBACK('m', "message", &merge_msg, "message",
+		"message to be used for the merge commit (if any)",
+		option_parse_message),
+	OPT_END()
+};
+
+/* Cleans up metadata that is uninteresting after a succeeded merge. */
+static void drop_save(void)
+{
+	unlink(git_path("MERGE_HEAD"));
+	unlink(git_path("MERGE_MSG"));
+}
+
+static void save_state(void)
+{
+	int len;
+	struct child_process cp;
+	struct strbuf buffer = STRBUF_INIT;
+	const char *argv[] = {"stash", "create", NULL};
+
+	memset(&cp, 0, sizeof(cp));
+	cp.argv = argv;
+	cp.out = -1;
+	cp.git_cmd = 1;
+
+	if (start_command(&cp))
+		die("could not run stash.");
+	len = strbuf_read(&buffer, cp.out, 1024);
+	close(cp.out);
+
+	if (finish_command(&cp) || len < 0)
+		die("stash failed");
+	else if (!len)
+		return;
+	strbuf_setlen(&buffer, buffer.len-1);
+	if (get_sha1(buffer.buf, stash))
+		die("not a valid object: %s", buffer.buf);
+}
+
+static void reset_hard(unsigned const char *sha1, int verbose)
+{
+	int i = 0;
+	const char *args[6];
+
+	args[i++] = "read-tree";
+	if (verbose)
+		args[i++] = "-v";
+	args[i++] = "--reset";
+	args[i++] = "-u";
+	args[i++] = sha1_to_hex(sha1);
+	args[i] = NULL;
+
+	if (run_command_v_opt(args, RUN_GIT_CMD))
+		die("read-tree failed");
+}
+
+static void restore_state(void)
+{
+	struct strbuf sb;
+	const char *args[] = { "stash", "apply", NULL, NULL };
+
+	if (is_null_sha1(stash))
+		return;
+
+	reset_hard(head, 1);
+
+	strbuf_init(&sb, 0);
+	args[2] = sha1_to_hex(stash);
+
+	/*
+	 * It is OK to ignore error here, for example when there was
+	 * nothing to restore.
+	 */
+	run_command_v_opt(args, RUN_GIT_CMD);
+
+	strbuf_release(&sb);
+	refresh_cache(REFRESH_QUIET);
+}
+
+/* This is called when no merge was necessary. */
+static void finish_up_to_date(const char *msg)
+{
+	printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
+	drop_save();
+}
+
+static void squash_message(void)
+{
+	struct rev_info rev;
+	struct commit *commit;
+	struct strbuf out;
+	struct commit_list *j;
+	int fd;
+
+	printf("Squash commit -- not updating HEAD\n");
+	fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+	if (fd < 0)
+		die("Could not write to %s", git_path("SQUASH_MSG"));
+
+	init_revisions(&rev, NULL);
+	rev.ignore_merges = 1;
+	rev.commit_format = CMIT_FMT_MEDIUM;
+
+	commit = lookup_commit(head);
+	commit->object.flags |= UNINTERESTING;
+	add_pending_object(&rev, &commit->object, NULL);
+
+	for (j = remoteheads; j; j = j->next)
+		add_pending_object(&rev, &j->item->object, NULL);
+
+	setup_revisions(0, NULL, &rev, NULL);
+	if (prepare_revision_walk(&rev))
+		die("revision walk setup failed");
+
+	strbuf_init(&out, 0);
+	strbuf_addstr(&out, "Squashed commit of the following:\n");
+	while ((commit = get_revision(&rev)) != NULL) {
+		strbuf_addch(&out, '\n');
+		strbuf_addf(&out, "commit %s\n",
+			sha1_to_hex(commit->object.sha1));
+		pretty_print_commit(rev.commit_format, commit, &out, rev.abbrev,
+			NULL, NULL, rev.date_mode, 0);
+	}
+	write(fd, out.buf, out.len);
+	close(fd);
+	strbuf_release(&out);
+}
+
+static int run_hook(const char *name)
+{
+	struct child_process hook;
+	const char *argv[3], *env[2];
+	char index[PATH_MAX];
+
+	argv[0] = git_path("hooks/%s", name);
+	if (access(argv[0], X_OK) < 0)
+		return 0;
+
+	snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", get_index_file());
+	env[0] = index;
+	env[1] = NULL;
+
+	if (squash)
+		argv[1] = "1";
+	else
+		argv[1] = "0";
+	argv[2] = NULL;
+
+	memset(&hook, 0, sizeof(hook));
+	hook.argv = argv;
+	hook.no_stdin = 1;
+	hook.stdout_to_stderr = 1;
+	hook.env = env;
+
+	return run_command(&hook);
+}
+
+static void finish(const unsigned char *new_head, const char *msg)
+{
+	struct strbuf reflog_message;
+
+	strbuf_init(&reflog_message, 0);
+	if (!msg)
+		strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
+	else {
+		printf("%s\n", msg);
+		strbuf_addf(&reflog_message, "%s: %s",
+			getenv("GIT_REFLOG_ACTION"), msg);
+	}
+	if (squash) {
+		squash_message();
+	} else {
+		if (!merge_msg.len)
+			printf("No merge message -- not updating HEAD\n");
+		else {
+			const char *argv_gc_auto[] = { "gc", "--auto", NULL };
+			update_ref(reflog_message.buf, "HEAD",
+				new_head, head, 0,
+				DIE_ON_ERR);
+			/*
+			 * We ignore errors in 'gc --auto', since the
+			 * user should see them.
+			 */
+			run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+		}
+	}
+	if (new_head && show_diffstat) {
+		struct diff_options opts;
+		diff_setup(&opts);
+		opts.output_format |=
+			DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+		opts.detect_rename = DIFF_DETECT_RENAME;
+		if (diff_use_color_default > 0)
+			DIFF_OPT_SET(&opts, COLOR_DIFF);
+		if (diff_setup_done(&opts) < 0)
+			die("diff_setup_done failed");
+		diff_tree_sha1(head, new_head, "", &opts);
+		diffcore_std(&opts);
+		diff_flush(&opts);
+	}
+
+	/* Run a post-merge hook */
+	run_hook("post-merge");
+
+	strbuf_release(&reflog_message);
+}
+
+/* Get the name for the merge commit's message. */
+static void merge_name(const char *remote, struct strbuf *msg)
+{
+	struct object *remote_head;
+	unsigned char branch_head[20], buf_sha[20];
+	struct strbuf buf;
+	const char *ptr;
+	int len, early;
+
+	memset(branch_head, 0, sizeof(branch_head));
+	remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+	if (!remote_head)
+		die("'%s' does not point to a commit", remote);
+
+	strbuf_init(&buf, 0);
+	strbuf_addstr(&buf, "refs/heads/");
+	strbuf_addstr(&buf, remote);
+	resolve_ref(buf.buf, branch_head, 0, 0);
+
+	if (!hashcmp(remote_head->sha1, branch_head)) {
+		strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
+			sha1_to_hex(branch_head), remote);
+		return;
+	}
+
+	/* See if remote matches <name>^^^.. or <name>~<number> */
+	for (len = 0, ptr = remote + strlen(remote);
+	     remote < ptr && ptr[-1] == '^';
+	     ptr--)
+		len++;
+	if (len)
+		early = 1;
+	else {
+		early = 0;
+		ptr = strrchr(remote, '~');
+		if (ptr) {
+			int seen_nonzero = 0;
+
+			len++; /* count ~ */
+			while (*++ptr && isdigit(*ptr)) {
+				seen_nonzero |= (*ptr != '0');
+				len++;
+			}
+			if (*ptr)
+				len = 0; /* not ...~<number> */
+			else if (seen_nonzero)
+				early = 1;
+			else if (len == 1)
+				early = 1; /* "name~" is "name~1"! */
+		}
+	}
+	if (len) {
+		struct strbuf truname = STRBUF_INIT;
+		strbuf_addstr(&truname, "refs/heads/");
+		strbuf_addstr(&truname, remote);
+		strbuf_setlen(&truname, len+11);
+		if (resolve_ref(truname.buf, buf_sha, 0, 0)) {
+			strbuf_addf(msg,
+				    "%s\t\tbranch '%s'%s of .\n",
+				    sha1_to_hex(remote_head->sha1),
+				    truname.buf,
+				    (early ? " (early part)" : ""));
+			return;
+		}
+	}
+
+	if (!strcmp(remote, "FETCH_HEAD") &&
+			!access(git_path("FETCH_HEAD"), R_OK)) {
+		FILE *fp;
+		struct strbuf line;
+		char *ptr;
+
+		strbuf_init(&line, 0);
+		fp = fopen(git_path("FETCH_HEAD"), "r");
+		if (!fp)
+			die("could not open %s for reading: %s",
+				git_path("FETCH_HEAD"), strerror(errno));
+		strbuf_getline(&line, fp, '\n');
+		fclose(fp);
+		ptr = strstr(line.buf, "\tnot-for-merge\t");
+		if (ptr)
+			strbuf_remove(&line, ptr-line.buf+1, 13);
+		strbuf_addbuf(msg, &line);
+		strbuf_release(&line);
+		return;
+	}
+	strbuf_addf(msg, "%s\t\tcommit '%s'\n",
+		sha1_to_hex(remote_head->sha1), remote);
+}
+
+int git_merge_config(const char *k, const char *v, void *cb)
+{
+	if (branch && !prefixcmp(k, "branch.") &&
+		!prefixcmp(k + 7, branch) &&
+		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+		const char **argv;
+		int argc;
+		char *buf;
+
+		buf = xstrdup(v);
+		argc = split_cmdline(buf, &argv);
+		argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+		memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+		argc++;
+		parse_options(argc, argv, builtin_merge_options,
+			      builtin_merge_usage, 0);
+		free(buf);
+	}
+
+	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+		show_diffstat = git_config_bool(k, v);
+	else if (!strcmp(k, "pull.twohead"))
+		return git_config_string(&pull_twohead, k, v);
+	else if (!strcmp(k, "pull.octopus"))
+		return git_config_string(&pull_octopus, k, v);
+	return git_diff_ui_config(k, v, cb);
+}
+
+static int read_tree_trivial(unsigned char *common, unsigned char *head,
+	unsigned char *one)
+{
+	int i, nr_trees = 0;
+	struct tree *trees[MAX_UNPACK_TREES];
+	struct tree_desc t[MAX_UNPACK_TREES];
+	struct unpack_trees_options opts;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.head_idx = 2;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.verbose_update = 1;
+	opts.trivial_merges_only = 1;
+	opts.merge = 1;
+	trees[nr_trees] = parse_tree_indirect(common);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(head);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(one);
+	if (!trees[nr_trees++])
+		return -1;
+	opts.fn = threeway_merge;
+	cache_tree_free(&active_cache_tree);
+	for (i = 0; i < nr_trees; i++) {
+		parse_tree(trees[i]);
+		init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+	}
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+	return 0;
+}
+
+static void write_tree_trivial(unsigned char *sha1)
+{
+	if (write_cache_as_tree(sha1, 0, NULL))
+		die("git write-tree failed to write a tree");
+}
+
+static int try_merge_strategy(const char *strategy, struct commit_list *common,
+	const char *head_arg)
+{
+	const char **args;
+	int i = 0, ret;
+	struct commit_list *j;
+	struct strbuf buf;
+
+	args = xmalloc((4 + commit_list_count(common) +
+			commit_list_count(remoteheads)) * sizeof(char *));
+	strbuf_init(&buf, 0);
+	strbuf_addf(&buf, "merge-%s", strategy);
+	args[i++] = buf.buf;
+	for (j = common; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i++] = "--";
+	args[i++] = head_arg;
+	for (j = remoteheads; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i] = NULL;
+	ret = run_command_v_opt(args, RUN_GIT_CMD);
+	strbuf_release(&buf);
+	i = 1;
+	for (j = common; j; j = j->next)
+		free((void *)args[i++]);
+	i += 2;
+	for (j = remoteheads; j; j = j->next)
+		free((void *)args[i++]);
+	free(args);
+	return -ret;
+}
+
+static void count_diff_files(struct diff_queue_struct *q,
+		struct diff_options *opt, void *data)
+{
+	int *count = data;
+
+	(*count) += q->nr;
+}
+
+static int count_unmerged_entries(void)
+{
+	const struct index_state *state = &the_index;
+	int i, ret = 0;
+
+	for (i = 0; i < state->cache_nr; i++)
+		if (ce_stage(state->cache[i]))
+			ret++;
+
+	return ret;
+}
+
+static int checkout_fast_forward(unsigned char *head, unsigned char *remote)
+{
+	struct tree *trees[MAX_UNPACK_TREES];
+	struct unpack_trees_options opts;
+	struct tree_desc t[MAX_UNPACK_TREES];
+	int i, fd, nr_trees = 0;
+	struct dir_struct dir;
+	struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+	if (read_cache_unmerged())
+		die("you need to resolve your current index first");
+
+	fd = hold_locked_index(lock_file, 1);
+
+	memset(&trees, 0, sizeof(trees));
+	memset(&opts, 0, sizeof(opts));
+	memset(&t, 0, sizeof(t));
+	dir.show_ignored = 1;
+	dir.exclude_per_dir = ".gitignore";
+	opts.dir = &dir;
+
+	opts.head_idx = 1;
+	opts.src_index = &the_index;
+	opts.dst_index = &the_index;
+	opts.update = 1;
+	opts.verbose_update = 1;
+	opts.merge = 1;
+	opts.fn = twoway_merge;
+
+	trees[nr_trees] = parse_tree_indirect(head);
+	if (!trees[nr_trees++])
+		return -1;
+	trees[nr_trees] = parse_tree_indirect(remote);
+	if (!trees[nr_trees++])
+		return -1;
+	for (i = 0; i < nr_trees; i++) {
+		parse_tree(trees[i]);
+		init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+	}
+	if (unpack_trees(nr_trees, t, &opts))
+		return -1;
+	if (write_cache(fd, active_cache, active_nr) ||
+		commit_locked_index(lock_file))
+		die("unable to write new index file");
+	return 0;
+}
+
+static void split_merge_strategies(const char *string, struct strategy **list,
+		int *nr, int *alloc)
+{
+	char *p, *q, *buf;
+
+	if (!string)
+		return;
+
+	buf = xstrdup(string);
+	q = buf;
+	for (;;) {
+		p = strchr(q, ' ');
+		if (!p) {
+			ALLOC_GROW(*list, *nr + 1, *alloc);
+			(*list)[(*nr)++].name = xstrdup(q);
+			free(buf);
+			return;
+		} else {
+			*p = '\0';
+			ALLOC_GROW(*list, *nr + 1, *alloc);
+			(*list)[(*nr)++].name = xstrdup(q);
+			q = ++p;
+		}
+	}
+}
+
+static void add_strategies(const char *string, unsigned attr)
+{
+	struct strategy *list = NULL;
+	int list_alloc = 0, list_nr = 0, i;
+
+	memset(&list, 0, sizeof(list));
+	split_merge_strategies(string, &list, &list_nr, &list_alloc);
+	if (list != NULL) {
+		for (i = 0; i < list_nr; i++) {
+			struct strategy *s;
+
+			s = get_strategy(list[i].name);
+			if (s)
+				append_strategy(s);
+		}
+		return;
+	}
+	for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+		if (all_strategy[i].attr & attr)
+			append_strategy(&all_strategy[i]);
+
+}
+
+static int merge_trivial(void)
+{
+	unsigned char result_tree[20], result_commit[20];
+	struct commit_list parent;
+
+	write_tree_trivial(result_tree);
+	printf("Wonderful.\n");
+	parent.item = remoteheads->item;
+	parent.next = NULL;
+	commit_tree(merge_msg.buf, result_tree, &parent, result_commit);
+	finish(result_commit, "In-index merge");
+	drop_save();
+	return 0;
+}
+
+static int finish_automerge(struct commit_list *common,
+	unsigned char *result_tree,
+	const char *wt_strategy)
+{
+	struct commit_list *parents = NULL, *j;
+	struct strbuf buf = STRBUF_INIT;
+	unsigned char result_commit[20];
+
+	free_commit_list(common);
+	if (allow_fast_forward) {
+		parents = remoteheads;
+		commit_list_insert(lookup_commit(head), &parents);
+		parents = reduce_heads(parents);
+	} else {
+		struct commit_list **pptr = &parents;
+
+		pptr = &commit_list_insert(lookup_commit(head),
+				pptr)->next;
+		for (j = remoteheads; j; j = j->next)
+			pptr = &commit_list_insert(j->item, pptr)->next;
+	}
+	free_commit_list(remoteheads);
+	strbuf_addch(&merge_msg, '\n');
+	commit_tree(merge_msg.buf, result_tree, parents, result_commit);
+	strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
+	finish(result_commit, buf.buf);
+	strbuf_release(&buf);
+	drop_save();
+	return 0;
+}
+
+static int suggest_conflicts(void)
+{
+	FILE *fp;
+	int pos;
+
+	fp = fopen(git_path("MERGE_MSG"), "a");
+	if (!fp)
+		die("Could open %s for writing", git_path("MERGE_MSG"));
+	fprintf(fp, "\nConflicts:\n");
+	for (pos = 0; pos < active_nr; pos++) {
+		struct cache_entry *ce = active_cache[pos];
+
+		if (ce_stage(ce)) {
+			fprintf(fp, "\t%s\n", ce->name);
+			while (pos + 1 < active_nr &&
+					!strcmp(ce->name,
+						active_cache[pos + 1]->name))
+				pos++;
+		}
+	}
+	fclose(fp);
+	rerere();
+	printf("Automatic merge failed; "
+			"fix conflicts and then commit the result.\n");
+	return 1;
+}
+
+static struct commit *is_old_style_invocation(int argc, const char **argv)
+{
+	struct commit *second_token = NULL;
+	if (argc > 1) {
+		unsigned char second_sha1[20];
+
+		if (get_sha1(argv[1], second_sha1))
+			return NULL;
+		second_token = lookup_commit_reference_gently(second_sha1, 0);
+		if (!second_token)
+			die("'%s' is not a commit", argv[1]);
+		if (hashcmp(second_token->object.sha1, head))
+			return NULL;
+	}
+	return second_token;
+}
+
+static int evaluate_result(void)
+{
+	int cnt = 0;
+	struct rev_info rev;
+
+	if (read_cache() < 0)
+		die("failed to read the cache");
+
+	/* Check how many files differ. */
+	init_revisions(&rev, "");
+	setup_revisions(0, NULL, &rev, NULL);
+	rev.diffopt.output_format |=
+		DIFF_FORMAT_CALLBACK;
+	rev.diffopt.format_callback = count_diff_files;
+	rev.diffopt.format_callback_data = &cnt;
+	run_diff_files(&rev, 0);
+
+	/*
+	 * Check how many unmerged entries are
+	 * there.
+	 */
+	cnt += count_unmerged_entries();
+
+	return cnt;
+}
+
+int cmd_merge(int argc, const char **argv, const char *prefix)
+{
+	unsigned char result_tree[20];
+	struct strbuf buf;
+	const char *head_arg;
+	int flag, head_invalid = 0, i;
+	int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
+	struct commit_list *common = NULL;
+	const char *best_strategy = NULL, *wt_strategy = NULL;
+	struct commit_list **remotes = &remoteheads;
+
+	setup_work_tree();
+	if (unmerged_cache())
+		die("You are in the middle of a conflicted merge.");
+
+	/*
+	 * Check if we are _not_ on a detached HEAD, i.e. if there is a
+	 * current branch.
+	 */
+	branch = resolve_ref("HEAD", head, 0, &flag);
+	if (branch && !prefixcmp(branch, "refs/heads/"))
+		branch += 11;
+	if (is_null_sha1(head))
+		head_invalid = 1;
+
+	git_config(git_merge_config, NULL);
+
+	/* for color.ui */
+	if (diff_use_color_default == -1)
+		diff_use_color_default = git_use_color_default;
+
+	argc = parse_options(argc, argv, builtin_merge_options,
+			builtin_merge_usage, 0);
+
+	if (squash) {
+		if (!allow_fast_forward)
+			die("You cannot combine --squash with --no-ff.");
+		option_commit = 0;
+	}
+
+	if (!argc)
+		usage_with_options(builtin_merge_usage,
+			builtin_merge_options);
+
+	/*
+	 * This could be traditional "merge <msg> HEAD <commit>..."  and
+	 * the way we can tell it is to see if the second token is HEAD,
+	 * but some people might have misused the interface and used a
+	 * committish that is the same as HEAD there instead.
+	 * Traditional format never would have "-m" so it is an
+	 * additional safety measure to check for it.
+	 */
+	strbuf_init(&buf, 0);
+
+	if (!have_message && is_old_style_invocation(argc, argv)) {
+		strbuf_addstr(&merge_msg, argv[0]);
+		head_arg = argv[1];
+		argv += 2;
+		argc -= 2;
+	} else if (head_invalid) {
+		struct object *remote_head;
+		/*
+		 * If the merged head is a valid one there is no reason
+		 * to forbid "git merge" into a branch yet to be born.
+		 * We do the same for "git pull".
+		 */
+		if (argc != 1)
+			die("Can merge only exactly one commit into "
+				"empty head");
+		remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+		if (!remote_head)
+			die("%s - not something we can merge", argv[0]);
+		update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
+				DIE_ON_ERR);
+		reset_hard(remote_head->sha1, 0);
+		return 0;
+	} else {
+		struct strbuf msg;
+
+		/* We are invoked directly as the first-class UI. */
+		head_arg = "HEAD";
+
+		/*
+		 * All the rest are the commits being merged;
+		 * prepare the standard merge summary message to
+		 * be appended to the given message.  If remote
+		 * is invalid we will die later in the common
+		 * codepath so we discard the error in this
+		 * loop.
+		 */
+		strbuf_init(&msg, 0);
+		for (i = 0; i < argc; i++)
+			merge_name(argv[i], &msg);
+		fmt_merge_msg(option_log, &msg, &merge_msg);
+		if (merge_msg.len)
+			strbuf_setlen(&merge_msg, merge_msg.len-1);
+	}
+
+	if (head_invalid || !argc)
+		usage_with_options(builtin_merge_usage,
+			builtin_merge_options);
+
+	strbuf_addstr(&buf, "merge");
+	for (i = 0; i < argc; i++)
+		strbuf_addf(&buf, " %s", argv[i]);
+	setenv("GIT_REFLOG_ACTION", buf.buf, 0);
+	strbuf_reset(&buf);
+
+	for (i = 0; i < argc; i++) {
+		struct object *o;
+
+		o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
+		if (!o)
+			die("%s - not something we can merge", argv[i]);
+		remotes = &commit_list_insert(lookup_commit(o->sha1),
+			remotes)->next;
+
+		strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
+		setenv(buf.buf, argv[i], 1);
+		strbuf_reset(&buf);
+	}
+
+	if (!use_strategies) {
+		if (!remoteheads->next)
+			add_strategies(pull_twohead, DEFAULT_TWOHEAD);
+		else
+			add_strategies(pull_octopus, DEFAULT_OCTOPUS);
+	}
+
+	for (i = 0; i < use_strategies_nr; i++) {
+		if (use_strategies[i]->attr & NO_FAST_FORWARD)
+			allow_fast_forward = 0;
+		if (use_strategies[i]->attr & NO_TRIVIAL)
+			allow_trivial = 0;
+	}
+
+	if (!remoteheads->next)
+		common = get_merge_bases(lookup_commit(head),
+				remoteheads->item, 1);
+	else {
+		struct commit_list *list = remoteheads;
+		commit_list_insert(lookup_commit(head), &list);
+		common = get_octopus_merge_bases(list);
+		free(list);
+	}
+
+	update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
+		DIE_ON_ERR);
+
+	if (!common)
+		; /* No common ancestors found. We need a real merge. */
+	else if (!remoteheads->next && !common->next &&
+			common->item == remoteheads->item) {
+		/*
+		 * If head can reach all the merge then we are up to date.
+		 * but first the most common case of merging one remote.
+		 */
+		finish_up_to_date("Already up-to-date.");
+		return 0;
+	} else if (allow_fast_forward && !remoteheads->next &&
+			!common->next &&
+			!hashcmp(common->item->object.sha1, head)) {
+		/* Again the most common case of merging one remote. */
+		struct strbuf msg;
+		struct object *o;
+		char hex[41];
+
+		strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+
+		printf("Updating %s..%s\n",
+			hex,
+			find_unique_abbrev(remoteheads->item->object.sha1,
+			DEFAULT_ABBREV));
+		refresh_cache(REFRESH_QUIET);
+		strbuf_init(&msg, 0);
+		strbuf_addstr(&msg, "Fast forward");
+		if (have_message)
+			strbuf_addstr(&msg,
+				" (no commit created; -m option ignored)");
+		o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
+			0, NULL, OBJ_COMMIT);
+		if (!o)
+			return 1;
+
+		if (checkout_fast_forward(head, remoteheads->item->object.sha1))
+			return 1;
+
+		finish(o->sha1, msg.buf);
+		drop_save();
+		return 0;
+	} else if (!remoteheads->next && common->next)
+		;
+		/*
+		 * We are not doing octopus and not fast forward.  Need
+		 * a real merge.
+		 */
+	else if (!remoteheads->next && !common->next && option_commit) {
+		/*
+		 * We are not doing octopus, not fast forward, and have
+		 * only one common.
+		 */
+		refresh_cache(REFRESH_QUIET);
+		if (allow_trivial) {
+			/* See if it is really trivial. */
+			git_committer_info(IDENT_ERROR_ON_NO_NAME);
+			printf("Trying really trivial in-index merge...\n");
+			if (!read_tree_trivial(common->item->object.sha1,
+					head, remoteheads->item->object.sha1))
+				return merge_trivial();
+			printf("Nope.\n");
+		}
+	} else {
+		/*
+		 * An octopus.  If we can reach all the remote we are up
+		 * to date.
+		 */
+		int up_to_date = 1;
+		struct commit_list *j;
+
+		for (j = remoteheads; j; j = j->next) {
+			struct commit_list *common_one;
+
+			/*
+			 * Here we *have* to calculate the individual
+			 * merge_bases again, otherwise "git merge HEAD^
+			 * HEAD^^" would be missed.
+			 */
+			common_one = get_merge_bases(lookup_commit(head),
+				j->item, 1);
+			if (hashcmp(common_one->item->object.sha1,
+				j->item->object.sha1)) {
+				up_to_date = 0;
+				break;
+			}
+		}
+		if (up_to_date) {
+			finish_up_to_date("Already up-to-date. Yeeah!");
+			return 0;
+		}
+	}
+
+	/* We are going to make a new commit. */
+	git_committer_info(IDENT_ERROR_ON_NO_NAME);
+
+	/*
+	 * At this point, we need a real merge.  No matter what strategy
+	 * we use, it would operate on the index, possibly affecting the
+	 * working tree, and when resolved cleanly, have the desired
+	 * tree in the index -- this means that the index must be in
+	 * sync with the head commit.  The strategies are responsible
+	 * to ensure this.
+	 */
+	if (use_strategies_nr != 1) {
+		/*
+		 * Stash away the local changes so that we can try more
+		 * than one.
+		 */
+		save_state();
+	} else {
+		memcpy(stash, null_sha1, 20);
+	}
+
+	for (i = 0; i < use_strategies_nr; i++) {
+		int ret;
+		if (i) {
+			printf("Rewinding the tree to pristine...\n");
+			restore_state();
+		}
+		if (use_strategies_nr != 1)
+			printf("Trying merge strategy %s...\n",
+				use_strategies[i]->name);
+		/*
+		 * Remember which strategy left the state in the working
+		 * tree.
+		 */
+		wt_strategy = use_strategies[i]->name;
+
+		ret = try_merge_strategy(use_strategies[i]->name,
+			common, head_arg);
+		if (!option_commit && !ret) {
+			merge_was_ok = 1;
+			/*
+			 * This is necessary here just to avoid writing
+			 * the tree, but later we will *not* exit with
+			 * status code 1 because merge_was_ok is set.
+			 */
+			ret = 1;
+		}
+
+		if (ret) {
+			/*
+			 * The backend exits with 1 when conflicts are
+			 * left to be resolved, with 2 when it does not
+			 * handle the given merge at all.
+			 */
+			if (ret == 1) {
+				int cnt = evaluate_result();
+
+				if (best_cnt <= 0 || cnt <= best_cnt) {
+					best_strategy = use_strategies[i]->name;
+					best_cnt = cnt;
+				}
+			}
+			if (merge_was_ok)
+				break;
+			else
+				continue;
+		}
+
+		/* Automerge succeeded. */
+		write_tree_trivial(result_tree);
+		automerge_was_ok = 1;
+		break;
+	}
+
+	/*
+	 * If we have a resulting tree, that means the strategy module
+	 * auto resolved the merge cleanly.
+	 */
+	if (automerge_was_ok)
+		return finish_automerge(common, result_tree, wt_strategy);
+
+	/*
+	 * Pick the result from the best strategy and have the user fix
+	 * it up.
+	 */
+	if (!best_strategy) {
+		restore_state();
+		if (use_strategies_nr > 1)
+			fprintf(stderr,
+				"No merge strategy handled the merge.\n");
+		else
+			fprintf(stderr, "Merge with strategy %s failed.\n",
+				use_strategies[0]->name);
+		return 2;
+	} else if (best_strategy == wt_strategy)
+		; /* We already have its result in the working tree. */
+	else {
+		printf("Rewinding the tree to pristine...\n");
+		restore_state();
+		printf("Using the %s to prepare resolving by hand.\n",
+			best_strategy);
+		try_merge_strategy(best_strategy, common, head_arg);
+	}
+
+	if (squash)
+		finish(NULL, NULL);
+	else {
+		int fd;
+		struct commit_list *j;
+
+		for (j = remoteheads; j; j = j->next)
+			strbuf_addf(&buf, "%s\n",
+				sha1_to_hex(j->item->object.sha1));
+		fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
+		if (fd < 0)
+			die("Could open %s for writing",
+				git_path("MERGE_HEAD"));
+		if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+			die("Could not write to %s", git_path("MERGE_HEAD"));
+		close(fd);
+		strbuf_addch(&merge_msg, '\n');
+		fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+		if (fd < 0)
+			die("Could open %s for writing", git_path("MERGE_MSG"));
+		if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
+			merge_msg.len)
+			die("Could not write to %s", git_path("MERGE_MSG"));
+		close(fd);
+	}
+
+	if (merge_was_ok) {
+		fprintf(stderr, "Automatic merge went well; "
+			"stopped before committing as requested\n");
+		return 0;
+	} else
+		return suggest_conflicts();
+}
diff --git a/builtin.h b/builtin.h
index 05ee56f..0e605d4 100644
--- a/builtin.h
+++ b/builtin.h
@@ -64,6 +64,7 @@ extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_ls_remote(int argc, const char **argv, const char *prefix);
 extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
 extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
+extern int cmd_merge(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
diff --git a/git-merge.sh b/contrib/examples/git-merge.sh
similarity index 100%
rename from git-merge.sh
rename to contrib/examples/git-merge.sh
diff --git a/git.c b/git.c
index 2fbe96b..770aadd 100644
--- a/git.c
+++ b/git.c
@@ -271,6 +271,7 @@ static void handle_internal_command(int argc, const char **argv)
 		{ "ls-remote", cmd_ls_remote },
 		{ "mailinfo", cmd_mailinfo },
 		{ "mailsplit", cmd_mailsplit },
+		{ "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
 		{ "merge-base", cmd_merge_base, RUN_SETUP },
 		{ "merge-file", cmd_merge_file },
 		{ "merge-ours", cmd_merge_ours, RUN_SETUP },
diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh
index f3a4bb2..fcb8285 100755
--- a/t/t7602-merge-octopus-many.sh
+++ b/t/t7602-merge-octopus-many.sh
@@ -23,7 +23,7 @@ test_expect_success 'setup' '
 	done
 '
 
-test_expect_failure 'merge c1 with c2, c3, c4, ... c29' '
+test_expect_success 'merge c1 with c2, c3, c4, ... c29' '
 	git reset --hard c1 &&
 	i=2 &&
 	refs="" &&
-- 
1.5.6.1.322.ge904b.dirty

^ permalink raw reply related	[flat|nested] 81+ messages in thread

* Re: [PATCH] Build in merge
  2008-07-08  1:05                                                                     ` Junio C Hamano
@ 2008-07-08  1:41                                                                       ` Miklos Vajna
  0 siblings, 0 replies; 81+ messages in thread
From: Miklos Vajna @ 2008-07-08  1:41 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Olivier Marin

[-- Attachment #1: Type: text/plain, Size: 1232 bytes --]

On Mon, Jul 07, 2008 at 06:05:43PM -0700, Junio C Hamano <gitster@pobox.com> wrote:
> > 1) A project has code in a repo, some documentation and po files.
> >
> > The first two can be merged with builtin strategies, the later probably
> > needs a custom merge driver.
> 
> Per-path merge is probably better handled with custom ll-merge driver
> anyway.  See gitattributes(5).

Ah, thanks. I did not know ll-merge supports custom drivers as well.

> It wasn't *me* ;-) who wanted to add these "flags".
> 
> I think it does not matter what "my-strategy" does unless "-s my-strategy"
> (or pull.twohead) is explicitly given by the user, and at that time,
> DEFAULT_* options should not matter.  It probably is Ok to allow fast
> forward and trivial merges to them.  We'll see.

OK, so at first round I think we could avoid flags for custom
strategies.

For the error message, I think the output could be something like git
help --all, which splits commands based on being in
GIT_EXEC_PATH or somewhere else in PATH.

Also, currently (the shell version and the C one as well) we silently
ignore the config setting if it's set to semething invalid. What about
changing that to a similar error message as well?

[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 81+ messages in thread

end of thread, other threads:[~2008-07-08  1:42 UTC | newest]

Thread overview: 81+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-06-27 16:21 [PATCH 00/15] Build in merge Miklos Vajna
2008-06-27 16:21 ` [PATCH 01/15] Move split_cmdline() to alias.c Miklos Vajna
2008-06-27 16:21   ` [PATCH 02/15] Move commit_list_count() to commit.c Miklos Vajna
2008-06-27 16:21     ` [PATCH 03/15] Move parse-options's skip_prefix() to git-compat-util.h Miklos Vajna
2008-06-27 16:21       ` [PATCH 04/15] Add new test to ensure git-merge handles pull.twohead and pull.octopus Miklos Vajna
2008-06-27 16:21         ` [PATCH 05/15] Move read_cache_unmerged() to read-cache.c Miklos Vajna
2008-06-27 16:21           ` [PATCH 06/15] git-fmt-merge-msg: make it usable from other builtins Miklos Vajna
2008-06-27 16:22             ` [PATCH 07/15] Introduce get_octopus_merge_bases() in commit.c Miklos Vajna
2008-06-27 16:22               ` [PATCH 08/15] Add new test to ensure git-merge handles more than 25 refs Miklos Vajna
2008-06-27 16:22                 ` [PATCH 09/15] Introduce get_merge_bases_many() Miklos Vajna
2008-06-27 16:22                   ` [PATCH 10/15] Introduce reduce_heads() Miklos Vajna
2008-06-27 16:22                     ` [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf() Miklos Vajna
2008-06-27 16:22                       ` [PATCH 12/15] strbuf_vaddf(): support %*s, too Miklos Vajna
2008-06-27 16:22                         ` [PATCH 13/15] Add new test case to ensure git-merge reduces octopus parents when possible Miklos Vajna
2008-06-27 16:22                           ` [PATCH 14/15] Add new test case to ensure git-merge prepends the custom merge message Miklos Vajna
2008-06-27 16:22                             ` [PATCH 15/15] Build in merge Miklos Vajna
2008-06-27 17:09                               ` Miklos Vajna
2008-06-28  2:00                       ` [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf() Junio C Hamano
2008-06-28  2:33                         ` Miklos Vajna
2008-06-28  2:38                           ` [PATCH 13/13] Build in merge Miklos Vajna
2008-06-29  7:46                             ` Junio C Hamano
2008-06-29  8:11                               ` Jakub Narebski
2008-06-30  1:36                               ` Miklos Vajna
2008-06-30  1:39                                 ` Miklos Vajna
2008-06-30  5:44                                   ` Junio C Hamano
2008-06-30 17:41                                     ` Alex Riesen
2008-07-01  2:13                                     ` Miklos Vajna
2008-07-01  2:22                                       ` [PATCH 13/14] git-commit-tree: make it usable from other builtins Miklos Vajna
2008-07-01  5:07                                         ` Johannes Schindelin
2008-07-01  5:50                                         ` Junio C Hamano
2008-07-01 12:09                                           ` Miklos Vajna
2008-07-01  2:22                                       ` [PATCH 14/14] Build in merge Miklos Vajna
2008-07-01  2:37                                         ` [PATCH 00/14] " Miklos Vajna
2008-07-01  2:37                                           ` [PATCH 08/14] Add new test to ensure git-merge handles more than 25 refs Miklos Vajna
2008-07-01  2:37                                             ` [PATCH 13/14] git-commit-tree: make it usable from other builtins Miklos Vajna
2008-07-01  2:37                                               ` [PATCH 14/14] Build in merge Miklos Vajna
2008-07-01  6:23                                                 ` Junio C Hamano
2008-07-01 12:50                                                   ` Miklos Vajna
2008-07-01 13:18                                                     ` Miklos Vajna
2008-07-01 23:55                                                       ` Junio C Hamano
2008-07-02  7:43                                                         ` Miklos Vajna
2008-07-06  8:50                                                       ` Junio C Hamano
2008-07-06  9:43                                                         ` Junio C Hamano
2008-07-07 17:17                                                           ` Miklos Vajna
2008-07-07 18:15                                                             ` Junio C Hamano
2008-07-07 23:42                                                               ` [PATCH] " Miklos Vajna
2008-07-08  0:32                                                                 ` Junio C Hamano
2008-07-08  0:53                                                                   ` Junio C Hamano
2008-07-08  1:18                                                                     ` Miklos Vajna
2008-07-08  1:00                                                                   ` Miklos Vajna
2008-07-08  1:05                                                                     ` Junio C Hamano
2008-07-08  1:41                                                                       ` Miklos Vajna
2008-07-06 12:38                                                         ` [PATCH 14/14] " Johannes Schindelin
2008-07-06 19:39                                                           ` Junio C Hamano
2008-07-07 17:24                                                         ` [PATCH] " Miklos Vajna
2008-07-07 17:35                                                           ` Miklos Vajna
2008-07-01  7:27                                                 ` [PATCH 14/14] " Junio C Hamano
2008-07-01 12:55                                                   ` Miklos Vajna
2008-06-30 22:44                                   ` [PATCH 13/13] " Olivier Marin
2008-06-30 22:58                                     ` Miklos Vajna
2008-06-30  5:40                                 ` Junio C Hamano
2008-06-30 22:48                                   ` Miklos Vajna
2008-06-28 17:33                         ` [PATCH 11/15] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf() Johannes Schindelin
2008-06-29  8:07                           ` Junio C Hamano
2008-06-29 13:40                             ` Johannes Schindelin
2008-06-29 20:17                               ` Alex Riesen
2008-06-29 20:24                                 ` Junio C Hamano
2008-06-29 20:30                                   ` Sverre Rabbelier
2008-06-27 17:06                 ` [PATCH 08/15] Add new test to ensure git-merge handles more than 25 refs Miklos Vajna
2008-06-29 13:30         ` [PATCH 04/15] Add new test to ensure git-merge handles pull.twohead and pull.octopus Olivier Marin
2008-06-29 14:51           ` [PATCH] " Miklos Vajna
2008-06-29 15:11             ` Miklos Vajna
2008-07-04 16:34         ` [PATCH 04/15] " Mike Ralphson
2008-07-05  0:26           ` Miklos Vajna
2008-07-05  0:32             ` [PATCH] Fix t7601-merge-pull-config.sh on AIX Miklos Vajna
2008-07-05  1:49               ` Junio C Hamano
2008-06-29 14:05   ` [PATCH 01/15] Move split_cmdline() to alias.c Olivier Marin
2008-06-29 14:15     ` Johannes Schindelin
2008-06-29 14:29       ` Olivier Marin
2008-06-29 14:43         ` Johannes Schindelin
2008-06-30 22:51           ` Olivier Marin

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).