Teach merge the '[-e|--edit]' option

Implemented internally instead of as "git merge --no-commit && git commit" so that "merge --edit" is otherwise consistent (hooks, etc) with "merge". Note: the edit message does not include the status information that one gets with "commit --status" and it is cleaned up after editing like one gets with "commit --cleanup=default". A later patch could add the status information if desired. Note: previously we were not calling stripspace() after running the prepare-commit-msg hook. Now we are, stripping comments and leading/trailing whitespace lines if --edit is given, otherwise only stripping leading/trailing whitespace lines if not given --edit. Signed-off-by: Jay Soffian <jaysoffian@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jay Soffian committed Oct 8, 2011 at 14:39 UTC 66f4b98ad9a8218ad97b7b2f1604b205072dda62
3 files changed +99 -39
Documentation/merge-options.txt
+6
@@ -7,6 +7,12 @@ With --no-commit perform the merge but pretend the merge
7 failed and do not autocommit, to give the user a chance to
8 inspect and further tweak the merge result before committing.
9
10 +--edit::
11 +-e::
12 ++
13 + Invoke editor before committing successful merge to further
14 + edit the default merge message.
15 +
16 --ff::
17 --no-ff::
18 Do not generate a merge commit if the merge resolved as
builtin/merge.c
+70 -39
@@ -46,7 +46,7 @@ static const char * const builtin_merge_usage[] = {
46
47 static int show_diffstat = 1, shortlog_len, squash;
48 static int option_commit = 1, allow_fast_forward = 1;
49 -static int fast_forward_only;
49 +static int fast_forward_only, option_edit;
50 static int allow_trivial = 1, have_message;
51 static struct strbuf merge_msg;
52 static struct commit_list *remoteheads;
@@ -190,6 +190,8 @@ static struct option builtin_merge_options[] = {
190 "create a single commit instead of doing a merge"),
191 OPT_BOOLEAN(0, "commit", &option_commit,
192 "perform a commit if the merge succeeds (default)"),
193 + OPT_BOOLEAN('e', "edit", &option_edit,
194 + "edit message before committing"),
195 OPT_BOOLEAN(0, "ff", &allow_fast_forward,
196 "allow fast-forward (default)"),
197 OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
@@ -832,30 +834,54 @@ static void add_strategies(const char *string, unsigned attr)
834
835 }
836
835 -static void write_merge_msg(void)
837 +static void write_merge_msg(struct strbuf *msg)
838 {
839 int fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
840 if (fd < 0)
841 die_errno(_("Could not open '%s' for writing"),
842 git_path("MERGE_MSG"));
841 - if (write_in_full(fd, merge_msg.buf, merge_msg.len) != merge_msg.len)
843 + if (write_in_full(fd, msg->buf, msg->len) != msg->len)
844 die_errno(_("Could not write to '%s'"), git_path("MERGE_MSG"));
845 close(fd);
846 }
847
846 -static void read_merge_msg(void)
848 +static void read_merge_msg(struct strbuf *msg)
849 {
848 - strbuf_reset(&merge_msg);
849 - if (strbuf_read_file(&merge_msg, git_path("MERGE_MSG"), 0) < 0)
850 + strbuf_reset(msg);
851 + if (strbuf_read_file(msg, git_path("MERGE_MSG"), 0) < 0)
852 die_errno(_("Could not read from '%s'"), git_path("MERGE_MSG"));
853 }
854
853 -static void run_prepare_commit_msg(void)
855 +static void write_merge_state(void);
856 +static void abort_commit(const char *err_msg)
857 {
855 - write_merge_msg();
858 + if (err_msg)
859 + error("%s", err_msg);
860 + fprintf(stderr,
861 + _("Not committing merge; use 'git commit' to complete the merge.\n"));
862 + write_merge_state();
863 + exit(1);
864 +}
865 +
866 +static void prepare_to_commit(void)
867 +{
868 + struct strbuf msg = STRBUF_INIT;
869 + strbuf_addbuf(&msg, &merge_msg);
870 + strbuf_addch(&msg, '\n');
871 + write_merge_msg(&msg);
872 run_hook(get_index_file(), "prepare-commit-msg",
873 git_path("MERGE_MSG"), "merge", NULL, NULL);
858 - read_merge_msg();
874 + if (option_edit) {
875 + if (launch_editor(git_path("MERGE_MSG"), NULL, NULL))
876 + abort_commit(NULL);
877 + }
878 + read_merge_msg(&msg);
879 + stripspace(&msg, option_edit);
880 + if (!msg.len)
881 + abort_commit(_("Empty commit message."));
882 + strbuf_release(&merge_msg);
883 + strbuf_addbuf(&merge_msg, &msg);
884 + strbuf_release(&msg);
885 }
886
887 static int merge_trivial(void)
@@ -869,7 +895,7 @@ static int merge_trivial(void)
895 parent->next = xmalloc(sizeof(*parent->next));
896 parent->next->item = remoteheads->item;
897 parent->next->next = NULL;
872 - run_prepare_commit_msg();
898 + prepare_to_commit();
899 commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
900 finish(result_commit, "In-index merge");
901 drop_save();
@@ -897,9 +923,9 @@ static int finish_automerge(struct commit_list *common,
923 for (j = remoteheads; j; j = j->next)
924 pptr = &commit_list_insert(j->item, pptr)->next;
925 }
900 - free_commit_list(remoteheads);
926 strbuf_addch(&merge_msg, '\n');
902 - run_prepare_commit_msg();
927 + prepare_to_commit();
928 + free_commit_list(remoteheads);
929 commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
930 strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
931 finish(result_commit, buf.buf);
@@ -1005,6 +1031,36 @@ static int setup_with_upstream(const char ***argv)
1031 return i;
1032 }
1033
1034 +static void write_merge_state(void)
1035 +{
1036 + int fd;
1037 + struct commit_list *j;
1038 + struct strbuf buf = STRBUF_INIT;
1039 +
1040 + for (j = remoteheads; j; j = j->next)
1041 + strbuf_addf(&buf, "%s\n",
1042 + sha1_to_hex(j->item->object.sha1));
1043 + fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
1044 + if (fd < 0)
1045 + die_errno(_("Could not open '%s' for writing"),
1046 + git_path("MERGE_HEAD"));
1047 + if (write_in_full(fd, buf.buf, buf.len) != buf.len)
1048 + die_errno(_("Could not write to '%s'"), git_path("MERGE_HEAD"));
1049 + close(fd);
1050 + strbuf_addch(&merge_msg, '\n');
1051 + write_merge_msg(&merge_msg);
1052 + fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
1053 + if (fd < 0)
1054 + die_errno(_("Could not open '%s' for writing"),
1055 + git_path("MERGE_MODE"));
1056 + strbuf_reset(&buf);
1057 + if (!allow_fast_forward)
1058 + strbuf_addf(&buf, "no-ff");
1059 + if (write_in_full(fd, buf.buf, buf.len) != buf.len)
1060 + die_errno(_("Could not write to '%s'"), git_path("MERGE_MODE"));
1061 + close(fd);
1062 +}
1063 +
1064 int cmd_merge(int argc, const char **argv, const char *prefix)
1065 {
1066 unsigned char result_tree[20];
@@ -1409,33 +1465,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
1465
1466 if (squash)
1467 finish(NULL, NULL);
1412 - else {
1413 - int fd;
1414 - struct commit_list *j;
1415 -
1416 - for (j = remoteheads; j; j = j->next)
1417 - strbuf_addf(&buf, "%s\n",
1418 - sha1_to_hex(j->item->object.sha1));
1419 - fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
1420 - if (fd < 0)
1421 - die_errno(_("Could not open '%s' for writing"),
1422 - git_path("MERGE_HEAD"));
1423 - if (write_in_full(fd, buf.buf, buf.len) != buf.len)
1424 - die_errno(_("Could not write to '%s'"), git_path("MERGE_HEAD"));
1425 - close(fd);
1426 - strbuf_addch(&merge_msg, '\n');
1427 - write_merge_msg();
1428 - fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
1429 - if (fd < 0)
1430 - die_errno(_("Could not open '%s' for writing"),
1431 - git_path("MERGE_MODE"));
1432 - strbuf_reset(&buf);
1433 - if (!allow_fast_forward)
1434 - strbuf_addf(&buf, "no-ff");
1435 - if (write_in_full(fd, buf.buf, buf.len) != buf.len)
1436 - die_errno(_("Could not write to '%s'"), git_path("MERGE_MODE"));
1437 - close(fd);
1438 - }
1468 + else
1469 + write_merge_state();
1470
1471 if (merge_was_ok) {
1472 fprintf(stderr, _("Automatic merge went well; "
t/t7600-merge.sh
+23
@@ -643,4 +643,27 @@ test_expect_success 'amending no-ff merge commit' '
643
644 test_debug 'git log --graph --decorate --oneline --all'
645
646 +cat >editor <<\EOF
647 +#!/bin/sh
648 +# Add a new message string that was not in the template
649 +(
650 + echo "Merge work done on the side branch c1"
651 + echo
652 + cat <"$1"
653 +) >"$1.tmp" && mv "$1.tmp" "$1"
654 +# strip comments and blank lines from end of message
655 +sed -e '/^#/d' < "$1" | sed -e :a -e '/^\n*$/{$d;N;ba' -e '}' > expected
656 +EOF
657 +chmod 755 editor
658 +
659 +test_expect_success 'merge --no-ff --edit' '
660 + git reset --hard c0 &&
661 + EDITOR=./editor git merge --no-ff --edit c1 &&
662 + verify_parents $c0 $c1 &&
663 + git cat-file commit HEAD >raw &&
664 + grep "work done on the side branch" raw &&
665 + sed "1,/^$/d" >actual raw &&
666 + test_cmp actual expected
667 +'
668 +
669 test_done