merge: cleanup messages like commit

This change allows git-merge messages to be cleaned up with the commit.cleanup configuration or --cleanup option, just like how git-commit does it. We also give git-pull the option of --cleanup so that it can also take advantage of this change. Finally, add testing to ensure that messages are properly cleaned up. Note that some newlines that were added to the commit message were removed so that if a file were read via -F, it would be copied faithfully. Helped-by: Eric Sunshine <sunshine@sunshineco.com> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> Signed-off-by: Denton Liu <liu.denton@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Denton Liu committed Apr 17, 2019 at 11:23 UTC d540b70c85ccd08507348785fd210ef8317a864d
7 files changed +128 -13
Documentation/merge-options.txt
+4
@@ -32,6 +32,10 @@ they run `git merge`. To make it easier to adjust such scripts to the
32 updated behaviour, the environment variable `GIT_MERGE_AUTOEDIT` can be
33 set to `no` at the beginning of them.
34
35 +--cleanup=<mode>::
36 + This option determines how the merge message will be cleaned up
37 + before commiting. See linkgit:git-commit[1] for more details.
38 +
39 --ff::
40 When the merge resolves as a fast-forward, only update the branch
41 pointer, without creating a merge commit. This is the default
builtin/merge.c
+31 -10
@@ -38,6 +38,7 @@
38 #include "tag.h"
39 #include "alias.h"
40 #include "commit-reach.h"
41 +#include "wt-status.h"
42
43 #define DEFAULT_TWOHEAD (1<<0)
44 #define DEFAULT_OCTOPUS (1<<1)
@@ -98,6 +99,9 @@ enum ff_type {
99
100 static enum ff_type fast_forward = FF_ALLOW;
101
102 +static const char *cleanup_arg;
103 +static enum commit_msg_cleanup_mode cleanup_mode;
104 +
105 static int option_parse_message(const struct option *opt,
106 const char *arg, int unset)
107 {
@@ -249,6 +253,7 @@ static struct option builtin_merge_options[] = {
253 N_("perform a commit if the merge succeeds (default)")),
254 OPT_BOOL('e', "edit", &option_edit,
255 N_("edit message before committing")),
256 + OPT_CLEANUP(&cleanup_arg),
257 OPT_SET_INT(0, "ff", &fast_forward, N_("allow fast-forward (default)"), FF_ALLOW),
258 OPT_SET_INT_F(0, "ff-only", &fast_forward,
259 N_("abort if fast-forward is not possible"),
@@ -612,6 +617,8 @@ static int git_merge_config(const char *k, const char *v, void *cb)
617 return git_config_string(&pull_twohead, k, v);
618 else if (!strcmp(k, "pull.octopus"))
619 return git_config_string(&pull_octopus, k, v);
620 + else if (!strcmp(k, "commit.cleanup"))
621 + return git_config_string(&cleanup_arg, k, v);
622 else if (!strcmp(k, "merge.renormalize"))
623 option_renormalize = git_config_bool(k, v);
624 else if (!strcmp(k, "merge.ff")) {
@@ -800,8 +807,13 @@ static void abort_commit(struct commit_list *remoteheads, const char *err_msg)
807 static const char merge_editor_comment[] =
808 N_("Please enter a commit message to explain why this merge is necessary,\n"
809 "especially if it merges an updated upstream into a topic branch.\n"
803 - "\n"
804 - "Lines starting with '%c' will be ignored, and an empty message aborts\n"
810 + "\n");
811 +
812 +static const char scissors_editor_comment[] =
813 +N_("An empty message aborts the commit.\n");
814 +
815 +static const char no_scissors_editor_comment[] =
816 +N_("Lines starting with '%c' will be ignored, and an empty message aborts\n"
817 "the commit.\n");
818
819 static void write_merge_heads(struct commit_list *);
@@ -809,11 +821,19 @@ static void prepare_to_commit(struct commit_list *remoteheads)
821 {
822 struct strbuf msg = STRBUF_INIT;
823 strbuf_addbuf(&msg, &merge_msg);
812 - strbuf_addch(&msg, '\n');
824 if (squash)
825 BUG("the control must not reach here under --squash");
815 - if (0 < option_edit)
816 - strbuf_commented_addf(&msg, _(merge_editor_comment), comment_line_char);
826 + if (0 < option_edit) {
827 + strbuf_addch(&msg, '\n');
828 + if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) {
829 + wt_status_append_cut_line(&msg);
830 + strbuf_commented_addf(&msg, "\n");
831 + }
832 + strbuf_commented_addf(&msg, _(merge_editor_comment));
833 + strbuf_commented_addf(&msg, _(cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS ?
834 + scissors_editor_comment :
835 + no_scissors_editor_comment), comment_line_char);
836 + }
837 if (signoff)
838 append_signoff(&msg, ignore_non_trailer(msg.buf, msg.len), 0);
839 write_merge_heads(remoteheads);
@@ -832,7 +852,7 @@ static void prepare_to_commit(struct commit_list *remoteheads)
852 abort_commit(remoteheads, NULL);
853
854 read_merge_msg(&msg);
835 - strbuf_stripspace(&msg, 0 < option_edit);
855 + cleanup_message(&msg, cleanup_mode, 0);
856 if (!msg.len)
857 abort_commit(remoteheads, _("Empty commit message."));
858 strbuf_release(&merge_msg);
@@ -880,7 +900,6 @@ static int finish_automerge(struct commit *head,
900 parents = remoteheads;
901 if (!head_subsumed || fast_forward == FF_NO)
902 commit_list_insert(head, &parents);
883 - strbuf_addch(&merge_msg, '\n');
903 prepare_to_commit(remoteheads);
904 if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents,
905 &result_commit, NULL, sign_commit))
@@ -1301,6 +1320,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
1320 }
1321 resolve_undo_clear();
1322
1323 + if (option_edit < 0)
1324 + option_edit = default_edit_option();
1325 +
1326 + cleanup_mode = get_cleanup_mode(cleanup_arg, 0 < option_edit);
1327 +
1328 if (verbosity < 0)
1329 show_diffstat = 0;
1330
@@ -1386,9 +1410,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
1410 fast_forward = FF_NO;
1411 }
1412
1389 - if (option_edit < 0)
1390 - option_edit = default_edit_option();
1391 -
1413 if (!use_strategies) {
1414 if (!remoteheads)
1415 ; /* already up-to-date */
builtin/pull.c
+12
@@ -24,6 +24,7 @@
24 #include "lockfile.h"
25 #include "wt-status.h"
26 #include "commit-reach.h"
27 +#include "sequencer.h"
28
29 enum rebase_type {
30 REBASE_INVALID = -1,
@@ -101,6 +102,7 @@ static char *opt_signoff;
102 static char *opt_squash;
103 static char *opt_commit;
104 static char *opt_edit;
105 +static char *cleanup_arg;
106 static char *opt_ff;
107 static char *opt_verify_signatures;
108 static int opt_autostash = -1;
@@ -168,6 +170,7 @@ static struct option pull_options[] = {
170 OPT_PASSTHRU(0, "edit", &opt_edit, NULL,
171 N_("edit message before committing"),
172 PARSE_OPT_NOARG),
173 + OPT_CLEANUP(&cleanup_arg),
174 OPT_PASSTHRU(0, "ff", &opt_ff, NULL,
175 N_("allow fast-forward"),
176 PARSE_OPT_NOARG),
@@ -644,6 +647,8 @@ static int run_merge(void)
647 argv_array_push(&args, opt_commit);
648 if (opt_edit)
649 argv_array_push(&args, opt_edit);
650 + if (cleanup_arg)
651 + argv_array_pushf(&args, "--cleanup=%s", cleanup_arg);
652 if (opt_ff)
653 argv_array_push(&args, opt_ff);
654 if (opt_verify_signatures)
@@ -875,6 +880,13 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
880
881 argc = parse_options(argc, argv, prefix, pull_options, pull_usage, 0);
882
883 + if (cleanup_arg)
884 + /*
885 + * this only checks the validity of cleanup_arg; we don't need
886 + * a valid value for use_editor
887 + */
888 + get_cleanup_mode(cleanup_arg, 0);
889 +
890 parse_repo_refspecs(argc, argv, &repo, &refspecs);
891
892 if (!opt_ff)
t/t5521-pull-options.sh
+8
@@ -77,6 +77,14 @@ test_expect_success 'git pull -q -v' '
77 test_must_be_empty out &&
78 test -s err)
79 '
80 +test_expect_success 'git pull --cleanup errors early on invalid argument' '
81 + mkdir clonedcleanup &&
82 + (cd clonedcleanup && git init &&
83 + test_must_fail git pull --cleanup invalid "../parent" >out 2>err &&
84 + test_must_be_empty out &&
85 + test -s err)
86 +'
87 +
88
89 test_expect_success 'git pull --force' '
90 mkdir clonedoldstyle &&
t/t7604-merge-custom-message.sh
+63
@@ -49,4 +49,67 @@ test_expect_success 'merge --log appends to custom message' '
49 test_cmp exp.log actual
50 '
51
52 +mesg_with_comment_and_newlines='
53 +# text
54 +
55 +'
56 +
57 +test_expect_success 'prepare file with comment line and trailing newlines' '
58 + printf "%s" "$mesg_with_comment_and_newlines" >expect
59 +'
60 +
61 +test_expect_success 'cleanup commit messages (verbatim option)' '
62 + git reset --hard c1 &&
63 + git merge --cleanup=verbatim -F expect c2 &&
64 + git cat-file commit HEAD >raw &&
65 + sed -e "1,/^$/d" raw >actual &&
66 + test_cmp expect actual
67 +'
68 +
69 +test_expect_success 'cleanup commit messages (whitespace option)' '
70 + git reset --hard c1 &&
71 + test_write_lines "" "# text" "" >text &&
72 + echo "# text" >expect &&
73 + git merge --cleanup=whitespace -F text c2 &&
74 + git cat-file commit HEAD >raw &&
75 + sed -e "1,/^$/d" raw >actual &&
76 + test_cmp expect actual
77 +'
78 +
79 +test_expect_success 'cleanup merge messages (scissors option)' '
80 + git reset --hard c1 &&
81 + cat >text <<-\EOF &&
82 +
83 + # to be kept
84 +
85 + # ------------------------ >8 ------------------------
86 + # to be kept, too
87 + # ------------------------ >8 ------------------------
88 + to be removed
89 + # ------------------------ >8 ------------------------
90 + to be removed, too
91 + EOF
92 +
93 + cat >expect <<-\EOF &&
94 + # to be kept
95 +
96 + # ------------------------ >8 ------------------------
97 + # to be kept, too
98 + EOF
99 + git merge --cleanup=scissors -e -F text c2 &&
100 + git cat-file commit HEAD >raw &&
101 + sed -e "1,/^$/d" raw >actual &&
102 + test_cmp expect actual
103 +'
104 +
105 +test_expect_success 'cleanup commit messages (strip option)' '
106 + git reset --hard c1 &&
107 + test_write_lines "" "# text" "sample" "" >text &&
108 + echo sample >expect &&
109 + git merge --cleanup=strip -F text c2 &&
110 + git cat-file commit HEAD >raw &&
111 + sed -e "1,/^$/d" raw >actual &&
112 + test_cmp expect actual
113 +'
114 +
115 test_done
wt-status.c
+9 -3
@@ -1006,13 +1006,19 @@ size_t wt_status_locate_end(const char *s, size_t len)
1006 return len;
1007 }
1008
1009 -void wt_status_add_cut_line(FILE *fp)
1009 +void wt_status_append_cut_line(struct strbuf *buf)
1010 {
1011 const char *explanation = _("Do not modify or remove the line above.\nEverything below it will be ignored.");
1012 +
1013 + strbuf_commented_addf(buf, "%s", cut_line);
1014 + strbuf_add_commented_lines(buf, explanation, strlen(explanation));
1015 +}
1016 +
1017 +void wt_status_add_cut_line(FILE *fp)
1018 +{
1019 struct strbuf buf = STRBUF_INIT;
1020
1014 - fprintf(fp, "%c %s", comment_line_char, cut_line);
1015 - strbuf_add_commented_lines(&buf, explanation, strlen(explanation));
1021 + wt_status_append_cut_line(&buf);
1022 fputs(buf.buf, fp);
1023 strbuf_release(&buf);
1024 }
wt-status.h
+1
@@ -129,6 +129,7 @@ struct wt_status {
129 };
130
131 size_t wt_status_locate_end(const char *s, size_t len);
132 +void wt_status_append_cut_line(struct strbuf *buf);
133 void wt_status_add_cut_line(FILE *fp);
134 void wt_status_prepare(struct repository *r, struct wt_status *s);
135 void wt_status_print(struct wt_status *s);