git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] Teach merge the '[-e|--edit]' option
@ 2011-10-07 15:29 Jay Soffian
  2011-10-07 17:30 ` Junio C Hamano
  0 siblings, 1 reply; 15+ messages in thread
From: Jay Soffian @ 2011-10-07 15:29 UTC (permalink / raw)
  To: git; +Cc: Jay Soffian, Junio C Hamano, Todd A. Jacobs

Implement "git merge [-e|--edit]" as "git merge --no-commit && git commit"
as a convenience for the user.

Signed-off-by: Jay Soffian <jaysoffian@gmail.com>
---
 Documentation/merge-options.txt |    6 ++++++
 builtin/merge.c                 |   14 ++++++++++++++
 t/t7600-merge.sh                |   15 +++++++++++++++
 3 files changed, 35 insertions(+), 0 deletions(-)

diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt
index b613d4ed08..6bd0b041c3 100644
--- a/Documentation/merge-options.txt
+++ b/Documentation/merge-options.txt
@@ -7,6 +7,12 @@ With --no-commit perform the merge but pretend the merge
 failed and do not autocommit, to give the user a chance to
 inspect and further tweak the merge result before committing.
 
+--edit::
+-e::
++
+	Invoke editor before committing successful merge to further
+	edit the default merge message.
+
 --ff::
 --no-ff::
 	Do not generate a merge commit if the merge resolved as
diff --git a/builtin/merge.c b/builtin/merge.c
index ee56974371..815e151487 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -46,6 +46,7 @@ static const char * const builtin_merge_usage[] = {
 
 static int show_diffstat = 1, shortlog_len, squash;
 static int option_commit = 1, allow_fast_forward = 1;
+static int option_edit = 0;
 static int fast_forward_only;
 static int allow_trivial = 1, have_message;
 static struct strbuf merge_msg;
@@ -190,6 +191,8 @@ static struct option builtin_merge_options[] = {
 		"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('e', "edit", &option_edit,
+		"edit message before committing"),
 	OPT_BOOLEAN(0, "ff", &allow_fast_forward,
 		"allow fast-forward (default)"),
 	OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
@@ -1092,6 +1095,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		option_commit = 0;
 	}
 
+	/* if not committing, edit is nonsensical */
+	if (!option_commit)
+		option_edit = 0;
+	/* if editing, invoke 'git commit -e' after successful merge */
+	if (option_edit)
+		option_commit = 0;
+
 	if (!allow_fast_forward && fast_forward_only)
 		die(_("You cannot combine --no-ff with --ff-only."));
 
@@ -1447,6 +1457,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 	}
 
 	if (merge_was_ok) {
+		if (option_edit) {
+			const char *args[] = {"commit", "-e", NULL};
+			return run_command_v_opt(args, RUN_GIT_CMD);
+		}
 		fprintf(stderr, _("Automatic merge went well; "
 			"stopped before committing as requested\n"));
 		return 0;
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
index 87aac835a1..8c6b811718 100755
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
@@ -643,4 +643,19 @@ test_expect_success 'amending no-ff merge commit' '
 
 test_debug 'git log --graph --decorate --oneline --all'
 
+cat >editor <<\EOF
+#!/bin/sh
+# strip comments and blank lines from end of message
+sed -e '/^#/d' < "$1" | sed -e :a -e '/^\n*$/{$d;N;ba' -e '}' > expected
+EOF
+chmod 755 editor
+
+test_expect_success 'merge --no-ff --edit' '
+	git reset --hard c0 &&
+	EDITOR=./editor git merge --no-ff --edit c1 &&
+	verify_parents $c0 $c1 &&
+	git cat-file commit HEAD | sed "1,/^$/d" > actual &&
+	test_cmp actual expected
+'
+
 test_done
-- 
1.7.7.147.g00fdf

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

* Re: [PATCH] Teach merge the '[-e|--edit]' option
  2011-10-07 15:29 [PATCH] Teach merge the '[-e|--edit]' option Jay Soffian
@ 2011-10-07 17:30 ` Junio C Hamano
  2011-10-07 18:01   ` Jay Soffian
  2011-10-07 21:46   ` [PATCH v2] " Jay Soffian
  0 siblings, 2 replies; 15+ messages in thread
From: Junio C Hamano @ 2011-10-07 17:30 UTC (permalink / raw)
  To: Jay Soffian; +Cc: git

Jay Soffian <jaysoffian@gmail.com> writes:

> Implement "git merge [-e|--edit]" as "git merge --no-commit && git commit"
> as a convenience for the user.
>
> Signed-off-by: Jay Soffian <jaysoffian@gmail.com>
> ---
> ...
> @@ -1447,6 +1457,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
>  	}
>  
>  	if (merge_was_ok) {
> +		if (option_edit) {
> +			const char *args[] = {"commit", "-e", NULL};
> +			return run_command_v_opt(args, RUN_GIT_CMD);
> +		}
>  		fprintf(stderr, _("Automatic merge went well; "
>  			"stopped before committing as requested\n"));
>  		return 0;


I wanted to like this approach, thinking this approach might be safer and
with the least chance of breaking other codepaths, but this feels like an
ugly hack.

Are we still honoring all the hooks "git merge" honors?  More importantly,
isn't this make it impossible for future maintainers of this command to
enhance the command by adding other hooks after the commit is made?

If we wanted to do this properly, we should update builtin/merge.c to call
launch_editor() before it runs commit_tree(), in a way similar to how
prepare_to_commit() in builtin/commit.c does so when e.g. "commit -m foo -e"
is run. An editmsg is prepared (you already have it in MERGE_MSG), the
editor is allowed to update it, and then the original code before such a
patch will run using the updated contents of MERGE_MSG. That way, the _only_
change in behaviour when "-e" is used is to let the user update the message
used in the commit log, and everything else would run exactly the same way
as if no "-e" was given, including the invocation of hooks.

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

* Re: [PATCH] Teach merge the '[-e|--edit]' option
  2011-10-07 17:30 ` Junio C Hamano
@ 2011-10-07 18:01   ` Jay Soffian
  2011-10-07 19:01     ` Junio C Hamano
  2011-10-07 19:07     ` Jay Soffian
  2011-10-07 21:46   ` [PATCH v2] " Jay Soffian
  1 sibling, 2 replies; 15+ messages in thread
From: Jay Soffian @ 2011-10-07 18:01 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Fri, Oct 7, 2011 at 1:30 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Jay Soffian <jaysoffian@gmail.com> writes:
>
>> Implement "git merge [-e|--edit]" as "git merge --no-commit && git commit"
>> as a convenience for the user.
>>
>> Signed-off-by: Jay Soffian <jaysoffian@gmail.com>
>> ---
>> ...
>> @@ -1447,6 +1457,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
>>       }
>>
>>       if (merge_was_ok) {
>> +             if (option_edit) {
>> +                     const char *args[] = {"commit", "-e", NULL};
>> +                     return run_command_v_opt(args, RUN_GIT_CMD);
>> +             }
>>               fprintf(stderr, _("Automatic merge went well; "
>>                       "stopped before committing as requested\n"));
>>               return 0;
>
>
> I wanted to like this approach, thinking this approach might be safer and
> with the least chance of breaking other codepaths, but this feels like an
> ugly hack.
>
> Are we still honoring all the hooks "git merge" honors?  More importantly,
> isn't this make it impossible for future maintainers of this command to
> enhance the command by adding other hooks after the commit is made?

Git is already inconsistent with respect to which hooks are called
when. Shouldn't post-merge be called on a merge commit regardless of
whether you use --no-commit or not? Well, it isn't, it's only called
when merge performs the commit internally. The post-merge hook was
probably a mistake -- git calls the post-commit hook passing the
context as an argument, so probably merge should just call post-commit
"merge". But that ship has sailed.

See also 65969d43d1 (merge: honor prepare-commit-msg hook, 2011-02-14).

> If we wanted to do this properly, we should update builtin/merge.c to call
> launch_editor() before it runs commit_tree(), in a way similar to how
> prepare_to_commit() in builtin/commit.c does so when e.g. "commit -m foo -e"
> is run. An editmsg is prepared (you already have it in MERGE_MSG), the
> editor is allowed to update it, and then the original code before such a
> patch will run using the updated contents of MERGE_MSG. That way, the _only_
> change in behaviour when "-e" is used is to let the user update the message
> used in the commit log, and everything else would run exactly the same way
> as if no "-e" was given, including the invocation of hooks.

I find git very difficult to reason about (and inconsistent in its
behavior) due to piecemeal hoisting of some functionality into
porcelain commands (another example, revert.c building in the
recursive merge strategy but not any others).

I actually think a better choice would be to remove commit_tree() from
merge and always have it run commit externally. I'm not seriously
suggesting that be done, but it would make git more consistent. But
I'm not going to send in a patch which makes the situation worse.

j.

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

* Re: [PATCH] Teach merge the '[-e|--edit]' option
  2011-10-07 18:01   ` Jay Soffian
@ 2011-10-07 19:01     ` Junio C Hamano
  2011-10-07 19:22       ` Jay Soffian
  2011-10-07 19:07     ` Jay Soffian
  1 sibling, 1 reply; 15+ messages in thread
From: Junio C Hamano @ 2011-10-07 19:01 UTC (permalink / raw)
  To: Jay Soffian; +Cc: git

Jay Soffian <jaysoffian@gmail.com> writes:

> I actually think a better choice would be to remove commit_tree() from
> merge and always have it run commit externally. I'm not seriously
> suggesting that be done, but it would make git more consistent. But
> I'm not going to send in a patch which makes the situation worse.

Think about it. What I suggested does no way make the situation
worse. Your patch _does_ make it worse by changing the hook behaviour
between "merge -m 'foo'" vs "merge -m 'foo' -e".

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

* Re: [PATCH] Teach merge the '[-e|--edit]' option
  2011-10-07 18:01   ` Jay Soffian
  2011-10-07 19:01     ` Junio C Hamano
@ 2011-10-07 19:07     ` Jay Soffian
  2011-10-07 19:40       ` Junio C Hamano
  1 sibling, 1 reply; 15+ messages in thread
From: Jay Soffian @ 2011-10-07 19:07 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Fri, Oct 7, 2011 at 2:01 PM, Jay Soffian <jaysoffian@gmail.com> wrote:
> I actually think a better choice would be to remove commit_tree() from
> merge and always have it run commit externally. I'm not seriously
> suggesting that be done, but it would make git more consistent. But
> I'm not going to send in a patch which makes the situation worse.

The other inconsistencies I'm aware of between "merge --no-commit &&
commit" vs "merge" on a clean merge are:

* reflog
  - merge uses either "Merge made by the '...' strategy." OR "In-index merge"
  - commit uses "commit (merge) <subject>"

* hooks
  - merge calls
    1) "prepare-commit-msg MERGE_MSG merge"
    2) "post-merge [0|1]" where [0|1] indicates a squash or not.
  - commit calls
    1) "pre-commit"
    2) "prepare-commit-msg COMMIT_EDITMSG merge"
    3) "commit-msg COMMIT_EDITMSG"
    4) "post-commit"

* gc
  - merge calls "git gc --auto" after a successful merge unless
--squash was used
  - commit does not call "git gc --auto"

* diffstat: merge shows it, commit does not

j.

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

* Re: [PATCH] Teach merge the '[-e|--edit]' option
  2011-10-07 19:01     ` Junio C Hamano
@ 2011-10-07 19:22       ` Jay Soffian
  0 siblings, 0 replies; 15+ messages in thread
From: Jay Soffian @ 2011-10-07 19:22 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Fri, Oct 7, 2011 at 3:01 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Think about it. What I suggested does no way make the situation
> worse. Your patch _does_ make it worse by changing the hook behaviour
> between "merge -m 'foo'" vs "merge -m 'foo' -e"

I think it's arguable how -e should behave. With -e opening my editor,
now I really feel like I'm making a commit and would be surprised by
not having the various commit hooks run.

j.

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

* Re: [PATCH] Teach merge the '[-e|--edit]' option
  2011-10-07 19:07     ` Jay Soffian
@ 2011-10-07 19:40       ` Junio C Hamano
  0 siblings, 0 replies; 15+ messages in thread
From: Junio C Hamano @ 2011-10-07 19:40 UTC (permalink / raw)
  To: Jay Soffian; +Cc: git

Jay Soffian <jaysoffian@gmail.com> writes:

> The other inconsistencies I'm aware of between "merge --no-commit &&
> commit" vs "merge" on a clean merge are:

Perhaps you would want to add these to a list of todo items when gitwiki
comes back.

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

* [PATCH v2] Teach merge the '[-e|--edit]' option
  2011-10-07 17:30 ` Junio C Hamano
  2011-10-07 18:01   ` Jay Soffian
@ 2011-10-07 21:46   ` Jay Soffian
  2011-10-07 22:15     ` Junio C Hamano
  1 sibling, 1 reply; 15+ messages in thread
From: Jay Soffian @ 2011-10-07 21:46 UTC (permalink / raw)
  To: git; +Cc: Jay Soffian, Junio C Hamano, Todd A. Jacobs

Implemented internally instead of as "git merge --no-commit && git commit"
so that "merge --edit" is otherwise consistent (hooks, etc) with "merge".

Signed-off-by: Jay Soffian <jaysoffian@gmail.com>
---
On Fri, Oct 7, 2011 at 1:30 PM, Junio C Hamano <gitster@pobox.com> wrote:
> If we wanted to do this properly, we should update builtin/merge.c to call
> launch_editor() before it runs commit_tree(), in a way similar to how

I disagree that this is the proper way to do it. --edit is a new option, there's
no obviously "correct" behavior. You think 'merge --edit' should behave just
like 'merge', I think 'merge --edit' should behave like 'merge --no-commit &&
commit'.

The commit performed internally by git-merge is already wildly inconsistent with
git-commit. If anything, --edit is a perfect excuse to say "we're trying to make
git-merge perform commits more consistently with git-commit, so we've
implemented 'merge --edit' in terms of git-commit."

I didn't bother with the commit status, it's more code than I wanted
to deal with duplicating/refactoring from commit.c.

 Documentation/merge-options.txt |    6 ++
 builtin/merge.c                 |  108 +++++++++++++++++++++++++--------------
 t/t7600-merge.sh                |   15 +++++
 3 files changed, 91 insertions(+), 38 deletions(-)

diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt
index b613d4ed08..6bd0b041c3 100644
--- a/Documentation/merge-options.txt
+++ b/Documentation/merge-options.txt
@@ -7,6 +7,12 @@ With --no-commit perform the merge but pretend the merge
 failed and do not autocommit, to give the user a chance to
 inspect and further tweak the merge result before committing.
 
+--edit::
+-e::
++
+	Invoke editor before committing successful merge to further
+	edit the default merge message.
+
 --ff::
 --no-ff::
 	Do not generate a merge commit if the merge resolved as
diff --git a/builtin/merge.c b/builtin/merge.c
index ee56974371..0dee53b7e4 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -46,6 +46,7 @@ static const char * const builtin_merge_usage[] = {
 
 static int show_diffstat = 1, shortlog_len, squash;
 static int option_commit = 1, allow_fast_forward = 1;
+static int option_edit = 0;
 static int fast_forward_only;
 static int allow_trivial = 1, have_message;
 static struct strbuf merge_msg;
@@ -190,6 +191,8 @@ static struct option builtin_merge_options[] = {
 		"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('e', "edit", &option_edit,
+		"edit message before committing"),
 	OPT_BOOLEAN(0, "ff", &allow_fast_forward,
 		"allow fast-forward (default)"),
 	OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
@@ -842,30 +845,54 @@ static void add_strategies(const char *string, unsigned attr)
 
 }
 
-static void write_merge_msg(void)
+static void write_merge_msg(struct strbuf *msg)
 {
 	int fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
 	if (fd < 0)
 		die_errno(_("Could not open '%s' for writing"),
 			  git_path("MERGE_MSG"));
-	if (write_in_full(fd, merge_msg.buf, merge_msg.len) != merge_msg.len)
+	if (write_in_full(fd, msg->buf, msg->len) != msg->len)
 		die_errno(_("Could not write to '%s'"), git_path("MERGE_MSG"));
 	close(fd);
 }
 
-static void read_merge_msg(void)
+static void read_merge_msg(struct strbuf *msg)
 {
-	strbuf_reset(&merge_msg);
-	if (strbuf_read_file(&merge_msg, git_path("MERGE_MSG"), 0) < 0)
+	strbuf_reset(msg);
+	if (strbuf_read_file(msg, git_path("MERGE_MSG"), 0) < 0)
 		die_errno(_("Could not read from '%s'"), git_path("MERGE_MSG"));
 }
 
-static void run_prepare_commit_msg(void)
+static void write_merge_state();
+static void abort_commit(const char *err_msg)
 {
-	write_merge_msg();
+	if (err_msg)
+		error("%s", err_msg);
+	fprintf(stderr,
+		_("Not committing merge; use 'git commit' to complete the merge.\n"));
+	write_merge_state();
+	exit(1);
+}
+
+static void prepare_to_commit(void)
+{
+	struct strbuf msg = STRBUF_INIT;
+	strbuf_addbuf(&msg, &merge_msg);
+	strbuf_addch(&msg, '\n');
+	write_merge_msg(&msg);
 	run_hook(get_index_file(), "prepare-commit-msg",
 		 git_path("MERGE_MSG"), "merge", NULL, NULL);
-	read_merge_msg();
+	if (option_edit) {
+		if (launch_editor(git_path("MERGE_MSG"), NULL, NULL))
+			abort_commit(NULL);
+	}
+	read_merge_msg(&msg);
+	stripspace(&msg, option_edit);
+	if (!msg.len)
+		abort_commit(_("Empty commit message."));
+	strbuf_release(&merge_msg);
+	strbuf_addbuf(&merge_msg, &msg);
+	strbuf_release(&msg);
 }
 
 static int merge_trivial(void)
@@ -879,7 +906,7 @@ static int merge_trivial(void)
 	parent->next = xmalloc(sizeof(*parent->next));
 	parent->next->item = remoteheads->item;
 	parent->next->next = NULL;
-	run_prepare_commit_msg();
+	prepare_to_commit();
 	commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
 	finish(result_commit, "In-index merge");
 	drop_save();
@@ -907,9 +934,9 @@ static int finish_automerge(struct commit_list *common,
 		for (j = remoteheads; j; j = j->next)
 			pptr = &commit_list_insert(j->item, pptr)->next;
 	}
-	free_commit_list(remoteheads);
 	strbuf_addch(&merge_msg, '\n');
-	run_prepare_commit_msg();
+	prepare_to_commit();
+	free_commit_list(remoteheads);
 	commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
 	strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
 	finish(result_commit, buf.buf);
@@ -1015,6 +1042,36 @@ static int setup_with_upstream(const char ***argv)
 	return i;
 }
 
+static void write_merge_state()
+{
+	int fd;
+	struct commit_list *j;
+	struct strbuf buf = STRBUF_INIT;
+
+	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_errno(_("Could not open '%s' for writing"),
+			  git_path("MERGE_HEAD"));
+	if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+		die_errno(_("Could not write to '%s'"), git_path("MERGE_HEAD"));
+	close(fd);
+	strbuf_addch(&merge_msg, '\n');
+	write_merge_msg(&merge_msg);
+	fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
+	if (fd < 0)
+		die_errno(_("Could not open '%s' for writing"),
+			  git_path("MERGE_MODE"));
+	strbuf_reset(&buf);
+	if (!allow_fast_forward)
+		strbuf_addf(&buf, "no-ff");
+	if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+		die_errno(_("Could not write to '%s'"), git_path("MERGE_MODE"));
+	close(fd);
+}
+
 int cmd_merge(int argc, const char **argv, const char *prefix)
 {
 	unsigned char result_tree[20];
@@ -1418,33 +1475,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 
 	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_errno(_("Could not open '%s' for writing"),
-				  git_path("MERGE_HEAD"));
-		if (write_in_full(fd, buf.buf, buf.len) != buf.len)
-			die_errno(_("Could not write to '%s'"), git_path("MERGE_HEAD"));
-		close(fd);
-		strbuf_addch(&merge_msg, '\n');
-		write_merge_msg();
-		fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
-		if (fd < 0)
-			die_errno(_("Could not open '%s' for writing"),
-				  git_path("MERGE_MODE"));
-		strbuf_reset(&buf);
-		if (!allow_fast_forward)
-			strbuf_addf(&buf, "no-ff");
-		if (write_in_full(fd, buf.buf, buf.len) != buf.len)
-			die_errno(_("Could not write to '%s'"), git_path("MERGE_MODE"));
-		close(fd);
-	}
+	else
+		write_merge_state();
 
 	if (merge_was_ok) {
 		fprintf(stderr, _("Automatic merge went well; "
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
index 87aac835a1..8c6b811718 100755
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
@@ -643,4 +643,19 @@ test_expect_success 'amending no-ff merge commit' '
 
 test_debug 'git log --graph --decorate --oneline --all'
 
+cat >editor <<\EOF
+#!/bin/sh
+# strip comments and blank lines from end of message
+sed -e '/^#/d' < "$1" | sed -e :a -e '/^\n*$/{$d;N;ba' -e '}' > expected
+EOF
+chmod 755 editor
+
+test_expect_success 'merge --no-ff --edit' '
+	git reset --hard c0 &&
+	EDITOR=./editor git merge --no-ff --edit c1 &&
+	verify_parents $c0 $c1 &&
+	git cat-file commit HEAD | sed "1,/^$/d" > actual &&
+	test_cmp actual expected
+'
+
 test_done
-- 
1.7.7.147.g3a3ce

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

* Re: [PATCH v2] Teach merge the '[-e|--edit]' option
  2011-10-07 21:46   ` [PATCH v2] " Jay Soffian
@ 2011-10-07 22:15     ` Junio C Hamano
  2011-10-08 18:11       ` Jay Soffian
  0 siblings, 1 reply; 15+ messages in thread
From: Junio C Hamano @ 2011-10-07 22:15 UTC (permalink / raw)
  To: Jay Soffian; +Cc: git, Todd A. Jacobs

Jay Soffian <jaysoffian@gmail.com> writes:

> Implemented internally instead of as "git merge --no-commit && git commit"
> so that "merge --edit" is otherwise consistent (hooks, etc) with "merge".
>
> Signed-off-by: Jay Soffian <jaysoffian@gmail.com>
> ---
> On Fri, Oct 7, 2011 at 1:30 PM, Junio C Hamano <gitster@pobox.com> wrote:
>> If we wanted to do this properly, we should update builtin/merge.c to call
>> launch_editor() before it runs commit_tree(), in a way similar to how
>
> I disagree that this is the proper way to do it. --edit is a new option, there's
> no obviously "correct" behavior. You think 'merge --edit' should behave just
> like 'merge', I think 'merge --edit' should behave like 'merge --no-commit &&
> commit'.
>
> The commit performed internally by git-merge is already wildly inconsistent with
> git-commit.

Think and look forward.

You are complaining that the "commit" does not know enough to behave as if
it were a part of the merge command workflow if you split a usual merge
into two steps "merge --no-commit; commit".

How would you make it better? Would you strip all the things usual "merge"
does, so that it would work identically to the split one, losing some hook
support and such, or would you rather make the split case work similar to
the usual merge?

I'd say between "merge" and "merge --no-commit ; commit", the latter is
what needs to be fixed. Viewed that way, why would you even consider
making the new option behave similar to the _wrong_ one?

> I didn't bother with the commit status, it's more code than I wanted
> to deal with duplicating/refactoring from commit.c.

What do you mean by "commit status"? If you mean this patch is incomplete,
it would have been nicer if it were labeled with [PATCH/RFC].

> diff --git a/builtin/merge.c b/builtin/merge.c
> index ee56974371..0dee53b7e4 100644
> --- a/builtin/merge.c
> +++ b/builtin/merge.c
> @@ -46,6 +46,7 @@ static const char * const builtin_merge_usage[] = {
>  
>  static int show_diffstat = 1, shortlog_len, squash;
>  static int option_commit = 1, allow_fast_forward = 1;
> +static int option_edit = 0;

No need to move this into .data segment when it can be in .bss
segment. Drop the unnecessary " = 0" before ";".

> @@ -842,30 +845,54 @@ static void add_strategies(const char *string, unsigned attr)
>  
>  }
>  
> -static void write_merge_msg(void)
> +static void write_merge_msg(struct strbuf *msg)
>  {
>  	int fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
>  	if (fd < 0)
>  		die_errno(_("Could not open '%s' for writing"),
>  			  git_path("MERGE_MSG"));
> -	if (write_in_full(fd, merge_msg.buf, merge_msg.len) != merge_msg.len)
> +	if (write_in_full(fd, msg->buf, msg->len) != msg->len)
>  		die_errno(_("Could not write to '%s'"), git_path("MERGE_MSG"));
>  	close(fd);
>  }
>  
> -static void read_merge_msg(void)
> +static void read_merge_msg(struct strbuf *msg)
>  {
> -	strbuf_reset(&merge_msg);
> -	if (strbuf_read_file(&merge_msg, git_path("MERGE_MSG"), 0) < 0)
> +	strbuf_reset(msg);
> +	if (strbuf_read_file(msg, git_path("MERGE_MSG"), 0) < 0)
>  		die_errno(_("Could not read from '%s'"), git_path("MERGE_MSG"));
>  }
>  
> -static void run_prepare_commit_msg(void)
> +static void write_merge_state();

s/()/(void)/;

Thanks.

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

* Re: [PATCH v2] Teach merge the '[-e|--edit]' option
  2011-10-07 22:15     ` Junio C Hamano
@ 2011-10-08 18:11       ` Jay Soffian
  2011-10-09 23:23         ` Junio C Hamano
  0 siblings, 1 reply; 15+ messages in thread
From: Jay Soffian @ 2011-10-08 18:11 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Todd A. Jacobs

On Fri, Oct 7, 2011 at 6:15 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Think and look forward.
>
> You are complaining that the "commit" does not know enough to behave as if
> it were a part of the merge command workflow if you split a usual merge
> into two steps "merge --no-commit; commit".

No Junio, you have my argument completely reversed.

I am complaining that git-merge implements commits internally, which
gives it unique behavior from git-{commit/cherry-pick/revert} (the
latter two of which just run external git-commit). I'm saying merge is
fundamentally broken to do it this way. And maybe that's something
that should be fixed in 2.0 -- that git-merge should just call out to
git-commit, just like cherry-pick/revert do.

In case that's not clear: I think that git-merge should eventually
behave identically to "merge --no-commit; commit".

> How would you make it better? Would you strip all the things usual "merge"
> does, so that it would work identically to the split one,

Yes.

> losing some hook support and such.

Yes, I would lose the post-merge hook and such.

>, or would you rather make the split case work similar to the usual merge?

No, I would not do that.

BTW, the same arguments apply to git-am, which uses git-commit-tree,
and so implements its own set of hooks.

> I'd say between "merge" and "merge --no-commit ; commit", the latter is
> what needs to be fixed. Viewed that way, why would you even consider
> making the new option behave similar to the _wrong_ one?

Strongly disagree. I think it would make much more sense for all
commits to flow through git-commit, which would ensure consistent
behavior. I think we've got a mishmash of hooks which evolved over
time.

>> I didn't bother with the commit status, it's more code than I wanted
>> to deal with duplicating/refactoring from commit.c.
>
> What do you mean by "commit status"? If you mean this patch is incomplete,
> it would have been nicer if it were labeled with [PATCH/RFC].

No, I meant that git-commit includes status information about the
commit itself as comments in the commit message (git config
commit.status), and I didn't implement that. I don't think that makes
this patch incomplete however, that could be added by a later patch.

I'll send another iteration with your comments below addressed.

j.

>> diff --git a/builtin/merge.c b/builtin/merge.c
>> index ee56974371..0dee53b7e4 100644
>> --- a/builtin/merge.c
>> +++ b/builtin/merge.c
>> @@ -46,6 +46,7 @@ static const char * const builtin_merge_usage[] = {
>>
>>  static int show_diffstat = 1, shortlog_len, squash;
>>  static int option_commit = 1, allow_fast_forward = 1;
>> +static int option_edit = 0;
>
> No need to move this into .data segment when it can be in .bss
> segment. Drop the unnecessary " = 0" before ";".
>
>> @@ -842,30 +845,54 @@ static void add_strategies(const char *string, unsigned attr)
>>
>>  }
>>
>> -static void write_merge_msg(void)
>> +static void write_merge_msg(struct strbuf *msg)
>>  {
>>       int fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
>>       if (fd < 0)
>>               die_errno(_("Could not open '%s' for writing"),
>>                         git_path("MERGE_MSG"));
>> -     if (write_in_full(fd, merge_msg.buf, merge_msg.len) != merge_msg.len)
>> +     if (write_in_full(fd, msg->buf, msg->len) != msg->len)
>>               die_errno(_("Could not write to '%s'"), git_path("MERGE_MSG"));
>>       close(fd);
>>  }
>>
>> -static void read_merge_msg(void)
>> +static void read_merge_msg(struct strbuf *msg)
>>  {
>> -     strbuf_reset(&merge_msg);
>> -     if (strbuf_read_file(&merge_msg, git_path("MERGE_MSG"), 0) < 0)
>> +     strbuf_reset(msg);
>> +     if (strbuf_read_file(msg, git_path("MERGE_MSG"), 0) < 0)
>>               die_errno(_("Could not read from '%s'"), git_path("MERGE_MSG"));
>>  }
>>
>> -static void run_prepare_commit_msg(void)
>> +static void write_merge_state();
>
> s/()/(void)/;
>
> Thanks.
>
>

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

* Re: [PATCH v2] Teach merge the '[-e|--edit]' option
  2011-10-08 18:11       ` Jay Soffian
@ 2011-10-09 23:23         ` Junio C Hamano
  2011-10-10  5:29           ` Junio C Hamano
  0 siblings, 1 reply; 15+ messages in thread
From: Junio C Hamano @ 2011-10-09 23:23 UTC (permalink / raw)
  To: Jay Soffian; +Cc: git, Ramkumar Ramachandra

[Adding Ram to the Cc: list as this topic has much to do with resuming a
sequencer-driven workflow, and dropping Todd who does not seem to have
much to say on this topic]

Jay Soffian <jaysoffian@gmail.com> writes:

> I am complaining that git-merge implements commits internally, which
> gives it unique behavior from git-{commit/cherry-pick/revert} (the
> latter two of which just run external git-commit). I'm saying merge is
> fundamentally broken to do it this way. And maybe that's something
> that should be fixed in 2.0 -- that git-merge should just call out to
> git-commit, just like cherry-pick/revert do.

The purpose of the plumbing commands, e.g. commit-tree, is to implement a
unit of logical step at the mechanical level well without enforcing policy
decisions that may not apply to all situations.

On the other hand, the purpose of the Porcelain commands is to support a
concrete workflow element. "git commit" should support what users want to
happen when advancing the history by one commit, "git pull" should support
what users want to happen when integrating work done in another repository
to the history you are currently growing, etc. It is where we should and
do allow users to implement their own policy with hooks and configuration
variables when they want to, and it is even fine if we implemented sane
default policies with ways to override them (e.g. "commit --allow-empty",
"merge --no-ff").

Some Porcelain commands cannot complete their workflow element by
themselves in certain situations without getting help from users, and they
give control back to the user when they need such help. "git rebase", "git
am", "git merge", etc. can and do stop and ask the user to help resolving
conflicts.

The unfortunate historical accident that we may want to correct is that
some of these "we stopped in the middle and asked the user to help before
continuing" situation were presented as if "we stopped and aborted in the
middle, leaving the user to fix up the mess", which is a completely wrong
mental model. "Upon conflicts, 'git merge' stops in the middle, and you
complete it with 'git commit'" is a prime example of this. We even wrongly
label such a situation as "failed merge". It is not failed---it merely is
not auto-completed and waiting to be completed with user's help.

To understand why it is a wrong mental model, you need to imagine a world
where the logic to resolve conflicts in "git merge" is improved so that it
needs less help from the users. rerere.autoupdate is half-way there---the
user allows the merge machinery to take advantage of conflict resolutions
that the user has performed previously. Even though we currently do not
let "git merge" proceed to commit the result, it is entirely plausible to
go one step further and treat the resulting tree from applying the rerere
information as the result of the automerge. When that happens, it is very
natural for the user to expect that the rest of what "git merge" does for
a clean automerge to be carried out. After all, from the end user's point
of view, it _is_ a clean auto-merge. The only difference is how the user
helped the automerge machinery.

The root cause of the inconsistencies you are bringing up (which I agree
are annoying and I further agree that it is a worthy thing to address) is
that even though we tell the users "after helping the 'git merge', you
conclude it with 'git commit'", the concluding 'git commit' does _not_
perform what the user configured 'git merge' to do before a merge is
concluded, unlike a cleanly resolved 'git merge'.

This is merely an unfortunate historical accident. Because "git merge" did
not have any user configurable policy decisions (read: hooks) when this
"conclude with commit" was coded, "conclude with commit" was sufficient to
emulate the case where the merge did not need any help from the user.

But it no longer is true with modern Git.

With more recent changes, e.g. the sequencer work and "git cherry-pick"
that takes multiple commits, "conclude with commit" is becoming less and
less correct thing to say. The workflow elements these commands implement
do have "create a commit" as one essential part, but that is not the only
thing they do. If anything, I think the right way forward is to update the
UI with this rule for consistency:

  Some tools can stop in the middle when they cannot automatically compute
  the outcome, and give control back to the user asking for help. After
  helping these tool, the way to resume what was being done is to invoke
  the tool with the "--continue" option. All user level policy decisions
  implemented by hooks and configurations the tool normally obey when it
  does not need such help from the end user are obeyed when continuing.

I wouldn't mind if that is "invoke 'git continue' command", even though I
suspect that may make the implementation slightly more complex (I haven't
thought things through). "git commit" as a way to conclude a merge that
was stopped in the middle due to a conflict should be deprecated in the
longer term, like say in Git 2.0 someday.


[Footnote]

*1* By the way, "git merge --no-commit" is an oddball. It primarily is
used when the user does _not_ want the resulting commit but wants to
further modify the tree state (e.g. cherry picking a part of what was done
in the side branch). At the philosophical level, the user should be using
merge machinery at the "plumbing" level (e.g. merge-recursive backend),
but the interface to invoke the plumbing level merge machinery is so
arcane (they are after all designed for scripts not for humans) that
nobody does so in practice. And for that purpose, I think it is Ok for the
user to do anything after "git merge --no-commit" finishes (either leaving
conflicts or leaving a cleanly merged state), including "git commit".
Because that "git commit" is very different from the "conclude conflicted
merge with commit" which is a poor substitute for "git merge --continue"
in modern Git, I think it is perfectly fine and even preferable if it does
not obey any "git merge" semantics (i.e. user defined policy that pertains
to "merge" operations).

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

* Re: [PATCH v2] Teach merge the '[-e|--edit]' option
  2011-10-09 23:23         ` Junio C Hamano
@ 2011-10-10  5:29           ` Junio C Hamano
  2011-10-10  7:05             ` Jakub Narebski
  0 siblings, 1 reply; 15+ messages in thread
From: Junio C Hamano @ 2011-10-10  5:29 UTC (permalink / raw)
  To: git; +Cc: Jay Soffian, Ramkumar Ramachandra

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

> To understand why it is a wrong mental model, you need to imagine a world
> where the logic to resolve conflicts in "git merge" is improved so that it
> needs less help from the users. rerere.autoupdate is half-way there---the
> user allows the merge machinery to take advantage of conflict resolutions
> that the user has performed previously. Even though we currently do not
> let "git merge" proceed to commit the result, it is entirely plausible to
> go one step further and treat the resulting tree from applying the rerere
> information as the result of the automerge. When that happens, it is very
> natural for the user to expect that the rest of what "git merge" does for
> a clean automerge to be carried out. After all, from the end user's point
> of view, it _is_ a clean auto-merge. The only difference is how the user
> helped the automerge machinery.

Addendum.

I am not suggesting that we should change rerere.autoupdate to go all the
way and record a merge commit by default automatically when rerere applies
cleanly.

I personally think that it is a sensible default to set rerere.autoupdate
to false (or not to set the variable at all) to ensure that a merge that
conflicts is always inspected by the end user, given that rerere is merely
a heuristic (even though it is a damn good one) and produces a surprising
result.

But that is a policy preference; some people want to trust rerere more
than I do and that is a valid choice for them to make. To support such a
policy preference, I am perfectly fine with introducing a third value to
rerere.autoupdate in addition to yes/no to allow commands (e.g. "merge",
"am", etc.) to continue when rerere resolved conflicts cleanly in a
situation where they would have stopped and asked user to help resolving.

By the way, on the other side of this same coin lies another use case
(different from the one in the footnote in the previous message) for
"merge --no-commit". When you know that a particular merge _will_ need
semantic adjustments, even if it were to textually merge cleanly, you
would want the command to ask you for help to come up with the final tree,
instead of trusting the clean automerge result. This often happens when
the topic branch you are about to merge has changed the semantics of an
existing function (e.g. adding a new parameter) while the branch you are
on has added new callsite to the function (or the other way around). In
such a merge, you would need to adjust the new callsite that does not know
about the additional parameter to the new function signature.  For exactly
the same reason, it is not a kosher advice to give to users of modern Git
to "interfere with the merge with 'merge --no-commit', and then conclude
with 'commit'", as 'commit' has less information than 'merge' itself what
'merge' wants to do in addition to recording the result as a 'commit'.

Either the 'commit' command needs to detect that it is conclusing the
merge and trigger the merge hooks the same way as 'merge' itself does,
(which is a bad design, as 'commit' will need to know about the clean-up
operations of all the other commands that may ask users to help and let
'commit' conclude it), or the end user instruction needs to be updated so
that 'merge --continue' is used in such a situation to give 'merge' a
chance to finish up. Again we could have "git continue" wrapper that knows
how to tell what operation was in progress and invokes "merge --continue"
when it detects that it was a 'merge' that was in progress, but that is a
mere fluff.

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

* Re: [PATCH v2] Teach merge the '[-e|--edit]' option
  2011-10-10  5:29           ` Junio C Hamano
@ 2011-10-10  7:05             ` Jakub Narebski
  2011-10-10  7:50               ` Matthieu Moy
  2011-10-10 15:23               ` Junio C Hamano
  0 siblings, 2 replies; 15+ messages in thread
From: Jakub Narebski @ 2011-10-10  7:05 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Jay Soffian, Ramkumar Ramachandra

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

> By the way, on the other side of this same coin lies another use case
> (different from the one in the footnote in the previous message) for
> "merge --no-commit". When you know that a particular merge _will_ need
> semantic adjustments, even if it were to textually merge cleanly, you
> would want the command to ask you for help to come up with the final tree,
> instead of trusting the clean automerge result. This often happens when
> the topic branch you are about to merge has changed the semantics of an
> existing function (e.g. adding a new parameter) while the branch you are
> on has added new callsite to the function (or the other way around). In
> such a merge, you would need to adjust the new callsite that does not know
> about the additional parameter to the new function signature.  For exactly
> the same reason, it is not a kosher advice to give to users of modern Git
> to "interfere with the merge with 'merge --no-commit', and then conclude
> with 'commit'", as 'commit' has less information than 'merge' itself what
> 'merge' wants to do in addition to recording the result as a 'commit'.

Yet another issue is if we should blindly trust automatic merge resolution.
It is considered a good practice by some to always check (e.g. by compiling
and possibly also running tests) the result of merge, whether it required
merge conflict resolution or not.

IIRC Linus lately said that making "git merge" automatically commit
was one of bad design decisions of git, for the above reason...

-- 
Jakub Narębski

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

* Re: [PATCH v2] Teach merge the '[-e|--edit]' option
  2011-10-10  7:05             ` Jakub Narebski
@ 2011-10-10  7:50               ` Matthieu Moy
  2011-10-10 15:23               ` Junio C Hamano
  1 sibling, 0 replies; 15+ messages in thread
From: Matthieu Moy @ 2011-10-10  7:50 UTC (permalink / raw)
  To: Jakub Narebski; +Cc: Junio C Hamano, git, Jay Soffian, Ramkumar Ramachandra

Jakub Narebski <jnareb@gmail.com> writes:

> Yet another issue is if we should blindly trust automatic merge resolution.
> It is considered a good practice by some to always check (e.g. by compiling
> and possibly also running tests) the result of merge, whether it required
> merge conflict resolution or not.

I agree that trusting merge blindly is bad, but still, if there are no
merge conflicts, and if the merge is broken, I'd prefer commiting a
fixup patch right after the merge than fixing it before committing.
Because if the merge needs a fix, it usually means something tricky that
deserves its own patch and commit message. At worse, one can still reset
--merge HEAD^.

One other issue with not committing automatically is for beginners. I
see that all the time when the merge has conflicts. newbies fix the
conflicts, and when they're done: "fine, conflicts solved, let's
continue hacking" without committing. The resulting history is totally
messy because it mixes merges and actual edits. For these users, not
committing automatically in the absence of conflict would make the
situation even worse.

-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/

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

* Re: [PATCH v2] Teach merge the '[-e|--edit]' option
  2011-10-10  7:05             ` Jakub Narebski
  2011-10-10  7:50               ` Matthieu Moy
@ 2011-10-10 15:23               ` Junio C Hamano
  1 sibling, 0 replies; 15+ messages in thread
From: Junio C Hamano @ 2011-10-10 15:23 UTC (permalink / raw)
  To: Jakub Narebski; +Cc: git, Jay Soffian, Ramkumar Ramachandra

Jakub Narebski <jnareb@gmail.com> writes:

> Yet another issue is if we should blindly trust automatic merge resolution.
> It is considered a good practice by some to always check (e.g. by compiling
> and possibly also running tests) the result of merge, whether it required
> merge conflict resolution or not.
>
> IIRC Linus lately said that making "git merge" automatically commit
> was one of bad design decisions of git, for the above reason...

I think your recalling this discussion

    http://thread.gmane.org/gmane.linux.kernel/1191100/focus=181362

While I agree with what Linus said in the message, I think you are not
remembering the discussion correctly. It was about bad commit _message_,
and an improvement is not to let users tweak a cleanly automerged result,
but is to allow users or force them to always write their own message,
perhaps with "merge --[no-]edit", which is exactly the point of Jay's
patch in this thread.

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

end of thread, other threads:[~2011-10-10 15:23 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-10-07 15:29 [PATCH] Teach merge the '[-e|--edit]' option Jay Soffian
2011-10-07 17:30 ` Junio C Hamano
2011-10-07 18:01   ` Jay Soffian
2011-10-07 19:01     ` Junio C Hamano
2011-10-07 19:22       ` Jay Soffian
2011-10-07 19:07     ` Jay Soffian
2011-10-07 19:40       ` Junio C Hamano
2011-10-07 21:46   ` [PATCH v2] " Jay Soffian
2011-10-07 22:15     ` Junio C Hamano
2011-10-08 18:11       ` Jay Soffian
2011-10-09 23:23         ` Junio C Hamano
2011-10-10  5:29           ` Junio C Hamano
2011-10-10  7:05             ` Jakub Narebski
2011-10-10  7:50               ` Matthieu Moy
2011-10-10 15:23               ` Junio C Hamano

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