From: Corentin Bompard <corentin.bompard@xxxxxxxxxxxxxxxxx> Add new argument which permits stripspace to escape backslashes in order to have commit messages which begins with commentChars and backslashes. Add new function strbuf_addbackslash which adds a backslash before commentChars and other backslashes used by git commit --amend which prevents escaping the commit message lines which starts with a commentChar. Signed-off-by: Corentin BOMPARD <corentin.bompard@xxxxxxxxxxxxxxxxx> Signed-off-by: Nathan BERBEZIER <nathan.berbezier@xxxxxxxxxxxxxxxxx> Signed-off-by: Pablo CHABANNE <pablo.chabanne@xxxxxxxxxxxxxxxxx> Signed-off-by: Matthieu MOY <matthieu.moy@xxxxxxxxxxxxx> --- builtin/am.c | 2 +- builtin/branch.c | 2 +- builtin/commit.c | 8 ++- builtin/merge.c | 2 +- builtin/notes.c | 6 +- builtin/stripspace.c | 2 +- builtin/tag.c | 2 +- rebase-interactive.c | 2 +- sequencer.c | 6 +- strbuf.c | 64 +++++++++++++++++++- strbuf.h | 8 ++- t/t7526-commit-message-comments.sh | 93 ++++++++++++++++++++++++++++++ 12 files changed, 180 insertions(+), 17 deletions(-) create mode 100755 t/t7526-commit-message-comments.sh diff --git a/builtin/am.c b/builtin/am.c index 58a2aef28..58817ab90 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -1212,7 +1212,7 @@ static int parse_mail(struct am_state *state, const char *mail) strbuf_addstr(&msg, "\n\n"); strbuf_addbuf(&msg, &mi.log_message); - strbuf_stripspace(&msg, 0); + strbuf_stripspace(&msg, 0, 0); assert(!state->author_name); state->author_name = strbuf_detach(&author_name, NULL); diff --git a/builtin/branch.c b/builtin/branch.c index 1be727209..0bfb17fc3 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -568,7 +568,7 @@ static int edit_branch_description(const char *branch_name) strbuf_release(&buf); return -1; } - strbuf_stripspace(&buf, 1); + strbuf_stripspace(&buf, 1, 0); strbuf_addf(&name, "branch.%s.description", branch_name); git_config_set(name.buf, buf.len ? buf.buf : NULL); diff --git a/builtin/commit.c b/builtin/commit.c index 2986553d5..49075a7ac 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -710,8 +710,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix, } else if (use_message) { char *buffer; buffer = strstr(use_message_buffer, "\n\n"); - if (buffer) + if (buffer) { strbuf_addstr(&sb, skip_blank_lines(buffer + 2)); + strbuf_addbackslash(&sb); + } hook_arg1 = "commit"; hook_arg2 = use_message; } else if (fixup_message) { @@ -786,7 +788,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, s->hints = 0; if (clean_message_contents) - strbuf_stripspace(&sb, 0); + strbuf_stripspace(&sb, 0, 0); if (signoff) append_signoff(&sb, ignore_non_trailer(sb.buf, sb.len), 0); @@ -1621,7 +1623,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) strbuf_setlen(&sb, wt_status_locate_end(sb.buf, sb.len)); if (cleanup_mode != COMMIT_MSG_CLEANUP_NONE) - strbuf_stripspace(&sb, cleanup_mode == COMMIT_MSG_CLEANUP_ALL); + strbuf_stripspace(&sb, cleanup_mode == COMMIT_MSG_CLEANUP_ALL, 1); if (message_is_empty(&sb, cleanup_mode) && !allow_empty_message) { rollback_index_files(); diff --git a/builtin/merge.c b/builtin/merge.c index e47d77bae..d0c3cb033 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -829,7 +829,7 @@ static void prepare_to_commit(struct commit_list *remoteheads) abort_commit(remoteheads, NULL); read_merge_msg(&msg); - strbuf_stripspace(&msg, 0 < option_edit); + strbuf_stripspace(&msg, 0 < option_edit, 0); if (!msg.len) abort_commit(remoteheads, _("Empty commit message.")); strbuf_release(&merge_msg); diff --git a/builtin/notes.c b/builtin/notes.c index 02e97f55c..fd304ed9c 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -196,7 +196,7 @@ static void prepare_note_data(const struct object_id *object, struct note_data * if (launch_editor(d->edit_path, &d->buf, NULL)) { die(_("please supply the note contents using either -m or -F option")); } - strbuf_stripspace(&d->buf, 1); + strbuf_stripspace(&d->buf, 1, 0); } } @@ -221,7 +221,7 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset) if (d->buf.len) strbuf_addch(&d->buf, '\n'); strbuf_addstr(&d->buf, arg); - strbuf_stripspace(&d->buf, 0); + strbuf_stripspace(&d->buf, 0, 0); d->given = 1; return 0; @@ -240,7 +240,7 @@ static int parse_file_arg(const struct option *opt, const char *arg, int unset) die_errno(_("cannot read '%s'"), arg); } else if (strbuf_read_file(&d->buf, arg, 1024) < 0) die_errno(_("could not open or read '%s'"), arg); - strbuf_stripspace(&d->buf, 0); + strbuf_stripspace(&d->buf, 0, 0); d->given = 1; return 0; diff --git a/builtin/stripspace.c b/builtin/stripspace.c index be33eb83c..c5d449b5e 100644 --- a/builtin/stripspace.c +++ b/builtin/stripspace.c @@ -55,7 +55,7 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix) die_errno("could not read the input"); if (mode == STRIP_DEFAULT || mode == STRIP_COMMENTS) - strbuf_stripspace(&buf, mode == STRIP_COMMENTS); + strbuf_stripspace(&buf, mode == STRIP_COMMENTS, 0); else comment_lines(&buf); diff --git a/builtin/tag.c b/builtin/tag.c index 02f6bd127..cb9422c17 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -261,7 +261,7 @@ static void create_tag(const struct object_id *object, const char *tag, } if (opt->cleanup_mode != CLEANUP_NONE) - strbuf_stripspace(buf, opt->cleanup_mode == CLEANUP_ALL); + strbuf_stripspace(buf, opt->cleanup_mode == CLEANUP_ALL, 0); if (!opt->message_given && !buf->len) die(_("no tag message?")); diff --git a/rebase-interactive.c b/rebase-interactive.c index 68aff1dac..6e6e45c45 100644 --- a/rebase-interactive.c +++ b/rebase-interactive.c @@ -61,7 +61,7 @@ int edit_todo_list(struct repository *r, unsigned flags) if (strbuf_read_file(&buf, todo_file, 0) < 0) return error_errno(_("could not read '%s'."), todo_file); - strbuf_stripspace(&buf, 1); + strbuf_stripspace(&buf, 1, 0); if (write_message(buf.buf, buf.len, todo_file, 0)) { strbuf_release(&buf); return -1; diff --git a/sequencer.c b/sequencer.c index 0db410d59..231682632 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1041,7 +1041,7 @@ int template_untouched(const struct strbuf *sb, const char *template_file, if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0) return 0; - strbuf_stripspace(&tmpl, cleanup_mode == COMMIT_MSG_CLEANUP_ALL); + strbuf_stripspace(&tmpl, cleanup_mode == COMMIT_MSG_CLEANUP_ALL, 0); if (!skip_prefix(sb->buf, tmpl.buf, &start)) start = sb->buf; strbuf_release(&tmpl); @@ -1386,7 +1386,7 @@ static int try_to_commit(struct repository *r, opts->default_msg_cleanup; if (cleanup != COMMIT_MSG_CLEANUP_NONE) - strbuf_stripspace(msg, cleanup == COMMIT_MSG_CLEANUP_ALL); + strbuf_stripspace(msg, cleanup == COMMIT_MSG_CLEANUP_ALL, 0); if ((flags & EDIT_MSG) && message_is_empty(msg, cleanup)) { res = 1; /* run 'git commit' to display error message */ goto out; @@ -4941,7 +4941,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla return -1; } - strbuf_stripspace(buf, 1); + strbuf_stripspace(buf, 1, 0); if (buf->len == 0) { apply_autostash(opts); sequencer_remove_state(opts); diff --git a/strbuf.c b/strbuf.c index f6a6cf78b..148ba2815 100644 --- a/strbuf.c +++ b/strbuf.c @@ -966,8 +966,12 @@ static size_t cleanup(char *line, size_t len) * * Enable skip_comments to skip every line starting with comment * character. + * + * Enable escape_backslash to remove backslash in beginning of + * lines. */ -void strbuf_stripspace(struct strbuf *sb, int skip_comments) +void strbuf_stripspace(struct strbuf *sb, int skip_comments, + int escape_backslash) { size_t empties = 0; size_t i, j, len, newlen; @@ -999,8 +1003,66 @@ void strbuf_stripspace(struct strbuf *sb, int skip_comments) } strbuf_setlen(sb, j); + + if (escape_backslash) { + strbuf_escape_backslash(sb); + } +} + +/* + * Add a backslash in front of commentChar and other backslash. + */ +void strbuf_addbackslash(struct strbuf *sb) +{ + size_t i, len = 0, total = 0; + char *eol; + + for (i = 0; i < sb->len; i += len) { + eol = memchr(sb->buf + i, '\n', sb->len -i); + len = eol ? eol - (sb->buf + i) + 1 : sb->len - i; + + if (len > 0 && sb->buf[i] == comment_line_char) { + memmove(sb->buf + i + 1, sb->buf + i, sb->len - i); + sb->buf[i] = '\\'; + total += (len + 1); + } else if (len > 0 && sb->buf[i] == '\\') { + memmove(sb->buf + i + 1, sb->buf + i, sb->len - i); + sb->buf[i] = '\\'; + total += len; + } else { + total += len; + } + } + strbuf_grow(sb, total-len); + strbuf_setlen(sb, total); } +/* + * Escape backslash in beginning of lines. + */ +void strbuf_escape_backslash(struct strbuf *sb) +{ + size_t i, len, total = sb->len; + char *eol; + + for (i = 0; i < sb->len; i += len) { + eol = memchr(sb->buf + i, '\n', sb->len - i); + len = eol ? eol - (sb->buf + i) + 1 : sb->len - i; + + if (sb->buf[i] == '\\') { + memmove(sb->buf + i, sb->buf + i + 1, total - 1 - i); + total --; + len --; + i++; + } + /* Avoid initite loop if len=0 */ + if (len <= 0) len = 1; + } + strbuf_setlen(sb, sb->len - (sb->len - total)); +} + + + int strbuf_normalize_path(struct strbuf *src) { struct strbuf dst = STRBUF_INIT; diff --git a/strbuf.h b/strbuf.h index fc40873b6..6ee958498 100644 --- a/strbuf.h +++ b/strbuf.h @@ -499,8 +499,14 @@ int strbuf_normalize_path(struct strbuf *sb); /** * Strip whitespace from a buffer. The second parameter controls if * comments are considered contents to be removed or not. + * The third parameter controls if backslashes are escaped. */ -void strbuf_stripspace(struct strbuf *buf, int skip_comments); +void strbuf_stripspace(struct strbuf *buf, int skip_comments, + int escape_backslash); + +void strbuf_escape_backslash(struct strbuf *sb); + +void strbuf_addbackslash(struct strbuf *sb); static inline int strbuf_strip_suffix(struct strbuf *sb, const char *suffix) { diff --git a/t/t7526-commit-message-comments.sh b/t/t7526-commit-message-comments.sh new file mode 100755 index 000000000..e4b04eb7a --- /dev/null +++ b/t/t7526-commit-message-comments.sh @@ -0,0 +1,93 @@ +#!/bin/sh + +test_description='"git commit" allows # in the start of a line in commit message. + +' +. ./test-lib.sh + + +# set up fake editor for interactive editing +cat >fake-editor <<'EOF' +#!/bin/sh +cp FAKE_MSG "$1" +exit 0 +EOF +chmod +x fake-editor + +FAKE_EDITOR="$(pwd)/fake-editor" +export FAKE_EDITOR + +commit_msg_is () { + test "$(git log --pretty=format:%s%b -1)" = "$1" +} + +ensure_fresh_upstream () { + rm -rf parent && git init --bare parent +} + +test_expect_success 'setup bare parent' ' + ensure_fresh_upstream && + git remote add upstream parent +' + +test_expect_success 'git commit "\#text" keeps "#text" as a commit message' ' + echo test1 >file && + git add file && + printf "%s\n" "\\#text" >FAKE_MSG && + GIT_EDITOR="\"\$FAKE_EDITOR\"" git commit && + commit_msg_is "#text" +' + +test_expect_success 'git commit "\text" keeps "text" as a commit message' ' + echo test2 >file && + git add file && + printf "%s\n" "\\text" >FAKE_MSG && + GIT_EDITOR="\"\$FAKE_EDITOR\"" git commit && + commit_msg_is "text" +' + +test_expect_success 'git commit "\\text" keeps "\text" as a commit message' ' + echo test5 >file && + git add file && + printf "%s\n" "\\\\text" >FAKE_MSG && + GIT_EDITOR="\"\$FAKE_EDITOR\"" git commit && + commit_msg_is "\\text" +' + +test_expect_success 'git commit -m "\#text" keeps "#text" as a commit message' ' + echo test1 >file && + git add file && + git commit -m "\\#text" && + commit_msg_is "#text" +' + +test_expect_success 'git commit -m "\text" keeps "text" as a commit message' ' + echo test2 >file && + git add file && + git commit -m "\\text" && + commit_msg_is "text" +' + +test_expect_success 'git commit -m "\\text" keeps "\text" as a commit message' ' + echo test3 >file && + git add file && + git commit -m "\\\\text" && + commit_msg_is "\\text" +' + +test_expect_success 'git commit --amend add backslash in front of comment +and other backslash' ' + echo "\\#Include something" > msg && + echo "" >> msg && + echo "Some content" >> msg && + echo "\\\\\\Backslash example" >> msg && + echo "test amend" > file && + git add file && + git commit -F msg && + cat .git/COMMIT_EDITMSG > expect && + git commit --amend && + sed "/^#/d" .git/COMMIT_EDITMSG > actual && + sed -i -e "$ d" actual && + test_cmp expect actual +' +test_done -- 2.21.0-rc0