git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [WIP PATCH 0/5] Build in merge
@ 2008-05-24  0:01 Miklos Vajna
  2008-05-24  0:01 ` [PATCH 1/5] Move reset_index_file() to a new reset.c Miklos Vajna
  0 siblings, 1 reply; 10+ messages in thread
From: Miklos Vajna @ 2008-05-24  0:01 UTC (permalink / raw)
  To: git

Hello,

This is a work in progress patch, reflecting the current status of
builtin-merge I have. As far as I know all the tests pass, but there are
still some work to do. In general, a few problems I'm aware of:

 1) There are still TODOs/FIXMEs in the code.

 2) It should be possible to avoid some exec() calls, like write-tree,
    show-branch and fmt-merge-msg.

 3) Minor memory leaks.

Other than that, comments are welcome.

Miklos Vajna (5):
  Move reset_index_file() to a new reset.c
  Move split_cmdline() to libgit
  Make verbosity configurable in reset_index_file()
  Move commit_list_count() to commit.c
  Build in merge

 Makefile                                      |    4 +-
 alias.c                                       |   54 ++
 builtin-merge-recursive.c                     |    8 -
 builtin-merge.c                               | 1117 +++++++++++++++++++++++++
 builtin-reset.c                               |   19 +-
 builtin.h                                     |    1 +
 cache.h                                       |    1 +
 commit.c                                      |    8 +
 commit.h                                      |    1 +
 git-merge.sh => contrib/examples/git-merge.sh |    0 
 git.c                                         |   54 +--
 reset.c                                       |   29 +
 reset.h                                       |    6 +
 13 files changed, 1223 insertions(+), 79 deletions(-)
 create mode 100644 builtin-merge.c
 rename git-merge.sh => contrib/examples/git-merge.sh (100%)
 create mode 100644 reset.c
 create mode 100644 reset.h

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

* [PATCH 1/5] Move reset_index_file() to a new reset.c
  2008-05-24  0:01 [WIP PATCH 0/5] Build in merge Miklos Vajna
@ 2008-05-24  0:01 ` Miklos Vajna
  2008-05-24  0:01   ` [PATCH 2/5] Move split_cmdline() to libgit Miklos Vajna
  2008-05-24  4:21   ` [PATCH 1/5] Move reset_index_file() to a new reset.c Daniel Barkalow
  0 siblings, 2 replies; 10+ messages in thread
From: Miklos Vajna @ 2008-05-24  0:01 UTC (permalink / raw)
  To: git

This is needed as we want to use it later in builtin-merge.

Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---
 Makefile        |    2 ++
 builtin-reset.c |   17 +----------------
 reset.c         |   28 ++++++++++++++++++++++++++++
 reset.h         |    6 ++++++
 4 files changed, 37 insertions(+), 16 deletions(-)
 create mode 100644 reset.c
 create mode 100644 reset.h

diff --git a/Makefile b/Makefile
index a2de075..91d938a 100644
--- a/Makefile
+++ b/Makefile
@@ -364,6 +364,7 @@ LIB_H += quote.h
 LIB_H += reflog-walk.h
 LIB_H += refs.h
 LIB_H += remote.h
+LIB_H += reset.h
 LIB_H += revision.h
 LIB_H += run-command.h
 LIB_H += sha1-lookup.h
@@ -443,6 +444,7 @@ LIB_OBJS += read-cache.o
 LIB_OBJS += reflog-walk.o
 LIB_OBJS += refs.o
 LIB_OBJS += remote.o
+LIB_OBJS += reset.o
 LIB_OBJS += revision.o
 LIB_OBJS += run-command.o
 LIB_OBJS += server-info.o
diff --git a/builtin-reset.c b/builtin-reset.c
index 79424bb..6e6e168 100644
--- a/builtin-reset.c
+++ b/builtin-reset.c
@@ -18,6 +18,7 @@
 #include "tree.h"
 #include "branch.h"
 #include "parse-options.h"
+#include "reset.h"
 
 static const char * const git_reset_usage[] = {
 	"git-reset [--mixed | --soft | --hard] [-q] [<commit>]",
@@ -49,22 +50,6 @@ static inline int is_merge(void)
 	return !access(git_path("MERGE_HEAD"), F_OK);
 }
 
-static int reset_index_file(const unsigned char *sha1, int is_hard_reset)
-{
-	int i = 0;
-	const char *args[6];
-
-	args[i++] = "read-tree";
-	args[i++] = "-v";
-	args[i++] = "--reset";
-	if (is_hard_reset)
-		args[i++] = "-u";
-	args[i++] = sha1_to_hex(sha1);
-	args[i] = NULL;
-
-	return run_command_v_opt(args, RUN_GIT_CMD);
-}
-
 static void print_new_head_line(struct commit *commit)
 {
 	const char *hex, *body;
diff --git a/reset.c b/reset.c
new file mode 100644
index 0000000..a75fec6
--- /dev/null
+++ b/reset.c
@@ -0,0 +1,28 @@
+/*
+ * generic fucntion(s) from the "git reset" builtin command
+ *
+ * Copyright (c) 2007 Carlos Rica
+ *
+ * Based on git-reset.sh, which is
+ *
+ * Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano
+ */
+
+#include "cache.h"
+#include "run-command.h"
+
+int reset_index_file(const unsigned char *sha1, int is_hard_reset)
+{
+	int i = 0;
+	const char *args[6];
+
+	args[i++] = "read-tree";
+	args[i++] = "-v";
+	args[i++] = "--reset";
+	if (is_hard_reset)
+		args[i++] = "-u";
+	args[i++] = sha1_to_hex(sha1);
+	args[i] = NULL;
+
+	return run_command_v_opt(args, RUN_GIT_CMD);
+}
diff --git a/reset.h b/reset.h
new file mode 100644
index 0000000..4ae235b
--- /dev/null
+++ b/reset.h
@@ -0,0 +1,6 @@
+#ifndef RESET_H
+#define RESET_H
+
+int reset_index_file(const unsigned char *sha1, int is_hard_reset);
+
+#endif
-- 
1.5.5.1.357.g1af8b.dirty

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

* [PATCH 2/5] Move split_cmdline() to libgit
  2008-05-24  0:01 ` [PATCH 1/5] Move reset_index_file() to a new reset.c Miklos Vajna
@ 2008-05-24  0:01   ` Miklos Vajna
  2008-05-24  0:01     ` [PATCH 3/5] Make verbosity configurable in reset_index_file() Miklos Vajna
  2008-05-24  4:21   ` [PATCH 1/5] Move reset_index_file() to a new reset.c Daniel Barkalow
  1 sibling, 1 reply; 10+ messages in thread
From: Miklos Vajna @ 2008-05-24  0:01 UTC (permalink / raw)
  To: git

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>
---
 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 116cac8..75bc48a 100644
--- a/alias.c
+++ b/alias.c
@@ -20,3 +20,57 @@ char *alias_lookup(const char *alias)
 	git_config(alias_lookup_cb);
 	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 093f04c..5e22b1e 100644
--- a/cache.h
+++ b/cache.h
@@ -814,5 +814,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 89b431f..b8df19e 100644
--- a/git.c
+++ b/git.c
@@ -87,59 +87,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.5.1.357.g1af8b.dirty

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

* [PATCH 3/5] Make verbosity configurable in reset_index_file()
  2008-05-24  0:01   ` [PATCH 2/5] Move split_cmdline() to libgit Miklos Vajna
@ 2008-05-24  0:01     ` Miklos Vajna
  2008-05-24  0:01       ` [PATCH 4/5] Move commit_list_count() to commit.c Miklos Vajna
  2008-05-27 14:08       ` [PATCH 3/5] Make verbosity configurable in reset_index_file() Kristian Høgsberg
  0 siblings, 2 replies; 10+ messages in thread
From: Miklos Vajna @ 2008-05-24  0:01 UTC (permalink / raw)
  To: git

Till now reset_index_file() was always verbose. Add a new argument to be
able to disable this behaviour.

Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---
 builtin-reset.c |    2 +-
 reset.c         |    5 +++--
 reset.h         |    2 +-
 3 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/builtin-reset.c b/builtin-reset.c
index 6e6e168..179c59c 100644
--- a/builtin-reset.c
+++ b/builtin-reset.c
@@ -216,7 +216,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		if (is_merge() || read_cache() < 0 || unmerged_cache())
 			die("Cannot do a soft reset in the middle of a merge.");
 	}
-	else if (reset_index_file(sha1, (reset_type == HARD)))
+	else if (reset_index_file(sha1, (reset_type == HARD), 1))
 		die("Could not reset index file to revision '%s'.", rev);
 
 	/* Any resets update HEAD to the head being switched to,
diff --git a/reset.c b/reset.c
index a75fec6..baae947 100644
--- a/reset.c
+++ b/reset.c
@@ -11,13 +11,14 @@
 #include "cache.h"
 #include "run-command.h"
 
-int reset_index_file(const unsigned char *sha1, int is_hard_reset)
+int reset_index_file(const unsigned char *sha1, int is_hard_reset, int verbose)
 {
 	int i = 0;
 	const char *args[6];
 
 	args[i++] = "read-tree";
-	args[i++] = "-v";
+	if (verbose)
+		args[i++] = "-v";
 	args[i++] = "--reset";
 	if (is_hard_reset)
 		args[i++] = "-u";
diff --git a/reset.h b/reset.h
index 4ae235b..c43ae22 100644
--- a/reset.h
+++ b/reset.h
@@ -1,6 +1,6 @@
 #ifndef RESET_H
 #define RESET_H
 
-int reset_index_file(const unsigned char *sha1, int is_hard_reset);
+int reset_index_file(const unsigned char *sha1, int is_hard_reset, int verbose);
 
 #endif
-- 
1.5.5.1.357.g1af8b.dirty

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

* [PATCH 4/5] Move commit_list_count() to commit.c
  2008-05-24  0:01     ` [PATCH 3/5] Make verbosity configurable in reset_index_file() Miklos Vajna
@ 2008-05-24  0:01       ` Miklos Vajna
  2008-05-24  0:01         ` [PATCH 5/5] Build in merge Miklos Vajna
  2008-05-27 14:08       ` [PATCH 3/5] Make verbosity configurable in reset_index_file() Kristian Høgsberg
  1 sibling, 1 reply; 10+ messages in thread
From: Miklos Vajna @ 2008-05-24  0:01 UTC (permalink / raw)
  To: git

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

Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---
 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 46e636f..7aaa215 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 94d5b3d..b45ec9b 100644
--- a/commit.c
+++ b/commit.c
@@ -331,6 +331,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.5.1.357.g1af8b.dirty

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

* [PATCH 5/5] Build in merge
  2008-05-24  0:01       ` [PATCH 4/5] Move commit_list_count() to commit.c Miklos Vajna
@ 2008-05-24  0:01         ` Miklos Vajna
  0 siblings, 0 replies; 10+ messages in thread
From: Miklos Vajna @ 2008-05-24  0:01 UTC (permalink / raw)
  To: git

Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---
 Makefile                                      |    2 +-
 builtin-merge.c                               | 1117 +++++++++++++++++++++++++
 builtin.h                                     |    1 +
 git-merge.sh => contrib/examples/git-merge.sh |    0 
 git.c                                         |    1 +
 5 files changed, 1120 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 91d938a..9820932 100644
--- a/Makefile
+++ b/Makefile
@@ -241,7 +241,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
@@ -509,6 +508,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..ffe6a67
--- /dev/null
+++ b/builtin-merge.c
@@ -0,0 +1,1117 @@
+/*
+ * 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 "strbuf.h"
+#include "run-command.h"
+#include "path-list.h"
+#include "diff.h"
+#include "refs.h"
+#include "reset.h"
+#include "commit.h"
+#include "diffcore.h"
+#include "revision.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, hide_diffstat, 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_addstr(buf, 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(const char *path, void *util,
+	struct path_list *list)
+{
+	path_list_append(path, list)->util = util;
+}
+
+static int option_parse_strategy(const struct option *opt,
+	const char *arg, int unset)
+{
+	int i;
+	struct path_list *list = opt->value;
+	struct path_list_item *item =
+		unsorted_path_list_lookup(arg, &strategies);
+
+	if (unset)
+		return 0;
+
+	if (item)
+		path_list_append_strategy(arg, item->util, list);
+	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 struct option builtin_merge_options[] = {
+	OPT_BOOLEAN('n', NULL, &hide_diffstat,
+		"don't show a diffstat at the end of the merge"),
+	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 sucesses (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()
+};
+
+/*
+ * TODO: This is the same as builtin-remote's skip_prefix, but it seems
+ * we can't move it simply to git-compat-util.h as parse-options.c has
+ * an other skip_prefix() function as well.
+ */
+static inline const char *skip_prefix(const char *name, const char *prefix)
+{
+	return !name ? "" :
+		prefixcmp(name, prefix) ?  name : name + strlen(prefix);
+}
+
+static const unsigned char *hex_to_sha1(char *hex)
+{
+	static unsigned char sha1[20];
+
+	if (get_sha1(hex, sha1))
+		die("bad revision '%s'", hex);
+	return sha1;
+}
+
+/* Cleans up metadata that is uninteresting after a succeeded merge. */
+static void dropsave()
+{
+	unlink(git_path("MERGE_HEAD"));
+	unlink(git_path("MERGE_MSG"));
+	unlink(git_path("MERGE_STASH"));
+}
+
+static void save_state()
+{
+	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 restore_state()
+{
+	struct strbuf sb;
+	const char *args[] = { "stash", "apply", NULL, NULL };
+
+	if (access(git_path("MERGE_STASH"), R_OK) < 0)
+		return;
+
+	reset_index_file(head, 1, 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;
+	if (run_command_v_opt(args, RUN_GIT_CMD))
+		die("Could not apply stash '%s'", sb.buf);
+
+	refresh_cache(REFRESH_QUIET);
+}
+
+/* This is called when no merge was necessary. */
+static void finish_up_to_date(const char *msg)
+{
+	if (squash)
+		printf("%s (nothing to squash)\n", msg);
+	else
+		printf("%s\n", msg);
+	dropsave();
+}
+
+static void squash_message(int out_fd)
+{
+	const char **args;
+	int i = 0;
+	/* TODO: it should be possible to avoid exec here() */
+	struct child_process log;
+	struct strbuf head_buf;
+	struct commit_list *j;
+	const char squash_header[] = "Squashed commit of the following:\n\n";
+
+	write(out_fd, squash_header, sizeof(squash_header) - 1);
+
+	strbuf_init(&head_buf, 0);
+	strbuf_addch(&head_buf, '^');
+	strbuf_addstr(&head_buf, sha1_to_hex(head));
+
+	args = xmalloc((5 + commit_list_count(remoteheads)) * sizeof(char *));
+	args[i++] = "log";
+	args[i++] = "--no-merges";
+	args[i++] = "--pretty=medium";
+	args[i++] = head_buf.buf;
+	for (j = remoteheads; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i] = NULL;
+	memset(&log, 0, sizeof(log));
+	log.argv = args;
+	log.out = out_fd;
+	log.git_cmd = 1;
+	run_command(&log);
+	strbuf_release(&head_buf);
+	for (i = 4; args[i]; i++)
+		free((char *)args[i]);
+	free(args);
+}
+
+static int run_hook(const char *name)
+{
+	struct child_process hook;
+	const char *argv[3], *env[2];
+	char index[PATH_MAX];
+
+	snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", get_index_file());
+	env[0] = index;
+	env[1] = NULL;
+
+	argv[0] = git_path("hooks/%s", name);
+	if (squash)
+		argv[1] = "1";
+	else
+		argv[1] = "0";
+	argv[2] = NULL;
+
+	if (access(argv[0], X_OK) < 0)
+		return 0;
+
+	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;
+	const char *argv_gc_auto[] = { "gc", "--auto", NULL };
+	struct diff_options opts;
+
+	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) {
+		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"));
+		squash_message(fd);
+		close(fd);
+	} else {
+		if (!merge_msg.len)
+			printf("No merge message -- not updating HEAD\n");
+		else {
+			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) {
+		diff_setup(&opts);
+		opts.output_format |=
+			DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+		opts.detect_rename = DIFF_DETECT_RENAME;
+		diff_tree_sha1(head, new_head, "", &opts);
+		diffcore_std(&opts);
+		diff_flush(&opts);
+	}
+
+	/* Run a post-merge hook */
+	run_hook("post-merge");
+}
+
+/* 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 *ref;
+	regex_t reg;
+
+	memset(branch_head, 0, sizeof(branch_head));
+	remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+	if (!remote_head)
+		return;
+
+	/* TODO: maybe here we could just use get_sha1()? */
+	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;
+	}
+	/*
+	 * TODO: This probably wants to match not only <name>~<number>,
+	 * but also <name>^
+	 */
+	/* TODO: using regcomp() is not sensible, we could do it by hand
+	 * just by using strchr(). */
+	regcomp(&reg, "~[1-9][0-9]*$", 0);
+	if (!regexec(&reg, remote, 0, 0, 0)) {
+		struct strbuf truname;
+		strbuf_addstr(&truname, remote);
+		strbuf_setlen(&truname, strrchr(truname.buf, '~')-truname.buf);
+		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 == NULL)
+			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)
+{
+	if (branch && !prefixcmp(k, "branch.") &&
+		!prefixcmp(k + 7, branch) &&
+		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+		const char **argv;
+		int argc;
+		struct strbuf cmdline;
+
+		/* TODO this is indeed a hack, maybe modify
+		 * split_cmdline() and add an option to tell
+		 * where should it start feeding options,
+		 * default to 0, but here to 1? */
+		strbuf_init(&cmdline, 0);
+		strbuf_addf(&cmdline, "merge %s", v);
+		argc = split_cmdline((char *)cmdline.buf, &argv);
+		parse_options(argc, argv, builtin_merge_options,
+				builtin_merge_usage, 0);
+		/* TODO: hack and code duplication */
+		if (hide_diffstat)
+			show_diffstat = hide_diffstat = 0;
+		strbuf_release(&cmdline);
+	}
+
+	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+		show_diffstat = git_config_bool(k, v);
+	/* FIXME: This may or may not be a list */
+	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)
+{
+	const char *args[] = {
+		"read-tree", "--trivial", "-m", "-u", "-v",
+		NULL, NULL, NULL, NULL };
+
+	args[5] = sha1_to_hex(common);
+	args[6] = sha1_to_hex(head);
+	args[7] = sha1_to_hex(one);
+
+	return run_command_v_opt(args, RUN_GIT_CMD);
+}
+
+static char *commit_tree_trivial(const char *msg, const char *tree,
+		struct commit_list *parents)
+{
+	int i = 0, len;
+	/* TODO: it should be possible to avoid exec here() */
+	struct child_process ct;
+	struct strbuf result_commit;
+	const char **args;
+	struct commit_list *j;
+
+	memset(&ct, 0, sizeof(ct));
+	strbuf_init(&result_commit, 0);
+	args = xmalloc((3 + 2 * commit_list_count(parents)) * sizeof(char *));
+	args[i++] = "commit-tree";
+	args[i++] = tree;
+	for (j = parents; j; j = j->next) {
+		args[i++] = "-p";
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	}
+	args[i++] = NULL;
+	ct.argv = args;
+	ct.in = -1;
+	ct.out = -1;
+	ct.git_cmd = 1;
+	if (start_command(&ct))
+		die("could not run git commit-tree");
+	if (write_in_full(ct.in, msg, strlen(msg)) != strlen(msg))
+		die("git commit-tree did not accept the commit message");
+	close(ct.in);
+	len = strbuf_read(&result_commit, ct.out, 1024);
+	close(ct.out);
+	if (finish_command(&ct) || !len || len < 0)
+		die("git commit-tree failed to commit the tree");
+	if (result_commit.len)
+		strbuf_setlen(&result_commit, result_commit.len-1);
+	return result_commit.buf;
+}
+
+static void write_tree_trivial(struct strbuf *result_tree)
+{
+	const char *argv_write_tree[] = { "write-tree", NULL };
+	/* TODO: it should be possible to avoid exec here() */
+	struct child_process wt;
+	int len;
+
+	memset(&wt, 0, sizeof(wt));
+	strbuf_init(result_tree, 0);
+	wt.argv = argv_write_tree;
+	wt.out = -1;
+	wt.git_cmd = 1;
+	if (start_command(&wt))
+		die("could not run git write-tree");
+	len = strbuf_read(result_tree, wt.out, 1024);
+	close(wt.out);
+	if (finish_command(&wt) || !len || len < 0)
+		die("git write-tree failed to write a tree");
+	strbuf_setlen(result_tree, result_tree->len-1);
+}
+
+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++]);
+	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;
+}
+
+struct commit_list *commit_list_append(struct commit *item,
+	struct commit_list **list_p)
+{
+	struct commit_list *i, *prev = NULL, *list = *list_p;
+	struct commit_list *new_list = xmalloc(sizeof(struct commit_list));
+
+	new_list->item = item;
+	new_list->next = NULL;
+
+	if (!list)
+		*list_p = new_list;
+	else {
+		for (i = list; i; i = i->next)
+			prev = i;
+		prev->next = new_list;
+	}
+	return list;
+}
+
+static void run_show_branch(char *opt, unsigned char *head,
+	struct commit_list **common)
+{
+	struct child_process show;
+	struct strbuf common_buf;
+	const char **args;
+	const char *ptr;
+	int i, len;
+	struct commit_list *j;
+	unsigned char sha1[20];
+
+	memset(&show, 0, sizeof(show));
+	strbuf_init(&common_buf, 0);
+	args = xmalloc((4 + commit_list_count(remoteheads)) * sizeof(char *));
+	i = 0;
+	args[i++] = "show-branch";
+	args[i++] = opt;
+	args[i++] = xstrdup(sha1_to_hex(head));
+	for (j = remoteheads; j; j = j->next)
+		args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+	args[i] = NULL;
+	show.argv = args;
+	show.out = -1;
+	show.git_cmd = 1;
+	if (start_command(&show))
+		die("could not run git show-branch");
+	len = strbuf_read(&common_buf, show.out, 1024);
+	close(show.out);
+	if (finish_command(&show) || !len || len < 0)
+		die("git show-branch failed to produce the list of merge bases");
+	for (i = 2; args[i]; i++)
+		free((char *)args[i]);
+	free(args);
+	ptr = common_buf.buf;
+	while ((ptr = strchr(ptr, '\n'))) {
+		char hex[41];
+		memset(hex, 0, sizeof(hex));
+		memcpy(hex, ptr-40, 40);
+		if (get_sha1(hex, sha1))
+			die("bad revision returned by show-branch: '%s'", hex);
+		commit_list_append(lookup_commit(sha1), common);
+		ptr++;
+	}
+}
+
+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;
+	struct commit_list *common = NULL;
+	struct path_list_item *best_strategy = NULL, *wt_strategy;
+
+
+	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)
+		branch = skip_prefix(branch, "refs/heads/");
+
+	git_config(git_merge_config);
+
+	argc = parse_options(argc, argv, builtin_merge_options,
+			builtin_merge_usage, 0);
+	/* TODO: hack */
+	if (hide_diffstat)
+		show_diffstat = hide_diffstat = 0;
+
+	if (squash) {
+		if (!allow_fast_forward)
+			die("You cannot combine --squash with --no-ff.");
+		option_commit = 0;
+	}
+
+	if (argc == 0)
+		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);
+		return reset_index_file(hex_to_sha1("HEAD"), 1, 0);
+	} else {
+		/* We are invoked directly as the first-class UI. */
+		strbuf_addstr(&head_arg, "HEAD");
+		if (!merge_msg.len) {
+			/*
+			 * 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.
+			 */
+			int i, len;
+			struct strbuf msg;
+			struct child_process fmt;
+			const char *args[3];
+
+			strbuf_init(&msg, 0);
+			for (i = 0; i < argc; i++)
+				merge_name(argv[i], &msg);
+			memset(&fmt, 0, sizeof(fmt));
+			args[0] = "fmt-merge-msg";
+			if (option_log)
+				args[1] = "--log";
+			else
+				args[1] = "--no-log";
+			args[2] = NULL;
+			fmt.argv = args;
+			fmt.in = -1;
+			fmt.out = -1;
+			fmt.git_cmd = 1;
+			if (start_command(&fmt))
+				die("could not run git fmt-merge-msg");
+			if (write_in_full(fmt.in, msg.buf, msg.len) != msg.len)
+				die("git fmt-merge-msg did not accept the tag data");
+			close(fmt.in);
+			len = strbuf_read(&merge_msg, fmt.out, 1024);
+			close(fmt.out);
+			if (finish_command(&fmt) || !len || len < 0)
+				die("git fmt-merge-msg failed to produce a merge commit message");
+			if(merge_msg.len)
+				strbuf_setlen(&merge_msg, merge_msg.len-1);
+		}
+	}
+
+	if (head_invalid || argc == 0)
+		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]);
+		commit_list_append(lookup_commit(o->sha1), &remoteheads);
+
+		strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
+		setenv(buf.buf, argv[i], 1);
+		strbuf_reset(&buf);
+	}
+
+	if (!use_strategies.nr) {
+		struct path_list_item *item;
+		/*
+		 * TODO: there is some code duplication here, it would
+		 * be nice to kill it.
+		 */
+		if (!remoteheads->next) {
+			item = unsorted_path_list_lookup(pull_twohead,
+				&strategies);
+			if (pull_twohead && item) {
+				path_list_append_strategy(pull_twohead,
+					item->util, &use_strategies);
+			} else {
+				for (i = 0; i < strategies.nr; i++)
+					if ((enum strategy)strategies.items[i].util & DEFAULT_TWOHEAD)
+						path_list_append_strategy(strategies.items[i].path,
+							strategies.items[i].util,
+							&use_strategies);
+			}
+		} else {
+			item = unsorted_path_list_lookup(pull_octopus,
+				&strategies);
+			if (pull_octopus && item)
+				path_list_append_strategy(pull_octopus,
+					item->util, &use_strategies);
+			else {
+				for (i = 0; i < strategies.nr; i++)
+					if ((enum strategy)strategies.items[i].util & DEFAULT_OCTOPUS)
+						path_list_append_strategy(strategies.items[i].path,
+							strategies.items[i].util,
+							&use_strategies);
+			}
+		}
+	}
+
+	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 {
+		/*
+		 * TODO: move functionality to commit.c and avoid fork()
+		 * here.
+		 */
+		run_show_branch("--merge-base", head, &common);
+	}
+
+	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 merge 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;
+		const char *args[7];
+		struct object *o;
+
+		printf("Updating %s..%s\n",
+			find_unique_abbrev(head, DEFAULT_ABBREV),
+			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;
+
+		i = 0;
+		args[i++] = "read-tree";
+		args[i++] = "-v";
+		args[i++] = "-m";
+		args[i++] = "-u";
+		args[i++] = "--exclude-per-directory=.gitignore";
+		args[i++] = sha1_to_hex(head);
+		args[i++] = sha1_to_hex(remoteheads->item->object.sha1);
+		args[i] = NULL;
+		if (run_command_v_opt(args, RUN_GIT_CMD))
+			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 and not fast forward.  Need
+		 * a real merge.
+		 */
+		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)) {
+				struct strbuf result_tree;
+				char *result_commit;
+				struct commit_list parent;
+
+				write_tree_trivial(&result_tree);
+				printf("Wonderful.\n");
+				parent.item = remoteheads->item;
+				parent.next = NULL;
+				result_commit =
+					commit_tree_trivial(merge_msg.buf,
+					result_tree.buf, &parent);
+				finish(hex_to_sha1(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;
+
+			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;
+			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");
+
+				/* TODO maybe somehow document that
+				 * these two blocks does the same as
+				 * diff-files and ls-files? */
+				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);
+
+				cnt += count_unmerged_entries();
+
+				if (best_cnt <= 0 || cnt <= best_cnt) {
+					best_strategy = &use_strategies.items[i];
+					best_cnt = cnt;
+				}
+			}
+			continue;
+		}
+
+		/* Automerge succeeded. */
+		write_tree_trivial(&buf);
+		if (get_sha1(buf.buf, result_tree))
+			die("bad tree '%s'", buf.buf);
+		strbuf_reset(&buf);
+		break;
+	}
+
+	/*
+	 * If we have a resulting tree, that means the strategy module
+	 * auto resolved the merge cleanly.
+	 */
+	/* TODO: is there a better check? somehow need to determine if
+	 * we wrote something to result_tree since its declaration */
+	if (result_tree[0] != '\0') {
+		struct commit_list *parents = NULL, *j;
+		char *result_commit;
+
+		if (allow_fast_forward)
+			run_show_branch("--independent", head, &parents);
+		else {
+			commit_list_append(lookup_commit(head), &parents);
+			for (j = remoteheads; j; j = j->next)
+				commit_list_append(j->item, &parents);
+		}
+		strbuf_addch(&merge_msg, '\n');
+		result_commit = commit_tree_trivial(merge_msg.buf,
+			sha1_to_hex(result_tree), parents);
+		strbuf_addf(&buf, "Merge made by %s.", wt_strategy->path);
+		finish(hex_to_sha1(result_commit), buf.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 95126fd..7c26d75 100644
--- a/builtin.h
+++ b/builtin.h
@@ -54,6 +54,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 b8df19e..fced435 100644
--- a/git.c
+++ b/git.c
@@ -267,6 +267,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.5.1.357.g1af8b.dirty

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

* Re: [PATCH 1/5] Move reset_index_file() to a new reset.c
  2008-05-24  0:01 ` [PATCH 1/5] Move reset_index_file() to a new reset.c Miklos Vajna
  2008-05-24  0:01   ` [PATCH 2/5] Move split_cmdline() to libgit Miklos Vajna
@ 2008-05-24  4:21   ` Daniel Barkalow
  2008-05-24 21:55     ` Miklos Vajna
  1 sibling, 1 reply; 10+ messages in thread
From: Daniel Barkalow @ 2008-05-24  4:21 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: git

On Sat, 24 May 2008, Miklos Vajna wrote:

> This is needed as we want to use it later in builtin-merge.
> 
> Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
> ---
>  Makefile        |    2 ++
>  builtin-reset.c |   17 +----------------
>  reset.c         |   28 ++++++++++++++++++++++++++++
>  reset.h         |    6 ++++++
>  4 files changed, 37 insertions(+), 16 deletions(-)
>  create mode 100644 reset.c
>  create mode 100644 reset.h
> 
> diff --git a/Makefile b/Makefile
> index a2de075..91d938a 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -364,6 +364,7 @@ LIB_H += quote.h
>  LIB_H += reflog-walk.h
>  LIB_H += refs.h
>  LIB_H += remote.h
> +LIB_H += reset.h
>  LIB_H += revision.h
>  LIB_H += run-command.h
>  LIB_H += sha1-lookup.h
> @@ -443,6 +444,7 @@ LIB_OBJS += read-cache.o
>  LIB_OBJS += reflog-walk.o
>  LIB_OBJS += refs.o
>  LIB_OBJS += remote.o
> +LIB_OBJS += reset.o
>  LIB_OBJS += revision.o
>  LIB_OBJS += run-command.o
>  LIB_OBJS += server-info.o
> diff --git a/builtin-reset.c b/builtin-reset.c
> index 79424bb..6e6e168 100644
> --- a/builtin-reset.c
> +++ b/builtin-reset.c
> @@ -18,6 +18,7 @@
>  #include "tree.h"
>  #include "branch.h"
>  #include "parse-options.h"
> +#include "reset.h"
>  
>  static const char * const git_reset_usage[] = {
>  	"git-reset [--mixed | --soft | --hard] [-q] [<commit>]",
> @@ -49,22 +50,6 @@ static inline int is_merge(void)
>  	return !access(git_path("MERGE_HEAD"), F_OK);
>  }
>  
> -static int reset_index_file(const unsigned char *sha1, int is_hard_reset)
> -{
> -	int i = 0;
> -	const char *args[6];
> -
> -	args[i++] = "read-tree";
> -	args[i++] = "-v";
> -	args[i++] = "--reset";
> -	if (is_hard_reset)
> -		args[i++] = "-u";
> -	args[i++] = sha1_to_hex(sha1);
> -	args[i] = NULL;
> -
> -	return run_command_v_opt(args, RUN_GIT_CMD);
> -}
> -
>  static void print_new_head_line(struct commit *commit)
>  {
>  	const char *hex, *body;
> diff --git a/reset.c b/reset.c
> new file mode 100644
> index 0000000..a75fec6
> --- /dev/null
> +++ b/reset.c
> @@ -0,0 +1,28 @@
> +/*
> + * generic fucntion(s) from the "git reset" builtin command
> + *
> + * Copyright (c) 2007 Carlos Rica
> + *
> + * Based on git-reset.sh, which is
> + *
> + * Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano
> + */
> +
> +#include "cache.h"
> +#include "run-command.h"
> +
> +int reset_index_file(const unsigned char *sha1, int is_hard_reset)
> +{
> +	int i = 0;
> +	const char *args[6];
> +
> +	args[i++] = "read-tree";
> +	args[i++] = "-v";
> +	args[i++] = "--reset";
> +	if (is_hard_reset)
> +		args[i++] = "-u";
> +	args[i++] = sha1_to_hex(sha1);
> +	args[i] = NULL;
> +
> +	return run_command_v_opt(args, RUN_GIT_CMD);
> +}

You might as well use unpack_trees() directly here, instead of running a 
separate command. In fact, you might skip making a function here, and just 
call unpack_trees() where you'd use it. (And maybe have a function to 
initialize a struct unpack_tree_options to some common defaults to make it 
easy to see what's being done.)

	-Daniel
*This .sig left intentionally blank*

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

* Re: [PATCH 1/5] Move reset_index_file() to a new reset.c
  2008-05-24  4:21   ` [PATCH 1/5] Move reset_index_file() to a new reset.c Daniel Barkalow
@ 2008-05-24 21:55     ` Miklos Vajna
  0 siblings, 0 replies; 10+ messages in thread
From: Miklos Vajna @ 2008-05-24 21:55 UTC (permalink / raw)
  To: Daniel Barkalow; +Cc: git

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

On Sat, May 24, 2008 at 12:21:42AM -0400, Daniel Barkalow <barkalow@iabervon.org> wrote:
> You might as well use unpack_trees() directly here, instead of running a 
> separate command. In fact, you might skip making a function here, and just 
> call unpack_trees() where you'd use it. (And maybe have a function to 
> initialize a struct unpack_tree_options to some common defaults to make it 
> easy to see what's being done.)

Thanks for the suggestion, I did so; as a result now patch #1 and #3 are
no longer necessary.

(I haven't sent out a new series yet, but it's in
git://repo.or.cz/git/vmiklos.git builtin-merge.)

Maybe builtin-reset could use unpack_trees() directly as well? I don't
know if it's just a question of "nobody did it yet", or there is some
reason for not using unpack_trees() there.

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

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

* Re: [PATCH 3/5] Make verbosity configurable in reset_index_file()
  2008-05-24  0:01     ` [PATCH 3/5] Make verbosity configurable in reset_index_file() Miklos Vajna
  2008-05-24  0:01       ` [PATCH 4/5] Move commit_list_count() to commit.c Miklos Vajna
@ 2008-05-27 14:08       ` Kristian Høgsberg
  2008-05-27 14:49         ` Miklos Vajna
  1 sibling, 1 reply; 10+ messages in thread
From: Kristian Høgsberg @ 2008-05-27 14:08 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: git

On Sat, 2008-05-24 at 02:01 +0200, Miklos Vajna wrote:
> Till now reset_index_file() was always verbose. Add a new argument to be
> able to disable this behaviour.
> 
> Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
> ---
>  builtin-reset.c |    2 +-
>  reset.c         |    5 +++--
>  reset.h         |    2 +-
>  3 files changed, 5 insertions(+), 4 deletions(-)
> 
> diff --git a/builtin-reset.c b/builtin-reset.c
> index 6e6e168..179c59c 100644
> --- a/builtin-reset.c
> +++ b/builtin-reset.c
> @@ -216,7 +216,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
>  		if (is_merge() || read_cache() < 0 || unmerged_cache())
>  			die("Cannot do a soft reset in the middle of a merge.");
>  	}
> -	else if (reset_index_file(sha1, (reset_type == HARD)))
> +	else if (reset_index_file(sha1, (reset_type == HARD), 1))
>  		die("Could not reset index file to revision '%s'.", rev);
>  
>  	/* Any resets update HEAD to the head being switched to,
> diff --git a/reset.c b/reset.c
> index a75fec6..baae947 100644
> --- a/reset.c
> +++ b/reset.c
> @@ -11,13 +11,14 @@
>  #include "cache.h"
>  #include "run-command.h"
>  
> -int reset_index_file(const unsigned char *sha1, int is_hard_reset)
> +int reset_index_file(const unsigned char *sha1, int is_hard_reset, int verbose)

I would suggest using flags here instead of a bunch of boolean args.
Consider the readability of

    reset_index_file(sha1, 1, 1);

vs

    reset_index_file(sha1, RESET_HARD | RESET_VERBOSE);

cheers,
Kristian

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

* Re: [PATCH 3/5] Make verbosity configurable in reset_index_file()
  2008-05-27 14:08       ` [PATCH 3/5] Make verbosity configurable in reset_index_file() Kristian Høgsberg
@ 2008-05-27 14:49         ` Miklos Vajna
  0 siblings, 0 replies; 10+ messages in thread
From: Miklos Vajna @ 2008-05-27 14:49 UTC (permalink / raw)
  To: Kristian H?gsberg; +Cc: git

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

On Tue, May 27, 2008 at 10:08:01AM -0400, Kristian H?gsberg <krh@redhat.com> wrote:
> > -int reset_index_file(const unsigned char *sha1, int is_hard_reset)
> > +int reset_index_file(const unsigned char *sha1, int is_hard_reset, int verbose)
> 
> I would suggest using flags here instead of a bunch of boolean args.
> Consider the readability of
> 
>     reset_index_file(sha1, 1, 1);
> 
> vs
> 
>     reset_index_file(sha1, RESET_HARD | RESET_VERBOSE);

Thanks for the suggestion, but - as Daniel already pointed out - the
same functionality can be achieved by using the unpack_trees() API
directly, so the patch is useless.

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

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

end of thread, other threads:[~2008-05-27 14:50 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-05-24  0:01 [WIP PATCH 0/5] Build in merge Miklos Vajna
2008-05-24  0:01 ` [PATCH 1/5] Move reset_index_file() to a new reset.c Miklos Vajna
2008-05-24  0:01   ` [PATCH 2/5] Move split_cmdline() to libgit Miklos Vajna
2008-05-24  0:01     ` [PATCH 3/5] Make verbosity configurable in reset_index_file() Miklos Vajna
2008-05-24  0:01       ` [PATCH 4/5] Move commit_list_count() to commit.c Miklos Vajna
2008-05-24  0:01         ` [PATCH 5/5] Build in merge Miklos Vajna
2008-05-27 14:08       ` [PATCH 3/5] Make verbosity configurable in reset_index_file() Kristian Høgsberg
2008-05-27 14:49         ` Miklos Vajna
2008-05-24  4:21   ` [PATCH 1/5] Move reset_index_file() to a new reset.c Daniel Barkalow
2008-05-24 21:55     ` Miklos Vajna

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).