Git development
 help / color / mirror / Atom feed
* [PATCH 11/13] Introduce reduce_heads()
From: Miklos Vajna @ 2008-06-21 17:00 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Junio C Hamano, git
In-Reply-To: <cover.1214066798.git.vmiklos@frugalware.org>

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>
---
 Makefile               |    1 +
 builtin-reduce-heads.c |   44 ++++++++++++++++++++++++++++++++++++++++++++
 builtin.h              |    1 +
 commit.c               |   45 +++++++++++++++++++++++++++++++++++++++++++++
 commit.h               |    2 ++
 git.c                  |    1 +
 6 files changed, 94 insertions(+), 0 deletions(-)
 create mode 100644 builtin-reduce-heads.c

diff --git a/Makefile b/Makefile
index b003e3e..d660756 100644
--- a/Makefile
+++ b/Makefile
@@ -522,6 +522,7 @@ BUILTIN_OBJS += builtin-pack-refs.o
 BUILTIN_OBJS += builtin-prune-packed.o
 BUILTIN_OBJS += builtin-prune.o
 BUILTIN_OBJS += builtin-push.o
+BUILTIN_OBJS += builtin-reduce-heads.o
 BUILTIN_OBJS += builtin-read-tree.o
 BUILTIN_OBJS += builtin-reflog.o
 BUILTIN_OBJS += builtin-remote.o
diff --git a/builtin-reduce-heads.c b/builtin-reduce-heads.c
new file mode 100644
index 0000000..ff6178d
--- /dev/null
+++ b/builtin-reduce-heads.c
@@ -0,0 +1,44 @@
+#include "cache.h"
+#include "commit.h"
+#include "strbuf.h"
+
+static void show_commit_list(struct commit_list *list)
+{
+	while (list) {
+		struct strbuf it;
+		struct commit *commit = list->item;
+
+		list = list->next;
+		strbuf_init(&it, 128);
+		parse_commit(commit);
+		pretty_print_commit(CMIT_FMT_ONELINE, commit, &it,
+				    0, NULL, NULL, 0, 0);
+		puts(it.buf);
+		strbuf_release(&it);
+	}
+}
+
+int cmd_reduce_heads(int ac, const char **av, const char *prefix)
+{
+	struct commit_list *list = NULL, **tail = &list;
+	int i;
+
+	for (i = 1; i < ac; i++) {
+		struct commit *commit;
+		unsigned char sha1[20];
+
+		if (get_sha1(av[i], sha1))
+			die("'%s' is not a valid ref.", av[i]);
+		commit = lookup_commit_reference(sha1);
+		if (!commit)
+			die("cannot find commit %s", av[i]);
+		tail = &commit_list_insert(commit, tail)->next;
+	}
+
+	show_commit_list(list);
+	putchar('\n');
+
+	list = reduce_heads(list);
+	show_commit_list(list);
+	return 0;
+}
diff --git a/builtin.h b/builtin.h
index 2b01fea..f069ee7 100644
--- a/builtin.h
+++ b/builtin.h
@@ -75,6 +75,7 @@ extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_reflog(int argc, const char **argv, const char *prefix);
 extern int cmd_remote(int argc, const char **argv, const char *prefix);
 extern int cmd_config(int argc, const char **argv, const char *prefix);
+extern int cmd_reduce_heads(int argc, const char **argv, const char *prefix);
 extern int cmd_rerere(int argc, const char **argv, const char *prefix);
 extern int cmd_reset(int argc, const char **argv, const char *prefix);
 extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
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 */
diff --git a/git.c b/git.c
index 2fbe96b..80b16ce 100644
--- a/git.c
+++ b/git.c
@@ -285,6 +285,7 @@ static void handle_internal_command(int argc, const char **argv)
 		{ "prune-packed", cmd_prune_packed, RUN_SETUP },
 		{ "push", cmd_push, RUN_SETUP },
 		{ "read-tree", cmd_read_tree, RUN_SETUP },
+		{ "reduce-heads", cmd_reduce_heads, RUN_SETUP },
 		{ "reflog", cmd_reflog, RUN_SETUP },
 		{ "remote", cmd_remote, RUN_SETUP },
 		{ "repo-config", cmd_config },
-- 
1.5.6

^ permalink raw reply related

* [PATCH 10/13] Introduce get_merge_bases_many()
From: Miklos Vajna @ 2008-06-21 17:00 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Junio C Hamano, git
In-Reply-To: <cover.1214066798.git.vmiklos@frugalware.org>

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

* [PATCH 00/13] Build in merge
From: Miklos Vajna @ 2008-06-21 17:00 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7vr6arazp9.fsf@gitster.siamese.dyndns.org>

On Sat, Jun 21, 2008 at 02:45:38AM -0700, Junio C Hamano <gitster@pobox.com> wrote:
> Miklos Vajna <vmiklos@frugalware.org> writes:
>
> > +struct commit_list *filter_independent(unsigned char *head,
> > +   struct commit_list *heads)
> > +{
> > +   struct commit_list *b, *i, *j, *k, *bases = NULL, *ret = NULL;
> > +   struct commit_list **pptr = &ret;
> > +
> > +   commit_list_insert(lookup_commit(head), &heads);
>
> Isn't the special casing of head making this function less easier to
> reuse in other contexts?  "show-branch --independent" is about getting
> N commits and removing commits from that set that can be reachable
> from another commit, so there is no need nor reason to treat one
> "head" in any special way.

Yes, sure. It originally it was in builtin-merge.c and _there_ it was
easier this way but in commit.c this should be generalized.

> > +   for (i = heads; i; i = i->next) {
> > +           for (j = heads; j; j = j->next) {
> > +                   if (i == j)
> > +                           continue;
> > +                   b = get_merge_bases(i->item, j->item, 1);
> > +                   for (k = b; k; k = k->next)
> > +                           commit_list_insert(k->item, &bases);
> > +           }
> > +   }
>
> You run (N-1)*N merge-base computation to get all pairwise merge-bases
> here.  As merge-base(A,B) == merge-base(B,A), this is computing the
> same
> thing twice.
>
> Isn't your "b" leaking?
>
> > +   for (i = heads; i; i = i->next) {
> > +           int found = 0;
> > +           for (b = bases; b; b = b->next) {
> > +                   if (!hashcmp(i->item->object.sha1, b->item->object.sha1)) {
> > +                           found = 1;
>
> Then you see if the given heads exactly match one of the merge bases
> you
> found earlier.  But does this have to be in a separate pass?
>
> Isn't your "bases" list leaking?
>
> Even though you may be able to reduce more than 25 heads, you run N^2
> merge base traversals, which means 625 merge base traversals for 25
> heads;
> show-branch engine can do the same thing with a single traversal.
>
> Can't we do better than O(N^2)?

Right, actually my primary target was to achieve the right behaviour and
I did not care enough about performance and memory leaks, my bad.

> Let's step back a bit and think.  You have N commits (stop thinking
> about "my head and N other heads" like your function signature
> suggests).  For each one, you would want to see if it is reachable
> from any of the other (N-1) commits, and if so, you would exclude it
> from the resulting set.  And you do that for all N commits and you are
> done.  You can relatively easily do this with an O(N) traversals.
>
> Now, if you have one commit and other (N-1) commits, is there a way to
> efficiently figure out if that one commit is reachable from any of the
> other (N-1) commits?
>
> If there were a merge of these other (N-1) commits, and if you compute
> a merge base between that merge commit and the one commit you are
> looking at, what would you get?  Yes, you will get your commit back if
> and only if it is reachable from some of these (N-1) commits.
>
> If you recall the merge-base-many patch we discussed earlier, that is
> exactly what it computes, isn't it?

Exactly. :-)

Now I dropped the filter_independent() patch from my branch and replaced
it with your ones, since reduce_heads() does exactly the same, but it
performs much better.

So, changes since the previous series:

- added a testcase to make sure parents are reduced properly (as
  suggested by Dscho)

- port 037e98f20241bf013cd007b0924936a29c3cacfa to builtin-merge.c ("fix
  typo in usage message")

- dropped filter_independent() and replaced it with reduce_heads() (your
  two patches)

I'm not sending patches 01-08 (up to "Introduce
get_octopus_merge_bases()") since they are unchanged and to avoid
unnecessary traffic.

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
  parseopt: add a new PARSE_OPT_ARGV0_IS_AN_OPTION option
  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.
  Build in merge
  Add new test case to ensure git-merge filters for independent parents

 Makefile                                      |    3 +-
 alias.c                                       |   54 ++
 builtin-fmt-merge-msg.c                       |  157 ++--
 builtin-merge-recursive.c                     |    8 -
 builtin-merge.c                               | 1130 +++++++++++++++++++++++++
 builtin-read-tree.c                           |   24 -
 builtin-reduce-heads.c                        |   44 +
 builtin-remote.c                              |   39 +-
 builtin.h                                     |    5 +
 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                                         |   55 +--
 parse-options.c                               |   11 +-
 parse-options.h                               |    1 +
 read-cache.c                                  |   31 +
 t/t7601-merge-pull-config.sh                  |   72 ++
 t/t7602-merge-octopus-many.sh                 |   52 ++
 t/t7603-merge-filter-independent.sh           |   63 ++
 21 files changed, 1709 insertions(+), 189 deletions(-)
 create mode 100644 builtin-merge.c
 create mode 100644 builtin-reduce-heads.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-filter-independent.sh

^ permalink raw reply

* [PATCH 12/13] Build in merge
From: Miklos Vajna @ 2008-06-21 17:00 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <cover.1214066798.git.vmiklos@frugalware.org>

Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---
 Makefile                                      |    2 +-
 builtin-merge.c                               | 1130 +++++++++++++++++++++++++
 builtin.h                                     |    1 +
 git-merge.sh => contrib/examples/git-merge.sh |    0 
 git.c                                         |    1 +
 5 files changed, 1133 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 d660756..f822458 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..dadde80
--- /dev/null
+++ b/builtin-merge.c
@@ -0,0 +1,1130 @@
+/*
+ * 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"
+
+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_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 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);
+
+	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)
+{
+	struct rev_info rev;
+	struct commit *commit;
+	struct strbuf out;
+	struct commit_list *j;
+
+	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) {
+		j->item->object.flags &= ~UNINTERESTING;
+		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(out_fd, out.buf, out.len);
+	strbuf_release(&out);
+}
+
+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");
+
+	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 match = 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')
+		match = 1;
+	else {
+		ptr = strrchr(remote, '~');
+		if (ptr && *(ptr+1) != '0' && isdigit(*(ptr+1))) {
+			ptr++;
+			match = 1;
+			while (*(++ptr))
+				if (!isdigit(*ptr)) {
+					match = 0;
+					break;
+				}
+		}
+	}
+	if (match) {
+		struct strbuf truname;
+		strbuf_addstr(&truname, remote);
+		strbuf_setlen(&truname, strrchr(truname.buf, '~')-truname.buf);
+		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 == 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, 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);
+		parse_options(argc, argv, builtin_merge_options,
+				builtin_merge_usage,
+				PARSE_OPT_ARGV0_IS_AN_OPTION);
+		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;
+
+	memset(&lock_file, 0, sizeof(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 = xcalloc(1, sizeof(*opts.dir));
+	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;
+	while (1) {
+		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(list.items[i].path,
+					item->util, &use_strategies);
+		}
+		return;
+	}
+	for (i = 0; i < strategies.nr; i++)
+		if ((enum strategy)strategies.items[i].util & strategy)
+			path_list_append_strategy(strategies.items[i].path,
+				strategies.items[i].util,
+				&use_strategies);
+}
+
+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);
+
+	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 == 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);
+		reset_hard(remote_head->sha1, 0);
+		return 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.
+			 */
+			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 == 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]);
+		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);
+	}
+
+	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;
+		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;
+
+		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 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)) {
+				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;
+
+			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");
+
+				/* 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;
+				}
+			}
+			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 f069ee7..64830ae 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 80b16ce..d4c5027 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

* [PATCH 13/13] Add new test case to ensure git-merge reduces octopus parents when possible
From: Miklos Vajna @ 2008-06-21 17:15 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <67668993d3fc7ea52c9c191178f3e1dea7bb4282.1214066799.git.vmiklos@frugalware.org>

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

On Sat, Jun 21, 2008 at 07:00:50PM +0200, Miklos Vajna <vmiklos@frugalware.org> wrote:
> The new C version uses filter_independent() from commit.c for this, so
> add test to ensure it works as expected.

Ouch, this info is now outdated. I updated the commit message and
renamed the test, no functional changes.

 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

* Re: git-relink status (or bug?)
From: Junio C Hamano @ 2008-06-21 19:22 UTC (permalink / raw)
  To: Marc Zonzon; +Cc: git
In-Reply-To: <20080621103636.GA696@kernoel.dyndns.org>

Marc Zonzon <marc.zonzon+git@gmail.com> writes:

> I found very few information about git relink, but as it appears in
> changelog of v1.5.4 I suppose it is not obsoleted.

I do not think anybody uses it these days.  Instead either they clone with
reference (or -s), or perhaps use new-workdir.

Here is a totally untested fix.

The "careful" part can be made much more clever and efficient by learning
implementation details about the .idx file (it has the checksum for itself
and the checksum for its .pack file at the end) but I did not bother.

I do not think this in its current shape is committable, without
improvements and success reports from the list.  Hint, hint...


 git-relink.perl |   26 ++++++++++++++++++--------
 1 files changed, 18 insertions(+), 8 deletions(-)

diff --git a/git-relink.perl b/git-relink.perl
index 15fb932..68e0f0e 100755
--- a/git-relink.perl
+++ b/git-relink.perl
@@ -10,10 +10,11 @@ use 5.006;
 use strict;
 use warnings;
 use Getopt::Long;
+use File::Compare;
 
 sub get_canonical_form($);
 sub do_scan_directory($$$);
-sub compare_two_files($$);
+sub compare_and_link($$$);
 sub usage();
 sub link_two_files($$);
 
@@ -67,6 +68,7 @@ sub do_scan_directory($$$) {
 
 	my $sfulldir = sprintf("%sobjects/%s/",$srcdir,$subdir);
 	my $dfulldir = sprintf("%sobjects/%s/",$dstdir,$subdir);
+	my $careful = ($subdir eq 'pack');
 
 	opendir(S,$sfulldir)
 		or die "Failed to opendir $sfulldir: $!";
@@ -75,14 +77,14 @@ sub do_scan_directory($$$) {
 		my $sfilename = $sfulldir . $file;
 		my $dfilename = $dfulldir . $file;
 
-		compare_two_files($sfilename,$dfilename);
+		compare_and_link($sfilename, $dfilename, $careful);
 
 	}
 	closedir(S);
 }
 
-sub compare_two_files($$) {
-	my ($sfilename, $dfilename) = @_;
+sub compare_and_link($$$) {
+	my ($sfilename, $dfilename, $careful) = @_;
 
 	# Perl's stat returns relevant information as follows:
 	# 0 = dev number
@@ -100,12 +102,20 @@ sub compare_two_files($$) {
 
 	if ( ($sstatinfo[0] == $dstatinfo[0]) &&
 	     ($sstatinfo[1] != $dstatinfo[1])) {
-		if ($sstatinfo[7] == $dstatinfo[7]) {
+		my $differs = undef;
+		if ($sstatinfo[7] != $dstatinfo[7]) {
+			$differs = "size";
+		}
+		if (!$differs && $careful) {
+			if (File::Compare::compare($sfilename, $dfilename)) {
+				$differs = "contents";
+			}
+		}
+		if (!$differs) {
 			link_two_files($sfilename, $dfilename);
-
 		} else {
-			my $err = sprintf("ERROR: File sizes are not the same, cannot relink %s to %s.\n",
-				$sfilename, $dfilename);
+			my $err = sprintf("ERROR: File differs (%s), cannot relink %s to %s.\n",
+					  $differs, $sfilename, $dfilename);
 			if ($fail_on_different_sizes) {
 				die $err;
 			} else {

^ permalink raw reply related

* Re: git-relink status (or bug?)
From: marc zonzon @ 2008-06-21 20:23 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7v4p7ma90e.fsf@gitster.siamese.dyndns.org>

Thank you for your answer

On Sat, Jun 21, 2008 at 9:22 PM, Junio C Hamano <gitster@pobox.com> wrote:

>
> I do not think anybody uses it these days.  Instead either they clone with
> reference (or -s), or perhaps use new-workdir.

The goal of git-relink is analogous to the default local clone,
hardlinks can be safer than
 sharing because you don't loose anything when the origin directory
reset a branch.
I remark that git-clone(1) warn about -s use, but not --reference, but
they seems identical on these aspects.

In numerous cases you cannot suppose your alternate will keep your
objects forever.
I have posted recently such a case study
http://thread.gmane.org/gmane.comp.version-control.git/85407
and when trying hardlinks, i found this bug. It happens that sharing
was a better solution
(but only with the help of Shawn answer I could set it up!)

This new-workdir seems also a nice script, that I never looked at
before (But why is there no documentation on these contrib?)

>
> Here is a totally untested fix.
>
> The "careful" part can be made much more clever and efficient by learning
> implementation details about the .idx file (it has the checksum for itself
> and the checksum for its .pack file at the end) but I did not bother.

Thank you
I see that you only take the safe way, don't hardlink if something is
different, but there would be a more efficient one, to link when the
packs have the same name, and link also the idx. If they have the same
name they have the same content (with a fair probability!)

I cannot provide a patch for that, because I'm not a perl programmer,
and I'm too lazy to rewrite it in C or python!

> I do not think this in its current shape is committable, without
> improvements and success reports from the list.  Hint, hint...

Being "perl challenged" I cannot readproof the script, but at least I
can test it but only on trivial test cases which make git-relink fail!
(I have only tried once to use it to solve the previously cited
problem)

Marc

^ permalink raw reply

* Re: [msysGit] Re: MinGW port pull request
From: Johannes Sixt @ 2008-06-21 21:18 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: msysGit, Git Mailing List
In-Reply-To: <7vskv79l37.fsf@gitster.siamese.dyndns.org>

On Samstag, 21. Juni 2008, Junio C Hamano wrote:
> Johannes Sixt <j.sixt@viscovery.net> writes:
> > please pull the MinGW (Windows) port patch series from
> >
> > git://repo.or.cz/git/mingw/j6t.git for-junio
>
> Took a look.  A quick impression.
>
>  * Too many whitespace breakages in borrowed compat/regex.[ch] are very
>    distracting.

Will fixup, no problem.

>  * It is a very nice touch to rename sample templates to make sure they
>    are not executable (after all they are just samples).

Note that they are only renamed on Windows. Do you think it makes sense to 
rename them on every platform?

I think I'll have to add a note in Documentation/githooks.txt for Windows that 
mentions that the '.noexec' part must be removed.

>  * Shouldn't my_mktime() if exported out of date.c be named a bit better?

How about tm_to_time_t()?

>  * The ifdef block in git.c::main() introduces decl-after-stmt which we
>    tend to avoid, but it is much worse to solve it by adding another ifdef
>    block just to enclose decl of char *bslash at the beginning of the
>    function.  Perhaps enclose it in an extra block?

It does not in my version. IIRC, I was careful that it does not.

>  * In sanitary_path_copy(), you left "break;" after /* (1) */ but now that
>    "break" is not inside a switch() anymore, so you are breaking out of
>    something else, aren't you?  -- Ah, the clean-up phase will be no-op in
>    that case because src points at '\0'.  Tricky but looks correct ;-)

I'm pretty certain that it is an omission. I'll remove the 'break' in the next 
round. It's just unnecessarily tricky.

>  * There seem to be an unrelated general fix in upload-pack.c

Yes, indeed. It's the fflush(pack_pipe) that could make a difference. I wonder 
why this ever worked without it. Notice that traverse_commit_list calls 
show_object() last, but show_object() never flushes pack_pipe. Are fdopen()ed 
pipes line-buffered or unbuffered?

>  * There are still too many ifdefs.  I am wondering if the changes to
>    pager and process stuff is easier to manage in the longer term if they
>    are made into completely separate files (i.e. instead of linking
>    pager.o you would link mingw-pager.o).  I dunno.

I think that would not be helpful. Both parts need to be maintained, whether 
they are in the same file or in different files. If they are in one file, and 
someone needs to make a change, then there is a chance that a corresponding 
change is made in the MINGW32 arm. If not then there is another chance that 
the person would at least say "I don't know how to do it for MINGW32". But if 
you separate the implementations completely, then both chances are missed 
much easier.

To reduce #ifdef in other places I have some proposals. Please tell me which 
you like or dislike:

* The #ifdef STRIP_EXTENSION can be removed with a conditional like this:

	static const char ext[] = STRIP_EXTENSION; // "" or ".exe"
	if (sizeof(ext) > 1) {
		...
	}

* The #ifdef in main() of git.c can be removed with a custom loop that checks 
for is_dir_sep():

	slash = cmd + strlen(cmd);
	while (slash > cmd && !is_dir_sep(*--slash))
		;
	if (slash >= cmd) {	// was: if (slash) {
		...

* We could wrap getenv(), so that the getenv("TEMPDIR") in path.c does not 
need to be followed up with getenv("TMP") and getenv("TEMP"). I'll do that.

* The #ifdef in setup.c, prefix_filename() could easily be removed by using 
the MINGW32 arm everywhere. This would penalize non-Windows, however, 
prefix_filename() is not performance critical.

>  * There is an interaction with dr/ceiling topic that is already in 'next'
>    that needs to be resolved before we merge this in 'next'.

How do you want me to proceed? Rebase on top of dr/ceiling? Wait until 
dr/ceiling is in master and rebase again? Merge it into my series? (I would 
make the merge the last commit in my series.) I'm asking because support of 
dr/ceiling was not overwhelming.

-- Hannes

^ permalink raw reply

* about c8af1de9 (git status uses pager)
From: Jan Engelhardt @ 2008-06-21 21:21 UTC (permalink / raw)
  To: Bart Trojanowski; +Cc: git


Since git 1.5.6, `git status` always invokes a pager, which is really 
annoying when the output is less than the number of terminal rows 
available. Can I turn that off somehow or do I need to send a reverting 
patch?


Jan

^ permalink raw reply

* Re: MinGW port pull request
From: Jim Raden @ 2008-06-21 21:21 UTC (permalink / raw)
  To: johannes.sixt; +Cc: Junio C Hamano, msysGit, Git Mailing List
In-Reply-To: <200806212318.47745.johannes.sixt@telecom.at>

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

On Sat, Jun 21, 2008 at 5:18 PM, Johannes Sixt <johannes.sixt@telecom.at>
wrote:

>
> >  * There are still too many ifdefs.  I am wondering if the changes to
> >    pager and process stuff is easier to manage in the longer term if they
> >    are made into completely separate files (i.e. instead of linking
> >    pager.o you would link mingw-pager.o).  I dunno.
>
> I think that would not be helpful. Both parts need to be maintained,
> whether
> they are in the same file or in different files. If they are in one file,
> and
> someone needs to make a change, then there is a chance that a corresponding
> change is made in the MINGW32 arm. If not then there is another chance that
> the person would at least say "I don't know how to do it for MINGW32". But
> if
> you separate the implementations completely, then both chances are missed
> much easier.
>
> I quite agree!!

[-- Attachment #2: Type: text/html, Size: 1256 bytes --]

^ permalink raw reply

* Re: about c8af1de9 (git status uses pager)
From: Vegard Nossum @ 2008-06-21 21:30 UTC (permalink / raw)
  To: Jan Engelhardt; +Cc: Bart Trojanowski, git
In-Reply-To: <alpine.LNX.1.10.0806212319410.22036@fbirervta.pbzchgretzou.qr>

On Sat, Jun 21, 2008 at 11:21 PM, Jan Engelhardt <jengelh@medozas.de> wrote:
>
> Since git 1.5.6, `git status` always invokes a pager, which is really
> annoying when the output is less than the number of terminal rows
> available. Can I turn that off somehow or do I need to send a reverting
> patch?

I think it would work to set PAGER="less -F" (a.k.a. --quit-if-one-screen)?

There's also GIT_PAGER variable, core.pager git setting, etc.


Vegard

-- 
"The animistic metaphor of the bug that maliciously sneaked in while
the programmer was not looking is intellectually dishonest as it
disguises that the error is the programmer's own creation."
	-- E. W. Dijkstra, EWD1036

^ permalink raw reply

* Re: [PATCH] git-svn: make rebuild respect rewriteRoot option
From: Eric Wong @ 2008-06-21 21:39 UTC (permalink / raw)
  To: Jan Krüger; +Cc: Git mailing list
In-Reply-To: <20080620003359.0dbb7725@neuron>

Jan Krüger <jk@jk.gs> wrote:
> Suppose someone fetches git-svn-ified commits from another repo and then
> attempts to use 'git-svn init --rewrite-root=foo bar'. Using git svn rebase
> after that will fail badly:
> 
>  * For each commit tried by working_head_info, rebuild is called indirectly.
>  * rebuild will iterate over all commits and skip all of them because the
>    URL does not match. Because of that no rev_map file is generated at all.
>  * Thus, rebuild will run once for every commit. This takes ages.
>  * In the end there still isn't any rev_map file and thus working_head_info
>    fails.
> 
> Addressing this behaviour fixes an apparently not too uncommon problem with
> providing git-svn mirrors of Subversion repositories. Some repositories are
> accessed using different URLs depending on whether the user has push
> privileges or not. In the latter case, an anonymous URL is often used that
> differs from the push URL. Providing a mirror that is usable in both cases
> becomes a lot more possible with this change.
> 
> Signed-off-by: Jan Krüger <jk@jk.gs>
> ---
> Since this patch focuses on a case that is discouraged by the git-svn
> manpage (use rewriteRoot even though commits already exist), a bit of
> discussion might be helpful. On the up side, I think it doesn't affect any
> other cases.
> 
> The specific problem situation looks like this:
> 
> % git fetch git://.../clone-of-some-svn-repo (...)
> % git svn init -s --rewrite-root=<url contained in fetched commits> <url \
>       we want to use>
> % git svn rebase
> [Boom]
> 
> This patch passes all existing git-svn tests; feel free to suggest
> additional test(s) to cover this sort of situation.

Seems to make sense to me...  If you have a test case for this
it'd be nicer :)

Acked-by: Eric Wong <normalperson@yhbt.net>

>  git-svn.perl |    6 +++---
>  1 files changed, 3 insertions(+), 3 deletions(-)
> 
> diff --git a/git-svn.perl b/git-svn.perl
> index a54979d..4c9c59b 100755
> --- a/git-svn.perl
> +++ b/git-svn.perl
> @@ -2577,8 +2577,8 @@ sub rebuild {
>  	my ($log, $ctx) =
>  	    command_output_pipe(qw/rev-list --pretty=raw --no-color --reverse/,
>  	                        $self->refname, '--');
> -	my $full_url = $self->full_url;
> -	remove_username($full_url);
> +	my $metadata_url = $self->metadata_url;
> +	remove_username($metadata_url);
>  	my $svn_uuid = $self->ra_uuid;
>  	my $c;
>  	while (<$log>) {
> @@ -2596,7 +2596,7 @@ sub rebuild {
>  		# if we merged or otherwise started elsewhere, this is
>  		# how we break out of it
>  		if (($uuid ne $svn_uuid) ||
> -		    ($full_url && $url && ($url ne $full_url))) {
> +		    ($metadata_url && $url && ($url ne $metadata_url))) {
>  			next;
>  		}
>  

^ permalink raw reply

* Re: about c8af1de9 (git status uses pager)
From: Junio C Hamano @ 2008-06-21 21:42 UTC (permalink / raw)
  To: Vegard Nossum; +Cc: Jan Engelhardt, Bart Trojanowski, git
In-Reply-To: <19f34abd0806211430x3d7195d8idc61b7103f899947@mail.gmail.com>

"Vegard Nossum" <vegard.nossum@gmail.com> writes:

> On Sat, Jun 21, 2008 at 11:21 PM, Jan Engelhardt <jengelh@medozas.de> wrote:
>>
>> Since git 1.5.6, `git status` always invokes a pager, which is really
>> annoying when the output is less than the number of terminal rows
>> available. Can I turn that off somehow or do I need to send a reverting
>> patch?
>
> I think it would work to set PAGER="less -F" (a.k.a. --quit-if-one-screen)?

Probably better with LESS=FRSX, which is what git uses as a sane default
if nothing is set.

^ permalink raw reply

* Re: about c8af1de9 (git status uses pager)
From: Jan Engelhardt @ 2008-06-21 21:45 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Vegard Nossum, Bart Trojanowski, git
In-Reply-To: <7vzlpe8nyo.fsf@gitster.siamese.dyndns.org>


On Saturday 2008-06-21 23:42, Junio C Hamano wrote:

>"Vegard Nossum" <vegard.nossum@gmail.com> writes:
>
>> On Sat, Jun 21, 2008 at 11:21 PM, Jan Engelhardt <jengelh@medozas.de> wrote:
>>>
>>> Since git 1.5.6, `git status` always invokes a pager, which is really
>>> annoying when the output is less than the number of terminal rows
>>> available. Can I turn that off somehow or do I need to send a reverting
>>> patch?
>>
>> I think it would work to set PAGER="less -F" (a.k.a. --quit-if-one-screen)?
>
>Probably better with LESS=FRSX, which is what git uses as a sane default
>if nothing is set.
>
I went with Vegard's suggestion to change the pager command in
~/.gitconfig, since I have the $LESS environment variable already
defined as "-MSi", and I do not want to change that; because if I am
going to run less (often at the end of a pipe), I certainly do not
want it to just quit on me. So -F in $LESS is a no-no.

Since I need "-MRSi" for git anyhow, tweaking ~/.gitconfig was easy.

^ permalink raw reply

* Re: [msysGit] Re: MinGW port pull request
From: Junio C Hamano @ 2008-06-21 21:47 UTC (permalink / raw)
  To: Johannes Sixt; +Cc: msysGit, Git Mailing List
In-Reply-To: <200806212318.47745.johannes.sixt@telecom.at>

Johannes Sixt <johannes.sixt@telecom.at> writes:

> * The #ifdef in setup.c, prefix_filename() could easily be removed by using 
> the MINGW32 arm everywhere. This would penalize non-Windows, however, 
> prefix_filename() is not performance critical.
>
>>  * There is an interaction with dr/ceiling topic that is already in 'next'
>>    that needs to be resolved before we merge this in 'next'.
>
> How do you want me to proceed? Rebase on top of dr/ceiling? Wait until 
> dr/ceiling is in master and rebase again? Merge it into my series? (I would 
> make the merge the last commit in my series.) I'm asking because support of 
> dr/ceiling was not overwhelming.

I personally feel MinGW branch is more important than ceiling work, not
just because it targets far wider audience but because it affects a lot
wider area.  j6t/mingw _will_ eventually graduate to master in some form
(possibly after fixups that is needed to keep things working on non
Windows environment), and dr/ceil may or may not.

So my preference would be to merge j6t/mingw into dr/ceil branch soon,
resolve conflicts there, and merge the result to 'next' when j6t/mingw is
merged to 'next' at the same time.  And you can help with that merge when
it happens.

^ permalink raw reply

* Re: Date parsing
From: Stephan Beyer @ 2008-06-21 21:52 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: sverre, Brandon Casey, Git Mailing List
In-Reply-To: <alpine.LFD.1.10.0806100942500.3101@woody.linux-foundation.org>

Hi,

> > On Tue, Jun 10, 2008 at 5:10 PM, Brandon Casey <casey@nrlssc.navy.mil> wrote:
> > > Take a look at match_multi_number in date.c
> > > European ordering is preferred when the separator is '.'
> > 
> > Ok, then I'll use . in the future, that's nice :).
> 
> Well, there are safer ways to give the date.
> 
> If you do it in strict rfc822 format, you'll never have any confusion 
> what-so-ever. The "approxidate()" thing tries to parse any random input, 
> but it *is* meant to be excessively liberal.

Today I've been playing around with approxidate(), too, and I think I
found some bug in date parsing. I let copy&paste speak...

Correct:

$ ./test-date "2008-07-01 23:59:59 +0200"
2008-07-01 23:59:59 +0200 -> 1214949599 +0200 -> Tue Jul  1 23:59:59 2008
2008-07-01 23:59:59 +0200 -> Tue Jul  1 23:59:59 2008

And even:

$ ./test-date "2008-07-01 24:00:00 +0200"
2008-07-01 24:00:00 +0200 -> 1214949600 +0200 -> Wed Jul  2 00:00:00 2008
2008-07-01 24:00:00 +0200 -> Wed Jul  2 00:00:00 2008

But then there's a jump in time:

$ ./test-date "2008-07-02 00:00:00 +0200"
2008-07-02 00:00:00 +0200 -> 1202335200 +0200 -> Wed Feb  6 23:00:00 2008
2008-07-02 00:00:00 +0200 -> Wed Feb  6 23:00:00 2008

$ ./test-date "2008-07-02 01:00:00 +0200"
2008-07-02 01:00:00 +0200 -> 1202338800 +0200 -> Thu Feb  7 00:00:00 2008
2008-07-02 01:00:00 +0200 -> Thu Feb  7 00:00:00 2008


If we let test-date just print the timestamp...

diff --git a/test-date.c b/test-date.c
index 62e8f23..18d53c1 100644
--- a/test-date.c
+++ b/test-date.c
@@ -11,10 +11,10 @@ int main(int argc, char **argv)
 		memcpy(result, "bad", 4);
 		parse_date(argv[i], result, sizeof(result));
 		t = strtoul(result, NULL, 0);
-		printf("%s -> %s -> %s", argv[i], result, ctime(&t));
+		printf("%s -> %lu\n", argv[i], t);
 
 		t = approxidate(argv[i]);
-		printf("%s -> %s\n", argv[i], ctime(&t));
+		printf("%s -> %lu\n", argv[i], t);
 	}
 	return 0;
 }
-- -- -- --

... then we get:

$ ./test-date "2008-07-01 23:59:59 +0200"
2008-07-01 23:59:59 +0200 -> 1214949599
2008-07-01 23:59:59 +0200 -> 1214949599

$ ./test-date "2008-07-02 00:00:00 +0200"
2008-07-02 00:00:00 +0200 -> 1202335200
2008-07-02 00:00:00 +0200 -> 1202335200

Also, with another timezone, we get:

$ ./test-date "2008-07-01 23:59:59 +0000"
2008-07-01 23:59:59 +0000 -> 1214956799
2008-07-01 23:59:59 +0000 -> 1214956799

$ ./test-date "2008-07-02 00:00:00 +0000"
2008-07-02 00:00:00 +0000 -> 1202342400
2008-07-02 00:00:00 +0000 -> 1202342400

Is something wrong in my format or is there a bug?

Providing timestamps works, of course ;-)

Regards,
  Stephan

-- 
Stephan Beyer <s-beyer@gmx.net>, PGP 0x6EDDD207FCC5040F

^ permalink raw reply related

* [PATCH] git.el: Don't reset HEAD in git-amend-file.
From: Nikolaj Schumacher @ 2008-06-21 22:27 UTC (permalink / raw)
  To: git

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

Hello.

The current implementation of git-amend-file is a little dangerous.
While git --amend is atomic, git-amend-file is not.

If the user calls it, but doesn't go through with the commit (due to
error or choice), git --reset HEAD^ has been called anyway.

With this patch it doesn't reset the HEAD till the actual commit.


regards,
Nikolaj Schumacher

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: patch --]
[-- Type: text/x-patch, Size: 6255 bytes --]

diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el
index 4fa853f..1360cb0 100644
--- a/contrib/emacs/git.el
+++ b/contrib/emacs/git.el
@@ -400,16 +400,17 @@ and returns the process output as a string, or nil if the git failed."
   (git-get-string-sha1
    (git-call-process-env-string (and index-file `(("GIT_INDEX_FILE" . ,index-file))) "write-tree")))
 
-(defun git-commit-tree (buffer tree head)
+(defun git-commit-tree (buffer tree parent &optional head)
   "Call git-commit-tree with buffer as input and return the resulting commit SHA1."
+  (unless head (setq head parent))
   (let ((author-name (git-get-committer-name))
         (author-email (git-get-committer-email))
         (subject "commit (initial): ")
         author-date log-start log-end args coding-system-for-write)
-    (when head
+    (when parent
       (setq subject "commit: ")
       (push "-p" args)
-      (push head args))
+      (push parent args))
     (with-current-buffer buffer
       (goto-char (point-min))
       (if
@@ -425,7 +426,7 @@ and returns the process output as a string, or nil if the git failed."
               (setq author-date (match-string 1)))
             (goto-char (point-min))
             (while (re-search-forward "^Parent: +\\([0-9a-f]+\\)" nil t)
-              (unless (string-equal head (match-string 1))
+              (unless (string-equal parent (match-string 1))
                 (setq subject "commit (merge): ")
                 (push "-p" args)
                 (push (match-string 1) args))))
@@ -852,7 +853,7 @@ Return the list of files that haven't been handled."
               (git-run-hook "pre-commit" `(("GIT_INDEX_FILE" . ,index-file))))
           (delete-file index-file))))))
 
-(defun git-do-commit ()
+(defun git-do-commit (&optional amend)
   "Perform the actual commit using the current buffer as log message."
   (interactive)
   (let ((buffer (current-buffer))
@@ -862,10 +863,11 @@ Return the list of files that haven't been handled."
           (message "You cannot commit unmerged files, resolve them first.")
         (unwind-protect
             (let ((files (git-marked-files-state 'added 'deleted 'modified))
-                  head head-tree)
+                  head parent head-tree)
               (unless (git-empty-db-p)
                 (setq head (git-rev-parse "HEAD")
-                      head-tree (git-rev-parse "HEAD^{tree}")))
+                      parent (if amend (git-rev-parse "HEAD^") head)
+                      head-tree (git-rev-parse (concat "HEAD^{tree}"))))
               (if files
                   (progn
                     (message "Running git commit...")
@@ -875,7 +877,7 @@ Return the list of files that haven't been handled."
                     (let ((tree (git-write-tree index-file)))
                       (if (or (not (string-equal tree head-tree))
                               (yes-or-no-p "The tree was not modified, do you really want to perform an empty commit? "))
-                          (let ((commit (git-commit-tree buffer tree head)))
+                          (let ((commit (git-commit-tree buffer tree parent head)))
                             (when commit
                               (condition-case nil (delete-file ".git/MERGE_HEAD") (error nil))
                               (condition-case nil (delete-file ".git/MERGE_MSG") (error nil))
@@ -1263,13 +1265,22 @@ Return the list of files that haven't been handled."
       (when sign-off (git-append-sign-off committer-name committer-email)))
     buffer))
 
-(defun git-commit-file ()
-  "Commit the marked file(s), asking for a commit message."
-  (interactive)
+(defun git-commit-file (&optional amend)
+  "Commit the marked file(s), asking for a commit message.
+With optional argument, amend HEAD."
+  (interactive "P")
   (unless git-status (error "Not in git-status buffer."))
+  (and amend (git-empty-db-p) (error "No commit to amend."))
   (when (git-run-pre-commit-hook)
     (let ((buffer (get-buffer-create "*git-commit*"))
           (coding-system (git-get-commits-coding-system))
+          (action (if amend
+                      `(lambda () (interactive) (git-do-commit t))
+                    'git-do-commit))
+          (env (if (boundp 'log-edit-diff-function)
+                   '((log-edit-listfun . git-log-edit-files)
+                     (log-edit-diff-function . git-log-edit-diff))
+                 'git-log-edit-files))
           author-name author-email subject date)
       (when (eq 0 (buffer-size buffer))
         (when (file-readable-p ".dotest/info")
@@ -1286,10 +1297,8 @@ Return the list of files that haven't been handled."
             (when (re-search-forward "^Date: \\(.*\\)$" nil t)
               (setq date (match-string 1)))))
         (git-setup-log-buffer buffer author-name author-email subject date))
-      (if (boundp 'log-edit-diff-function)
-	  (log-edit 'git-do-commit nil '((log-edit-listfun . git-log-edit-files)
-					 (log-edit-diff-function . git-log-edit-diff)) buffer)
-	(log-edit 'git-do-commit nil 'git-log-edit-files buffer))
+      (when amend (git-setup-commit-buffer "HEAD"))
+      (log-edit action nil env buffer)
       (setq font-lock-keywords (font-lock-compile-keywords git-log-edit-font-lock-keywords))
       (setq buffer-file-coding-system coding-system)
       (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t))))
@@ -1326,19 +1335,9 @@ Return the list of files that haven't been handled."
     files))
 
 (defun git-amend-commit ()
-  "Undo the last commit on HEAD, and set things up to commit an
-amended version of it."
+  "Call `git-commit-file' and have it amend HEAD."
   (interactive)
-  (unless git-status (error "Not in git-status buffer."))
-  (when (git-empty-db-p) (error "No commit to amend."))
-  (let* ((commit (git-rev-parse "HEAD"))
-         (files (git-get-commit-files commit)))
-    (when (git-call-process-display-error "reset" "--soft" "HEAD^")
-      (git-update-status-files (copy-sequence files) 'uptodate)
-      (git-mark-files git-status files)
-      (git-refresh-files)
-      (git-setup-commit-buffer commit)
-      (git-commit-file))))
+  (git-commit-file t))
 
 (defun git-find-file ()
   "Visit the current file in its own buffer."

^ permalink raw reply related

* Re: about c8af1de9 (git status uses pager)
From: Johannes Gilger @ 2008-06-21 21:42 UTC (permalink / raw)
  To: git
In-Reply-To: <alpine.LNX.1.10.0806212319410.22036@fbirervta.pbzchgretzou.qr>

On 21/06/08 23:21, Jan Engelhardt wrote:
> 
> Since git 1.5.6, `git status` always invokes a pager, which is really 
> annoying when the output is less than the number of terminal rows 
> available. Can I turn that off somehow or do I need to send a reverting 
> patch?
> 

Wow, I just noticed it myself. Why was that changed? I don't know about 
your status lines, but I for one find it really annoying. Anything 
that's in a pager isn't visible in my console afterwards. What's next? 
git branch in a pager too?

Regards,
Jojo

-- 
Johannes Gilger <heipei@hackvalue.de>
http://hackvalue.de/heipei/
GPG-Key: 0x42F6DE81
GPG-Fingerprint: BB49 F967 775E BB52 3A81  882C 58EE B178 42F6 DE81

^ permalink raw reply

* Re: Date parsing
From: Linus Torvalds @ 2008-06-21 22:28 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: sverre, Brandon Casey, Git Mailing List
In-Reply-To: <20080621215240.GD15111@leksak.fem-net>



On Sat, 21 Jun 2008, Stephan Beyer wrote:
> 
> Today I've been playing around with approxidate(), too, and I think I
> found some bug in date parsing. I let copy&paste speak...
> 
> Correct:
> 
> $ ./test-date "2008-07-01 23:59:59 +0200"
> 2008-07-01 23:59:59 +0200 -> 1214949599 +0200 -> Tue Jul  1 23:59:59 2008
> 2008-07-01 23:59:59 +0200 -> Tue Jul  1 23:59:59 2008
> 
> And even:
> 
> $ ./test-date "2008-07-01 24:00:00 +0200"
> 2008-07-01 24:00:00 +0200 -> 1214949600 +0200 -> Wed Jul  2 00:00:00 2008
> 2008-07-01 24:00:00 +0200 -> Wed Jul  2 00:00:00 2008
> 
> But then there's a jump in time:
> 
> $ ./test-date "2008-07-02 00:00:00 +0200"
> 2008-07-02 00:00:00 +0200 -> 1202335200 +0200 -> Wed Feb  6 23:00:00 2008
> 2008-07-02 00:00:00 +0200 -> Wed Feb  6 23:00:00 2008

Heh.

What you're seeing is that approxidate() does not like dates in the 
future. You're hitting this case:

                /* Be it commit time or author time, it does not make
                 * sense to specify timestamp way into the future.  Make
                 * sure it is not later than ten days from now...
                 */
                if (now + 10*24*3600 < specified)
                        return 0;

so approxidate() refuses to think that "2008-07-02" is a valid date in 
July, because it is more than ten days in the future. So it decides that 
if somebody tried to feed it a date like that, it must be the seventh of 
February instead of July.

So the refusal to look at future dates is part of trying to disambiguate 
the "dd.mm" form from the "mm.dd" form, where it will decide that if it's 
far in the future (where "far" is 10 days), it cannot be right.

Remember: the git date handling was _not_ meant to be a generic date 
library. It is very much meant to be a *git* date library. This is why you 
can say things like "7 days ago", and it will return something sane, but 
if you say "7 days from now", it will still think you're talking about 
seven days ago - it simply doesn't have the concept of "future date".

That said, I think that in this case the thing just doesn't make sense. 
For the specific case of iso time format, we perhaps shouldn't even try to 
refuse future dates. Does anybody use the insane yyyy-dd-mm format? 

To avoid the future check, you could try something like the appended.

Of course, the whole parser was really designed to parse email dates from 
the beginning, and rfc2822 actually ends up being totally unambiguous and 
also won't ever hit the "refuse future" case.

So I was wrong. Rather than using the European ISO format (yyyy-mm-dd), 
the really safest format is the "English month name spelled out" format, 
ie

	./test-date "12 Jul 2010"

parses correctly, but

	./test-date 2010-07-12

does not, because the latter is found suspect due to being in the future.

		Linus

---
 date.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/date.c b/date.c
index 1a4eb87..870419a 100644
--- a/date.c
+++ b/date.c
@@ -374,7 +374,7 @@ static int match_multi_number(unsigned long num, char c, const char *date, char
 
 		if (num > 70) {
 			/* yyyy-mm-dd? */
-			if (is_date(num, num2, num3, refuse_future, now, tm))
+			if (is_date(num, num2, num3, NULL, now, tm))
 				break;
 			/* yyyy-dd-mm? */
 			if (is_date(num, num3, num2, refuse_future, now, tm))

^ permalink raw reply related

* Re: Date parsing
From: Linus Torvalds @ 2008-06-21 22:37 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: sverre, Brandon Casey, Git Mailing List
In-Reply-To: <alpine.LFD.1.10.0806211507370.2926@woody.linux-foundation.org>



On Sat, 21 Jun 2008, Linus Torvalds wrote:
> 
> Remember: the git date handling was _not_ meant to be a generic date 
> library. It is very much meant to be a *git* date library.

Btw, I'm not proud of that. The whole of "date.c" was a huge hack to 
overcome the fact that

 - standard library date parsing is pure and utter shit, even for standard 
   date formats.

 - Never mind the standard date formats, very few seem to do the relative 
   dateslike "10 days ago".

 - So I couldn't find anybody elses code to "borrow" that did a reasonable 
   job and handled the nice relative dates too.

The GNU "date" command has pretty good support for doing things like "5 
minutes ago", but I think I tried to look at the code, and quicky decided 
that it was not an option (when I say "I think I tried", I have to admit 
that I don't have strong memories from it, but I suspect it was all so 
utterly horrid that I repressed the episode).

I don't mind using other peoples code (the whole xdiff library and the 
SHA1 stuff was certainly all from outside), but it needs to be in a format 
that doesn't cause projectile vomiting.

If somebody has a good date parser, I think we could drop the git-specific 
one in an instant.

Or if somebody wants to just improve on it... Hint, hint.

		Linus

^ permalink raw reply

* [RFC/PATCH v3] gitweb: respect $GITPERLLIB
From: Lea Wiemann @ 2008-06-21 22:40 UTC (permalink / raw)
  To: git; +Cc: Lea Wiemann
In-Reply-To: <7vabhfazok.fsf@gitster.siamese.dyndns.org>

gitweb/gitweb.cgi now respects $GITPERLLIB, like the Perl-based Git
commands.

This patch is not for inclusion, it'll be squashed with a larger
commit.
---
Junio C Hamano wrote:
> This part seems to duplicate quite a bit of sed insn used elsewhere, and
> we may want to factor the common part out, perhaps like this...

Thanks!  This needs some tender loving quoting though; diff to your
version:

   diff --git a/Makefile b/Makefile
   index e6fd8ac..92a802f 100644
   --- a/Makefile
   +++ b/Makefile
   @@ -1065,13 +1065,13 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak
    perl/perl.mak: GIT-CFLAGS perl/Makefile perl/Makefile.PL
    	$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' $(@F)
 
    PERL_USE_LIB_REWRITE = \
    	-e '1{' \
   -	-e '	s|#!.*perl|#!$(PERL_PATH_SQ)|' \
   +	-e '	s|\#!.*perl|\#!$(PERL_PATH_SQ)|' \
    	-e '	h' \
   -	-e '	s=.*=use lib (split(/:/, $$ENV{GITPERLLIB} || "@@INSTLIBDIR@@"));=' \
   +	-e '	s=.*=use lib (split(/:/, \$$ENV{GITPERLLIB} || \"@@INSTLIBDIR@@\"));=' \
    	-e '	H' \
    	-e '	x' \
    	-e '}' \
    	-e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
    	-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g'

I wonder though if that's too brittle or unmaintainable and we should
rather use the explicit version in v2 (even if it duplicated code);
I'd prefer v2 off the top of my head.

Also, note that I really only did trial-and-error quoting here. ;-)

-- Lea

 Makefile |   25 ++++++++++++++-----------
 1 files changed, 14 insertions(+), 11 deletions(-)

diff --git a/Makefile b/Makefile
index fda9133..92a802f 100644
--- a/Makefile
+++ b/Makefile
@@ -1065,25 +1065,28 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak
 perl/perl.mak: GIT-CFLAGS perl/Makefile perl/Makefile.PL
 	$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' $(@F)
 
+PERL_USE_LIB_REWRITE = \
+	-e '1{' \
+	-e '	s|\#!.*perl|\#!$(PERL_PATH_SQ)|' \
+	-e '	h' \
+	-e '	s=.*=use lib (split(/:/, \$$ENV{GITPERLLIB} || \"@@INSTLIBDIR@@\"));=' \
+	-e '	H' \
+	-e '	x' \
+	-e '}' \
+	-e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
+	-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g'
+
 $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
 	$(QUIET_GEN)$(RM) $@ $@+ && \
 	INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C perl -s --no-print-directory instlibdir` && \
-	sed -e '1{' \
-	    -e '	s|#!.*perl|#!$(PERL_PATH_SQ)|' \
-	    -e '	h' \
-	    -e '	s=.*=use lib (split(/:/, $$ENV{GITPERLLIB} || "@@INSTLIBDIR@@"));=' \
-	    -e '	H' \
-	    -e '	x' \
-	    -e '}' \
-	    -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
-	    -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
-	    $@.perl >$@+ && \
+	sed $(PERL_USE_LIB_REWRITE) $@.perl >$@+ && \
 	chmod +x $@+ && \
 	mv $@+ $@
 
 gitweb/gitweb.cgi: gitweb/gitweb.perl
 	$(QUIET_GEN)$(RM) $@ $@+ && \
-	sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
+	INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C perl -s --no-print-directory instlibdir` && \
+	sed $(PERL_USE_LIB_REWRITE) \
 	    -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
 	    -e 's|++GIT_BINDIR++|$(bindir)|g' \
 	    -e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \
-- 
1.5.6.85.g0a2e.dirty

^ permalink raw reply related

* Re: Date parsing
From: Stephan Beyer @ 2008-06-21 22:40 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: sverre, Brandon Casey, Git Mailing List
In-Reply-To: <alpine.LFD.1.10.0806211507370.2926@woody.linux-foundation.org>

Hi,

On Sat, Jun 21, 2008 at 03:28:56PM -0700,
Linus Torvalds <torvalds@linux-foundation.org>:
[...]
>                 /* Be it commit time or author time, it does not make
>                  * sense to specify timestamp way into the future.  Make
>                  * sure it is not later than ten days from now...
>                  */
>                 if (now + 10*24*3600 < specified)
>                         return 0;
[...]

Great.
So it really turns out that this bug is a nifty feature. (Except if we receive
patches from the future with a numeric date format.)

Thanks for the explanation,
  Stephan

-- 
Stephan Beyer <s-beyer@gmx.net>, PGP 0x6EDDD207FCC5040F

^ permalink raw reply

* [PATCH] api-builtin.txt: update and fix typo
From: Stephan Beyer @ 2008-06-21 23:54 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Stephan Beyer

Mention NEED_WORK_TREE flag and command-list.txt.
Fix "bulit-in" typo and AsciiDoc-formatting of a paragraph.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 Documentation/technical/api-builtin.txt |   15 ++++++++++-----
 1 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/Documentation/technical/api-builtin.txt b/Documentation/technical/api-builtin.txt
index 52cdb4c..7ede1e6 100644
--- a/Documentation/technical/api-builtin.txt
+++ b/Documentation/technical/api-builtin.txt
@@ -4,7 +4,7 @@ builtin API
 Adding a new built-in
 ---------------------
 
-There are 4 things to do to add a bulit-in command implementation to
+There are 4 things to do to add a built-in command implementation to
 git:
 
 . Define the implementation of the built-in command `foo` with
@@ -18,8 +18,8 @@ git:
   defined in `git.c`.  The entry should look like:
 
 	{ "foo", cmd_foo, <options> },
-
-  where options is the bitwise-or of:
++
+where options is the bitwise-or of:
 
 `RUN_SETUP`::
 
@@ -33,6 +33,12 @@ git:
 	If the standard output is connected to a tty, spawn a pager and
 	feed our output to it.
 
+`NEED_WORK_TREE`::
+
+	Make sure there is a work tree, i.e. the command cannot act
+	on bare repositories.
+	This makes only sense when `RUN_SETUP` is also set.
+
 . Add `builtin-foo.o` to `BUILTIN_OBJS` in `Makefile`.
 
 Additionally, if `foo` is a new command, there are 3 more things to do:
@@ -41,8 +47,7 @@ Additionally, if `foo` is a new command, there are 3 more things to do:
 
 . Write documentation in `Documentation/git-foo.txt`.
 
-. Add an entry for `git-foo` to the list at the end of
-  `Documentation/cmd-list.perl`.
+. Add an entry for `git-foo` to `command-list.txt`.
 
 
 How a built-in is called
-- 
1.5.6.310.g344d

^ permalink raw reply related

* [PATCH v3 1/2] t3404: stricter tests for git-rebase--interactive
From: Stephan Beyer @ 2008-06-21 23:55 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Christian Couder, Stephan Beyer
In-Reply-To: <7v4p7nazof.fsf@gitster.siamese.dyndns.org>

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
Hi,

ok, next try.
Because I do not want to do a half-ass job, I changed
the remaining ! git to test_must_fail and changed the commit
message subject and removed the commit message body. ;-)

Again, this only applies cleanly to "pu".

 t/t3404-rebase-interactive.sh |   43 ++++++++++++++++++++++++++++++++--------
 1 files changed, 34 insertions(+), 9 deletions(-)

diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index e6f3fad..940eb24 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -107,6 +107,7 @@ chmod a+x fake-editor.sh
 
 test_expect_success 'no changes are a nop' '
 	git rebase -i F &&
+	test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" &&
 	test $(git rev-parse I) = $(git rev-parse HEAD)
 '
 
@@ -115,14 +116,26 @@ test_expect_success 'test the [branch] option' '
 	git rm file6 &&
 	git commit -m "stop here" &&
 	git rebase -i F branch2 &&
+	test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" &&
+	test $(git rev-parse I) = $(git rev-parse branch2) &&
 	test $(git rev-parse I) = $(git rev-parse HEAD)
 '
 
+test_expect_success 'test --onto <branch>' '
+	git checkout -b test-onto branch2 &&
+	git rebase -i --onto branch1 F &&
+	test "$(git symbolic-ref -q HEAD)" = "refs/heads/test-onto" &&
+	test $(git rev-parse HEAD^) = $(git rev-parse branch1) &&
+	test $(git rev-parse I) = $(git rev-parse branch2)
+'
+
 test_expect_success 'rebase on top of a non-conflicting commit' '
 	git checkout branch1 &&
 	git tag original-branch1 &&
 	git rebase -i branch2 &&
 	test file6 = $(git diff --name-only original-branch1) &&
+	test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" &&
+	test $(git rev-parse I) = $(git rev-parse branch2) &&
 	test $(git rev-parse I) = $(git rev-parse HEAD~2)
 '
 
@@ -155,9 +168,12 @@ EOF
 
 test_expect_success 'stop on conflicting pick' '
 	git tag new-branch1 &&
-	! git rebase -i master &&
+	test_must_fail git rebase -i master &&
+	test "$(git rev-parse HEAD~3)" = "$(git rev-parse master)" &&
 	test_cmp expect .git/.dotest-merge/patch &&
 	test_cmp expect2 file1 &&
+	test "$(git-diff --name-status |
+		sed -n -e "/^U/s/^U[^a-z]*//p")" = file1 &&
 	test 4 = $(grep -v "^#" < .git/.dotest-merge/done | wc -l) &&
 	test 0 = $(grep -c "^[^#]" < .git/.dotest-merge/git-rebase-todo)
 '
@@ -165,6 +181,7 @@ test_expect_success 'stop on conflicting pick' '
 test_expect_success 'abort' '
 	git rebase --abort &&
 	test $(git rev-parse new-branch1) = $(git rev-parse HEAD) &&
+	test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" &&
 	! test -d .git/.dotest-merge
 '
 
@@ -331,7 +348,7 @@ test_expect_success 'interactive -t preserves tags' '
 test_expect_success '--continue tries to commit' '
 	git checkout to-be-rebased &&
 	test_tick &&
-	! git rebase -i --onto new-branch1 HEAD^ &&
+	test_must_fail git rebase -i --onto new-branch1 HEAD^ &&
 	echo resolved > file1 &&
 	git add file1 &&
 	FAKE_COMMIT_MESSAGE="chouette!" git rebase --continue &&
@@ -342,7 +359,7 @@ test_expect_success '--continue tries to commit' '
 test_expect_success 'verbose flag is heeded, even after --continue' '
 	git reset --hard HEAD@{1} &&
 	test_tick &&
-	! git rebase -v -i --onto new-branch1 HEAD^ &&
+	test_must_fail git rebase -v -i --onto new-branch1 HEAD^ &&
 	echo resolved > file1 &&
 	git add file1 &&
 	git rebase --continue > output &&
@@ -377,10 +394,14 @@ test_expect_success 'interrupted squash works as expected' '
 		git commit -m $n
 	done &&
 	one=$(git rev-parse HEAD~3) &&
-	! FAKE_LINES="1 squash 3 2" git rebase -i HEAD~3 &&
+	(
+		FAKE_LINES="1 squash 3 2" &&
+		export FAKE_LINES &&
+		test_must_fail git rebase -i HEAD~3
+	) &&
 	(echo one; echo two; echo four) > conflict &&
 	git add conflict &&
-	! git rebase --continue &&
+	test_must_fail git rebase --continue &&
 	echo resolved > conflict &&
 	git add conflict &&
 	git rebase --continue &&
@@ -395,13 +416,17 @@ test_expect_success 'interrupted squash works as expected (case 2)' '
 		git commit -m $n
 	done &&
 	one=$(git rev-parse HEAD~3) &&
-	! FAKE_LINES="3 squash 1 2" git rebase -i HEAD~3 &&
+	(
+		FAKE_LINES="3 squash 1 2" &&
+		export FAKE_LINES &&
+		test_must_fail git rebase -i HEAD~3
+	) &&
 	(echo one; echo four) > conflict &&
 	git add conflict &&
-	! git rebase --continue &&
+	test_must_fail git rebase --continue &&
 	(echo one; echo two; echo four) > conflict &&
 	git add conflict &&
-	! git rebase --continue &&
+	test_must_fail git rebase --continue &&
 	echo resolved > conflict &&
 	git add conflict &&
 	git rebase --continue &&
@@ -449,7 +474,7 @@ test_expect_success 'rebase a commit violating pre-commit' '
 	chmod a+x $PRE_COMMIT &&
 	echo "monde! " >> file1 &&
 	test_tick &&
-	! git commit -m doesnt-verify file1 &&
+	test_must_fail git commit -m doesnt-verify file1 &&
 	git commit -m doesnt-verify --no-verify file1 &&
 	test_tick &&
 	FAKE_LINES=2 git rebase -i HEAD~2
-- 
1.5.6.310.g344d

^ permalink raw reply related

* [PATCH v3 2/2] Make rebase--interactive use OPTIONS_SPEC
From: Stephan Beyer @ 2008-06-21 23:55 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Schindelin, Christian Couder, Stephan Beyer
In-Reply-To: <1214092551-8075-1-git-send-email-s-beyer@gmx.net>

Also add some checks that --continue/--abort/--skip
actions are used without --onto, -p, -t, etc.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
Hi,

I resent this patch, too, because I've changed something:
To make the OPTIONS_SPEC more consistent with other OPTIONS_SPECs,
I lowe-cased the first letters of the help string for each option.

Regards,
  Stephan


 git-rebase--interactive.sh |   71 +++++++++++++++++++++++++++++--------------
 1 files changed, 48 insertions(+), 23 deletions(-)

diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 3f926d8..f13768c 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -10,10 +10,27 @@
 # The original idea comes from Eric W. Biederman, in
 # http://article.gmane.org/gmane.comp.version-control.git/22407
 
-USAGE='(--continue | --abort | --skip | [--preserve-merges] [--first-parent]
-	[--preserve-tags] [--verbose] [--onto <branch>] <upstream> [<branch>])'
+OPTIONS_KEEPDASHDASH=
+OPTIONS_SPEC="\
+git-rebase [-i] [options] [--] <upstream> [<branch>]
+git-rebase [-i] (--continue | --abort | --skip)
+--
+ Available options are
+p,preserve-merges  try to recreate merges instead of ignoring them
+t,preserve-tags    update tags to the new commit object
+m,merge            always used (no-op)
+i,interactive      always used (no-op)
+onto=              rebase onto given branch instead of upstream
+v,verbose          display a diffstat of what changed upstream
+ When preserving merges
+f,first-parent     show only commits following the first parent of each commit
+s,strategy=        use the given merge strategy
+ Actions:
+continue           continue rebasing process
+abort              abort rebasing process and restore original branch
+skip               skip current patch and continue rebasing process
+"
 
-OPTIONS_SPEC=
 . git-sh-setup
 require_work_tree
 
@@ -25,6 +42,8 @@ SQUASH_MSG="$DOTEST"/message-squash
 PRESERVE_MERGES=
 STRATEGY=
 VERBOSE=
+ONTO=
+MARK_PREFIX='refs/rebase-marks'
 test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)"
 test -f "$DOTEST"/verbose && VERBOSE=t
 
@@ -515,10 +534,19 @@ create_extended_todo_list () {
 	done
 }
 
+is_standalone () {
+	test $# -eq 2 -a "$2" = '--' &&
+	test -z "$ONTO" &&
+	test -z "$PRESERVE_TAGS" &&
+	test -z "$PRESERVE_MERGES" &&
+	test -z "$FIRST_PARENT"
+}
+
 while test $# != 0
 do
 	case "$1" in
 	--continue)
+		is_standalone "$@" || usage
 		comment_for_reflog continue
 
 		test -d "$DOTEST" || die "No interactive rebase running"
@@ -551,6 +579,7 @@ do
 		do_rest
 		;;
 	--abort)
+		is_standalone "$@" || usage
 		comment_for_reflog abort
 
 		git rerere clear
@@ -568,6 +597,7 @@ do
 		exit
 		;;
 	--skip)
+		is_standalone "$@" || usage
 		comment_for_reflog skip
 
 		git rerere clear
@@ -575,7 +605,7 @@ do
 
 		output git reset --hard && do_rest
 		;;
-	-s|--strategy)
+	-s)
 		case "$#,$1" in
 		*,*=*)
 			STRATEGY="-s "$(expr "z$1" : 'z-[^=]*=\(.*\)') ;;
@@ -586,32 +616,36 @@ do
 			shift ;;
 		esac
 		;;
-	-m|--merge)
+	-m)
 		# we use merge anyway
 		;;
-	-C*)
-		die "Interactive rebase uses merge, so $1 does not make sense"
-		;;
-	-v|--verbose)
+	-v)
 		VERBOSE=t
 		;;
-	-p|--preserve-merges)
+	-p)
 		PRESERVE_MERGES=t
 		;;
-	-f|--first-parent)
+	-f)
 		FIRST_PARENT=t
 		PRESERVE_MERGES=t
 		;;
-	-t|--preserve-tags)
+	-t)
 		PRESERVE_TAGS=t
 		;;
-	-i|--interactive)
+	-i)
 		# yeah, we know
 		;;
+	--onto)
+		shift
+		ONTO=$(git rev-parse --verify "$1") ||
+			die "Does not point to a valid commit: $1"
+		;;
 	''|-h)
 		usage
 		;;
-	*)
+	--)
+		shift
+		test $# -eq 1 -o $# -eq 2 || usage
 		test -d "$DOTEST" &&
 			die "Interactive rebase already started"
 
@@ -620,15 +654,6 @@ do
 
 		comment_for_reflog start
 
-		ONTO=
-		case "$1" in
-		--onto)
-			ONTO=$(git rev-parse --verify "$2") ||
-				die "Does not point to a valid commit: $2"
-			shift; shift
-			;;
-		esac
-
 		require_clean_work_tree
 
 		UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
-- 
1.5.6.310.g344d

^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox