From: Corentin Bompard <[email protected]>

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 <[email protected]>
Signed-off-by: Nathan BERBEZIER <[email protected]>
Signed-off-by: Pablo CHABANNE <[email protected]>
Signed-off-by: Matthieu MOY <[email protected]>
---
 
 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

Reply via email to