{"thread":{"id":"59667","subject":"[PATCH v9 1/6] notes.c: cleanup 'strbuf_grow' call in 'append_edit'","startedAt":"2023-04-28T09:23:51Z","lastAt":"2023-06-21T02:50:37Z","messageCount":55,"participants":["Teng Long","Junio C Hamano","Kristoffer Haugsbakk","Jeff King","Eric Sunshine"],"isPatch":true,"patchVersion":9,"patchTotal":6},"messages":[{"id":"476258","messageId":"0634434e2a3e40ef4af5013349202491d55fe1d7.1682671758.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1682671758.git.dyroneteng@gmail.com","subject":"[PATCH v9 1/6] notes.c: cleanup 'strbuf_grow' call in 'append_edit'","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-04-28T09:23:29Z","receivedAt":"2023-04-28T09:23:51Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\nLet's cleanup the unnecessary 'strbuf_grow' call in 'append_edit'. This\n\"strbuf_grow(&d.buf, size + 1);\" is prepared for insert a blank line if\nneeded, but actually when inserting, \"strbuf_insertstr(&d.buf, 0,\n\"\\n\");\" will do the \"grow\" for us.\n\n348f199b (builtin-notes: Refactor handling of -F option to allow\ncombining -m and -F, 2010-02-13) added these to mimic the code\nintroduced by 2347fae5 (builtin-notes: Add \"append\" subcommand for\nappending to note objects, 2010-02-13) that reads in previous note\nbefore the message.  And the resulting code with explicit sizing is\ncarried to this day.\n\nIn the context of reading an existing note in, exact sizing may have\nmade sense, but because the resulting note needs cleansing with\nstripspace() when appending with this option, such an exact sizing\ndoes not buy us all that much in practice.\n\nIt may help avoiding overallocation due to ALLOC_GROW() slop, but\nnobody can feed so many long messages for it to matter from the\ncommand line.\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nHelped-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/notes.c | 2 --\n 1 file changed, 2 deletions(-)\n\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 4ff44f1e..c501c6ee 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -219,7 +219,6 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)\n \n \tBUG_ON_OPT_NEG(unset);\n \n-\tstrbuf_grow(&d->buf, strlen(arg) + 2);\n \tif (d->buf.len)\n \t\tstrbuf_addch(&d->buf, '\\n');\n \tstrbuf_addstr(&d->buf, arg);\n@@ -623,7 +622,6 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \t\tchar *prev_buf = repo_read_object_file(the_repository, note,\n \t\t\t\t\t\t       &type, &size);\n \n-\t\tstrbuf_grow(&d.buf, size + 1);\n \t\tif (d.buf.len && prev_buf && size)\n \t\t\tstrbuf_insertstr(&d.buf, 0, \"\\n\");\n \t\tif (prev_buf && size)\n-- \n2.40.0.358.g2947072e.dirty\n\n"},{"id":"476259","messageId":"cover.1682671758.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1682429602.git.dyroneteng@gmail.com","subject":"[PATCH v9 0/6] notes.c: introduce \"--separator\" option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-04-28T09:23:28Z","receivedAt":"2023-04-28T09:23:55Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\nDiff since v8:\n\n1. test case: Uniform indent format, as recommended by Junio C Hamano.\n\n2. [4/6] make the static var \"separator\" initialized as \"\\n\", simplify\n   the code in \"insert_separator(...)\".\n\n3. [4/6] I don't change the other parts about the \"struct note_msg\", the\n   stripspace way (Junio suggest to consider about the stripspace each messge\n   individually, but I found it will break the compatibility about \"-C\",\n   which can be found the case of 'reuse with \"-C\" and add note with \"-m\",\n   \"-m\" will stripspace all together').\n4. [5/6] Optimized the commit message and replace \"strbuf_insert*(...)\" with\n   \"strbuf_add*(...)\".\n\n5. [6/6] As Junio replied, I'm not sure whether the \"-C\" problem (When the\n   \"-C\" argument is used with \"-m/-F\", the order of \"-C\" in the options will\n   affect the result of stripspace differently,) is need to be fixed or keep\n   as is, I choose to do not break the old behaviour (In fact, I hope to fix\n   this issue in another patch, if at all, and let this long-tailed patchset\n   to mature faster, maybe).\n\nThanks.\n\nTeng Long (6):\n  notes.c: cleanup 'strbuf_grow' call in 'append_edit'\n  notes.c: use designated initializers for clarity\n  t3321: add test cases about the notes stripspace behavior\n  notes.c: introduce '--separator=<paragraph-break>' option\n  notes.c: append separator instead of insert by pos\n  notes.c: introduce \"--[no-]stripspace\" option\n\n Documentation/git-notes.txt |  42 ++-\n builtin/notes.c             | 141 ++++++---\n t/t3301-notes.sh            | 126 ++++++++\n t/t3321-notes-stripspace.sh | 577 ++++++++++++++++++++++++++++++++++++\n 4 files changed, 844 insertions(+), 42 deletions(-)\n create mode 100755 t/t3321-notes-stripspace.sh\n\nRange-diff against v8:\n1:  0634434e = 1:  0634434e notes.c: cleanup 'strbuf_grow' call in 'append_edit'\n2:  4ad78405 = 2:  4ad78405 notes.c: use designated initializers for clarity\n3:  6dfb5bf2 ! 3:  c2fc2091 t3321: add test cases about the notes stripspace behavior\n    @@ t/t3321-notes-stripspace.sh (new)\n     @@\n     +#!/bin/sh\n     +#\n    -+# Copyright (c) 2007 Teng Long\n    ++# Copyright (c) 2023 Teng Long\n     +#\n     +\n     +test_description='Test commit notes with stripspace behavior'\n    @@ t/t3321-notes-stripspace.sh (new)\n     +test_expect_success 'add note by editor' '\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >expect <<-EOF &&\n    -+\t\tfirst-line\n    ++\tfirst-line\n     +\n    -+\t\tsecond-line\n    ++\tsecond-line\n     +\tEOF\n     +\n     +\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes add  &&\n    @@ t/t3321-notes-stripspace.sh (new)\n     +test_expect_success 'add note by specifying single \"-m\"' '\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >expect <<-EOF &&\n    -+\t\tfirst-line\n    ++\tfirst-line\n     +\n    -+\t\tsecond-line\n    ++\tsecond-line\n     +\tEOF\n     +\n     +\tgit notes add -m \"${LF}first-line${MULTI_LF}second-line${LF}\" &&\n    @@ t/t3321-notes-stripspace.sh (new)\n     +test_expect_success 'append note by editor' '\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >expect <<-EOF &&\n    -+\t\tfirst-line\n    ++\tfirst-line\n     +\n    -+\t\tsecond-line\n    ++\tsecond-line\n     +\tEOF\n     +\n     +\tgit notes add -m \"first-line\" &&\n    @@ t/t3321-notes-stripspace.sh (new)\n     +test_expect_success 'append note by specifying single \"-m\"' '\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >expect <<-EOF &&\n    -+\t\tfirst-line\n    ++\tfirst-line\n     +\n    -+\t\tsecond-line\n    ++\tsecond-line\n     +\tEOF\n     +\n     +\tgit notes add -m \"${LF}first-line\" &&\n    @@ t/t3321-notes-stripspace.sh (new)\n     +test_expect_success 'add note by specifying single \"-F\"' '\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >expect <<-EOF &&\n    -+\t\tfirst-line\n    ++\tfirst-line\n     +\n    -+\t\tsecond-line\n    ++\tsecond-line\n     +\tEOF\n     +\n     +\tcat >note-file <<-EOF &&\n    -+\t\t${LF}\n    -+\t\tfirst-line\n    -+\t\t${MULTI_LF}\n    -+\t\tsecond-line\n    -+\t\t${LF}\n    ++\t${LF}\n    ++\tfirst-line\n    ++\t${MULTI_LF}\n    ++\tsecond-line\n    ++\t${LF}\n     +\tEOF\n     +\n     +\tgit notes add -F note-file &&\n    @@ t/t3321-notes-stripspace.sh (new)\n     +test_expect_success 'add notes by specifying multiple \"-F\"' '\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >expect <<-EOF &&\n    -+\t\tfile-1-first-line\n    ++\tfile-1-first-line\n     +\n    -+\t\tfile-1-second-line\n    ++\tfile-1-second-line\n     +\n    -+\t\tfile-2-first-line\n    ++\tfile-2-first-line\n     +\n    -+\t\tfile-2-second-line\n    ++\tfile-2-second-line\n     +\tEOF\n     +\n     +\tcat >note-file-1 <<-EOF &&\n    -+\t\t${LF}\n    -+\t\tfile-1-first-line\n    -+\t\t${MULTI_LF}\n    -+\t\tfile-1-second-line\n    -+\t\t${LF}\n    ++\t${LF}\n    ++\tfile-1-first-line\n    ++\t${MULTI_LF}\n    ++\tfile-1-second-line\n    ++\t${LF}\n     +\tEOF\n     +\n     +\tcat >note-file-2 <<-EOF &&\n    -+\t\t${LF}\n    -+\t\tfile-2-first-line\n    -+\t\t${MULTI_LF}\n    -+\t\tfile-2-second-line\n    -+\t\t${LF}\n    ++\t${LF}\n    ++\tfile-2-first-line\n    ++\t${MULTI_LF}\n    ++\tfile-2-second-line\n    ++\t${LF}\n     +\tEOF\n     +\n     +\tgit notes add -F note-file-1 -F note-file-2 &&\n    @@ t/t3321-notes-stripspace.sh (new)\n     +test_expect_success 'append note by specifying single \"-F\"' '\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >expect <<-EOF &&\n    -+\t\tinitial-line\n    ++\tinitial-line\n     +\n    -+\t\tfirst-line\n    ++\tfirst-line\n     +\n    -+\t\tsecond-line\n    ++\tsecond-line\n     +\tEOF\n     +\n     +\tcat >note-file <<-EOF &&\n    -+\t\t${LF}\n    -+\t\tfirst-line\n    -+\t\t${MULTI_LF}\n    -+\t\tsecond-line\n    -+\t\t${LF}\n    ++\t${LF}\n    ++\tfirst-line\n    ++\t${MULTI_LF}\n    ++\tsecond-line\n    ++\t${LF}\n     +\tEOF\n     +\n     +\tgit notes add -m \"initial-line\" &&\n    @@ t/t3321-notes-stripspace.sh (new)\n     +test_expect_success 'append notes by specifying multiple \"-F\"' '\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >expect <<-EOF &&\n    -+\t\tinitial-line\n    ++\tinitial-line\n     +\n    -+\t\tfile-1-first-line\n    ++\tfile-1-first-line\n     +\n    -+\t\tfile-1-second-line\n    ++\tfile-1-second-line\n     +\n    -+\t\tfile-2-first-line\n    ++\tfile-2-first-line\n     +\n    -+\t\tfile-2-second-line\n    ++\tfile-2-second-line\n     +\tEOF\n     +\n     +\tcat >note-file-1 <<-EOF &&\n    -+\t\t${LF}\n    -+\t\tfile-1-first-line\n    -+\t\t${MULTI_LF}\n    -+\t\tfile-1-second-line\n    -+\t\t${LF}\n    ++\t${LF}\n    ++\tfile-1-first-line\n    ++\t${MULTI_LF}\n    ++\tfile-1-second-line\n    ++\t${LF}\n     +\tEOF\n     +\n     +\tcat >note-file-2 <<-EOF &&\n    -+\t\t${LF}\n    -+\t\tfile-2-first-line\n    -+\t\t${MULTI_LF}\n    -+\t\tfile-2-second-line\n    -+\t\t${LF}\n    ++\t${LF}\n    ++\tfile-2-first-line\n    ++\t${MULTI_LF}\n    ++\tfile-2-second-line\n    ++\t${LF}\n     +\tEOF\n     +\n     +\tgit notes add -m \"initial-line\" &&\n    @@ t/t3321-notes-stripspace.sh (new)\n     +test_expect_success 'add note by specifying \"-C\" , do not stripspace is the default behavior' '\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >expect <<-EOF &&\n    -+\t\t${LF}\n    -+\t\tfirst-line\n    -+\t\t${MULTI_LF}\n    -+\t\tsecond-line\n    -+\t\t${LF}\n    ++\t${LF}\n    ++\tfirst-line\n    ++\t${MULTI_LF}\n    ++\tsecond-line\n    ++\t${LF}\n     +\tEOF\n     +\n     +\tcat expect | git hash-object -w --stdin >blob &&\n    @@ t/t3321-notes-stripspace.sh (new)\n     +test_expect_success 'add notes with \"-C\" and \"-m\", \"-m\" will stripspace all together' '\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >data <<-EOF &&\n    -+\t\t${LF}\n    -+\t\tfirst-line\n    -+\t\t${MULTI_LF}\n    -+\t\tsecond-line\n    -+\t\t${LF}\n    ++\t${LF}\n    ++\tfirst-line\n    ++\t${MULTI_LF}\n    ++\tsecond-line\n    ++\t${LF}\n     +\tEOF\n     +\n     +\tcat >expect <<-EOF &&\n    -+\t\tfirst-line\n    ++\tfirst-line\n     +\n    -+\t\tsecond-line\n    ++\tsecond-line\n     +\n    -+\t\tthird-line\n    ++\tthird-line\n     +\tEOF\n     +\n     +\tcat data | git hash-object -w --stdin >blob &&\n    @@ t/t3321-notes-stripspace.sh (new)\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >data <<-EOF &&\n     +\n    -+\t\tsecond-line\n    ++\tsecond-line\n     +\tEOF\n     +\n     +\tcat >expect <<-EOF &&\n    -+\t\tfirst-line\n    -+\t\t${LF}\n    -+\t\tsecond-line\n    ++\tfirst-line\n    ++\t${LF}\n    ++\tsecond-line\n     +\tEOF\n     +\n     +\tcat data | git hash-object -w --stdin >blob &&\n4:  be86f9ca ! 4:  ed930ef4 notes.c: introduce '--separator=<paragraph-break>' option\n    @@ Commit message\n         insert a blank line between the paragraphs, like:\n     \n              $ git notes add -m foo -m bar\n    -         $ git notes show HEAD | cat\n    +         $ git notes show HEAD\n              foo\n     \n              bar\n    @@ Commit message\n         'git notes append', for example when executing:\n     \n             $ git notes add -m foo -m bar --separator=\"-\"\n    -        $ git notes show HEAD | cat\n    +        $ git notes show HEAD\n             foo\n             -\n             bar\n    @@ builtin/notes.c\n      #include \"worktree.h\"\n      #include \"write-or-die.h\"\n      \n    -+static char *separator = NULL;\n    ++static char *separator = \"\\n\";\n      static const char * const git_notes_usage[] = {\n      \tN_(\"git notes [--ref <notes-ref>] [list [<object>]]\"),\n     -\tN_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n    @@ builtin/notes.c: static void free_note_data(struct note_data *d)\n      \t}\n      \tstrbuf_release(&d->buf);\n     +\n    -+\twhile (d->msg_nr) {\n    -+\t\t--d->msg_nr;\n    ++\twhile (d->msg_nr--) {\n     +\t\tstrbuf_release(&d->messages[d->msg_nr]->buf);\n     +\t\tfree(d->messages[d->msg_nr]);\n     +\t}\n    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_\n      \n     +static void insert_separator(struct strbuf *message, size_t pos)\n     +{\n    -+\tif (!separator)\n    -+\t\tstrbuf_insertstr(message, pos, \"\\n\");\n    -+\telse if (separator[strlen(separator) - 1] == '\\n')\n    -+\t\tstrbuf_insertstr(message, pos, separator);\n    ++\tif (separator[strlen(separator) - 1] == '\\n')\n    ++\t\tstrbuf_addstr(message, separator);\n     +\telse\n     +\t\tstrbuf_insertf(message, pos, \"%s%s\", separator, \"\\n\");\n     +}\n5:  ef40e0ef ! 5:  eea2246f notes.c: append separator instead of insert by pos\n    @@ Metadata\n      ## Commit message ##\n         notes.c: append separator instead of insert by pos\n     \n    -    This commit rename \"insert_separator\" to \"append_separator\" and also\n    -    remove the \"postion\" argument, this serves two purpose:\n    +    Rename \"insert_separator\" to \"append_separator\" and also remove the\n    +    \"postion\" argument, this serves two purpose:\n     \n         The first is that when specifying more than one \"-m\" ( like \"-F\", etc)\n         to \"git notes add\" or \"git notes append\", the order of them matters,\n    @@ Commit message\n         so we don't have to make the caller specify the position, the \"append\"\n         operation is enough and clear.\n     \n    -    The second is that when we execute the \"git notes append\" subcommand\n    -    , we need to combine the \"prev_note\" and \"current_note\" to get the\n    +    The second is that when we execute the \"git notes append\" subcommand,\n    +    we need to combine the \"prev_note\" and \"current_note\" to get the\n         final result. Before, we inserted a newline character at the beginning\n         of \"current_note\". Now, we will append a newline to the end of\n         \"prev_note\" instead, this will give the consisitent results.\n    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_\n     -static void insert_separator(struct strbuf *message, size_t pos)\n     +static void append_separator(struct strbuf *message)\n      {\n    - \tif (!separator)\n    --\t\tstrbuf_insertstr(message, pos, \"\\n\");\n    -+\t\tstrbuf_insertstr(message, message->len, \"\\n\");\n    - \telse if (separator[strlen(separator) - 1] == '\\n')\n    --\t\tstrbuf_insertstr(message, pos, separator);\n    -+\t\tstrbuf_insertstr(message, message->len, separator);\n    + \tif (separator[strlen(separator) - 1] == '\\n')\n    + \t\tstrbuf_addstr(message, separator);\n      \telse\n     -\t\tstrbuf_insertf(message, pos, \"%s%s\", separator, \"\\n\");\n    -+\t\tstrbuf_insertf(message, message->len, \"%s%s\", separator, \"\\n\");\n    ++\t\tstrbuf_addf(message, \"%s%s\", separator, \"\\n\");\n      }\n      \n      static void concat_messages(struct note_data *d)\n6:  f60f7432 ! 6:  20063bea notes.c: introduce \"--[no-]stripspace\" option\n    @@ t/t3321-notes-stripspace.sh: test_expect_success 'add note by editor' '\n     +test_expect_success 'add note by specifying single \"-m\", \"--stripspace\" is the default behavior' '\n      \ttest_when_finished \"git notes remove\" &&\n      \tcat >expect <<-EOF &&\n    - \t\tfirst-line\n    + \tfirst-line\n     @@ t/t3321-notes-stripspace.sh: test_expect_success 'add note by specifying single \"-m\"' '\n      \n      \tgit notes add -m \"${LF}first-line${MULTI_LF}second-line${LF}\" &&\n    @@ t/t3321-notes-stripspace.sh: test_expect_success 'add note by specifying single\n     +test_expect_success 'add note by specifying single \"-m\" and \"--no-stripspace\" ' '\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >expect <<-EOF &&\n    -+\t\t${LF}first-line${MULTI_LF}second-line\n    ++\t${LF}first-line${MULTI_LF}second-line\n     +\tEOF\n     +\n     +\tgit notes add --no-stripspace \\\n    @@ t/t3321-notes-stripspace.sh: test_expect_success 'add note by specifying multipl\n      \ttest_cmp expect actual\n      '\n      \n    --\n    --test_expect_success 'append note by editor' '\n     +test_expect_success 'add notes by specifying multiple \"-m\" and \"--no-stripspace\"' '\n    - \ttest_when_finished \"git notes remove\" &&\n    - \tcat >expect <<-EOF &&\n    -+\t\t${LF}\n    - \t\tfirst-line\n    --\n    --\t\tsecond-line\n    -+\t\t${MULTI_LF}\n    -+\t\tsecond-line${LF}\n    - \tEOF\n    - \n    --\tgit notes add -m \"first-line\" &&\n    --\tMSG=\"${MULTI_LF}second-line${LF}\" git notes append  &&\n    ++\ttest_when_finished \"git notes remove\" &&\n    ++\tcat >expect <<-EOF &&\n    ++\t${LF}\n    ++\tfirst-line\n    ++\t${MULTI_LF}\n    ++\tsecond-line${LF}\n    ++\tEOF\n    ++\n     +\tgit notes add --no-stripspace \\\n     +\t\t      -m \"${LF}\" \\\n     +\t\t      -m \"first-line\" \\\n     +\t\t      -m \"${MULTI_LF}\" \\\n     +\t\t      -m \"second-line\" \\\n     +\t\t      -m \"${LF}\" &&\n    - \tgit notes show >actual &&\n    - \ttest_cmp expect actual\n    - '\n    - \n    --test_expect_success 'append note by specifying single \"-m\"' '\n    -+test_expect_success 'add note by specifying single \"-F\", \"--stripspace\" is the default behavior' '\n    - \ttest_when_finished \"git notes remove\" &&\n    - \tcat >expect <<-EOF &&\n    - \t\tfirst-line\n    -@@ t/t3321-notes-stripspace.sh: test_expect_success 'append note by specifying single \"-m\"' '\n    - \t\tsecond-line\n    - \tEOF\n    - \n    --\tgit notes add -m \"${LF}first-line\" &&\n    --\tgit notes append -m \"${MULTI_LF}second-line${LF}\" &&\n    --\tgit notes show >actual &&\n    --\ttest_cmp expect actual\n    --'\n    --\n    --test_expect_success 'append note by specifying multiple \"-m\"' '\n    --\ttest_when_finished \"git notes remove\" &&\n    --\tcat >expect <<-EOF &&\n    --\tfirst-line\n    --\n    --\tsecond-line\n    -+\tcat >note-file <<-EOF &&\n    -+\t\t${LF}\n    -+\t\tfirst-line\n    -+\t\t${MULTI_LF}\n    -+\t\tsecond-line\n    -+\t\t${LF}\n    - \tEOF\n    - \n    --\tgit notes add -m \"${LF}first-line\" &&\n    --\tgit notes append -m \"${MULTI_LF}\" \\\n    --\t\t      -m \"second-line\" \\\n    --\t\t      -m \"${LF}\" &&\n    -+\tgit notes add -F note-file &&\n    - \tgit notes show >actual &&\n    --\ttest_cmp expect actual\n    -+\ttest_cmp expect actual &&\n    -+\tgit notes remove &&\n    -+\tgit notes add --stripspace -F note-file &&\n    -+\tgit notes show >actual\n    - '\n    - \n    --test_expect_success 'add note by specifying single \"-F\"' '\n    -+test_expect_success 'add note by specifying single \"-F\" and \"--no-stripspace\"' '\n    - \ttest_when_finished \"git notes remove\" &&\n    - \tcat >expect <<-EOF &&\n    -+\t\t${LF}\n    - \t\tfirst-line\n    --\n    -+\t\t${MULTI_LF}\n    - \t\tsecond-line\n    -+\t\t${LF}\n    - \tEOF\n    - \n    - \tcat >note-file <<-EOF &&\n    -@@ t/t3321-notes-stripspace.sh: test_expect_success 'add note by specifying single \"-F\"' '\n    - \t\t${LF}\n    - \tEOF\n    - \n    --\tgit notes add -F note-file &&\n    -+\tgit notes add --no-stripspace -F note-file &&\n    - \tgit notes show >actual &&\n    - \ttest_cmp expect actual\n    - '\n    - \n    --test_expect_success 'add notes by specifying multiple \"-F\"' '\n    -+test_expect_success 'add note by specifying multiple \"-F\", \"--stripspace\" is the default behavior' '\n    - \ttest_when_finished \"git notes remove\" &&\n    - \tcat >expect <<-EOF &&\n    - \t\tfile-1-first-line\n    -@@ t/t3321-notes-stripspace.sh: test_expect_success 'add notes by specifying multiple \"-F\"' '\n    - \n    - \tgit notes add -F note-file-1 -F note-file-2 &&\n    - \tgit notes show >actual &&\n    -+\ttest_cmp expect actual &&\n    -+\tgit notes remove &&\n    -+\tgit notes add --stripspace -F note-file-1 -F note-file-2 &&\n     +\tgit notes show >actual &&\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success 'add note by specifying multiple \"-F\" with \"--no-stripspace\"' '\n    ++test_expect_success 'add note by specifying single \"-F\", \"--stripspace\" is the default behavior' '\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >expect <<-EOF &&\n    -+\t\t${LF}\n    -+\t\tfile-1-first-line\n    -+\t\t${MULTI_LF}\n    -+\t\tfile-1-second-line\n    -+\t\t${LF}\n    -+\n    -+\t\t${LF}\n    -+\t\tfile-2-first-line\n    -+\t\t${MULTI_LF}\n    -+\t\tfile-2-second-line\n    -+\t\t${LF}\n    -+\tEOF\n    ++\tfirst-line\n     +\n    -+\tcat >note-file-1 <<-EOF &&\n    -+\t\t${LF}\n    -+\t\tfile-1-first-line\n    -+\t\t${MULTI_LF}\n    -+\t\tfile-1-second-line\n    -+\t\t${LF}\n    ++\tsecond-line\n     +\tEOF\n     +\n    -+\tcat >note-file-2 <<-EOF &&\n    -+\t\t${LF}\n    -+\t\tfile-2-first-line\n    -+\t\t${MULTI_LF}\n    -+\t\tfile-2-second-line\n    -+\t\t${LF}\n    ++\tcat >note-file <<-EOF &&\n    ++\t${LF}\n    ++\tfirst-line\n    ++\t${MULTI_LF}\n    ++\tsecond-line\n    ++\t${LF}\n     +\tEOF\n     +\n    -+\tgit notes add --no-stripspace -F note-file-1 -F note-file-2 &&\n    ++\tgit notes add -F note-file &&\n     +\tgit notes show >actual &&\n    -+\ttest_cmp expect actual\n    ++\ttest_cmp expect actual &&\n    ++\tgit notes remove &&\n    ++\tgit notes add --stripspace -F note-file &&\n    ++\tgit notes show >actual\n     +'\n     +\n    -+test_expect_success 'append note by editor' '\n    ++test_expect_success 'add note by specifying single \"-F\" and \"--no-stripspace\"' '\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >expect <<-EOF &&\n    -+\t\tfirst-line\n    ++\t${LF}\n    ++\tfirst-line\n    ++\t${MULTI_LF}\n    ++\tsecond-line\n    ++\t${LF}\n    ++\tEOF\n     +\n    -+\t\tsecond-line\n    ++\tcat >note-file <<-EOF &&\n    ++\t${LF}\n    ++\tfirst-line\n    ++\t${MULTI_LF}\n    ++\tsecond-line\n    ++\t${LF}\n     +\tEOF\n     +\n    -+\tgit notes add -m \"first-line\" &&\n    -+\tMSG=\"${MULTI_LF}second-line${LF}\" git notes append  &&\n    ++\tgit notes add --no-stripspace -F note-file &&\n     +\tgit notes show >actual &&\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success 'append note by specifying single \"-m\"' '\n    ++test_expect_success 'add note by specifying multiple \"-F\", \"--stripspace\" is the default behavior' '\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >expect <<-EOF &&\n    -+\t\tfirst-line\n    ++\tfile-1-first-line\n    ++\n    ++\tfile-1-second-line\n    ++\n    ++\tfile-2-first-line\n     +\n    -+\t\tsecond-line\n    ++\tfile-2-second-line\n    ++\tEOF\n    ++\n    ++\tcat >note-file-1 <<-EOF &&\n    ++\t${LF}\n    ++\tfile-1-first-line\n    ++\t${MULTI_LF}\n    ++\tfile-1-second-line\n    ++\t${LF}\n    ++\tEOF\n    ++\n    ++\tcat >note-file-2 <<-EOF &&\n    ++\t${LF}\n    ++\tfile-2-first-line\n    ++\t${MULTI_LF}\n    ++\tfile-2-second-line\n    ++\t${LF}\n     +\tEOF\n     +\n    -+\tgit notes add -m \"${LF}first-line\" &&\n    -+\tgit notes append -m \"${MULTI_LF}second-line${LF}\" &&\n    ++\tgit notes add -F note-file-1 -F note-file-2 &&\n    ++\tgit notes show >actual &&\n    ++\ttest_cmp expect actual &&\n    ++\tgit notes remove &&\n    ++\tgit notes add --stripspace -F note-file-1 -F note-file-2 &&\n     +\tgit notes show >actual &&\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success 'append note by specifying multiple \"-m\"' '\n    ++test_expect_success 'add note by specifying multiple \"-F\" with \"--no-stripspace\"' '\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >expect <<-EOF &&\n    -+\tfirst-line\n    ++\t${LF}\n    ++\tfile-1-first-line\n    ++\t${MULTI_LF}\n    ++\tfile-1-second-line\n    ++\t${LF}\n    ++\n    ++\t${LF}\n    ++\tfile-2-first-line\n    ++\t${MULTI_LF}\n    ++\tfile-2-second-line\n    ++\t${LF}\n    ++\tEOF\n     +\n    -+\tsecond-line\n    ++\tcat >note-file-1 <<-EOF &&\n    ++\t${LF}\n    ++\tfile-1-first-line\n    ++\t${MULTI_LF}\n    ++\tfile-1-second-line\n    ++\t${LF}\n    ++\tEOF\n    ++\n    ++\tcat >note-file-2 <<-EOF &&\n    ++\t${LF}\n    ++\tfile-2-first-line\n    ++\t${MULTI_LF}\n    ++\tfile-2-second-line\n    ++\t${LF}\n     +\tEOF\n     +\n    -+\tgit notes add -m \"${LF}first-line\" &&\n    -+\tgit notes append -m \"${MULTI_LF}\" -m \"second-line\" -m \"${LF}\" &&\n    ++\tgit notes add --no-stripspace -F note-file-1 -F note-file-2 &&\n     +\tgit notes show >actual &&\n    - \ttest_cmp expect actual\n    - '\n    ++\ttest_cmp expect actual\n    ++'\n      \n    + test_expect_success 'append note by editor' '\n    + \ttest_when_finished \"git notes remove\" &&\n     @@ t/t3321-notes-stripspace.sh: test_expect_success 'append notes by specifying multiple \"-F\"' '\n      \ttest_cmp expect actual\n      '\n    @@ t/t3321-notes-stripspace.sh: test_expect_success 'append notes by specifying mul\n     +test_expect_success 'append note by specifying multiple \"-F\" with \"--no-stripspace\"' '\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >expect <<-EOF &&\n    -+\t\tinitial-line\n    -+\t\t${LF}${LF}\n    -+\t\tfile-1-first-line\n    -+\t\t${MULTI_LF}\n    -+\t\tfile-1-second-line\n    -+\t\t${LF}\n    -+\n    -+\t\t${LF}\n    -+\t\tfile-2-first-line\n    -+\t\t${MULTI_LF}\n    -+\t\tfile-2-second-line\n    -+\t\t${LF}\n    ++\tinitial-line\n    ++\t${LF}${LF}\n    ++\tfile-1-first-line\n    ++\t${MULTI_LF}\n    ++\tfile-1-second-line\n    ++\t${LF}\n    ++\n    ++\t${LF}\n    ++\tfile-2-first-line\n    ++\t${MULTI_LF}\n    ++\tfile-2-second-line\n    ++\t${LF}\n     +\tEOF\n     +\n     +\tcat >note-file-1 <<-EOF &&\n    -+\t\t${LF}\n    -+\t\tfile-1-first-line\n    -+\t\t${MULTI_LF}\n    -+\t\tfile-1-second-line\n    -+\t\t${LF}\n    ++\t${LF}\n    ++\tfile-1-first-line\n    ++\t${MULTI_LF}\n    ++\tfile-1-second-line\n    ++\t${LF}\n     +\tEOF\n     +\n     +\tcat >note-file-2 <<-EOF &&\n    -+\t\t${LF}\n    -+\t\tfile-2-first-line\n    -+\t\t${MULTI_LF}\n    -+\t\tfile-2-second-line\n    -+\t\t${LF}\n    ++\t${LF}\n    ++\tfile-2-first-line\n    ++\t${MULTI_LF}\n    ++\tfile-2-second-line\n    ++\t${LF}\n     +\tEOF\n     +\n     +\tgit notes add -m \"initial-line\" &&\n    @@ t/t3321-notes-stripspace.sh: test_expect_success 'add notes with empty messages'\n     +test_expect_success 'add note by specifying \"-C\", \"--no-stripspace\" is the default behavior' '\n      \ttest_when_finished \"git notes remove\" &&\n      \tcat >expect <<-EOF &&\n    - \t\t${LF}\n    + \t${LF}\n     @@ t/t3321-notes-stripspace.sh: test_expect_success 'add note by specifying \"-C\" , do not stripspace is the defa\n      \tcat expect | git hash-object -w --stdin >blob &&\n      \tgit notes add -C $(cat blob) &&\n    @@ t/t3321-notes-stripspace.sh: test_expect_success 'add note by specifying \"-C\" ,\n     +test_expect_success 'reuse note by specifying \"-C\" and \"--stripspace\"' '\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >data <<-EOF &&\n    -+\t\t${LF}\n    -+\t\tfirst-line\n    -+\t\t${MULTI_LF}\n    -+\t\tsecond-line\n    -+\t\t${LF}\n    ++\t${LF}\n    ++\tfirst-line\n    ++\t${MULTI_LF}\n    ++\tsecond-line\n    ++\t${LF}\n     +\tEOF\n     +\n     +\tcat >expect <<-EOF &&\n    -+\t\tfirst-line\n    ++\tfirst-line\n     +\n    -+\t\tsecond-line\n    ++\tsecond-line\n     +\tEOF\n     +\n     +\tcat data | git hash-object -w --stdin >blob &&\n    @@ t/t3321-notes-stripspace.sh: test_expect_success 'add note by specifying \"-C\" ,\n     +test_expect_success 'reuse with \"-C\" and add note with \"-m\", \"-m\" will stripspace all together' '\n      \ttest_when_finished \"git notes remove\" &&\n      \tcat >data <<-EOF &&\n    - \t\t${LF}\n    + \t${LF}\n     @@ t/t3321-notes-stripspace.sh: test_expect_success 'add notes with \"-C\" and \"-m\", \"-m\" will stripspace all toge\n      \ttest_cmp expect actual\n      '\n    @@ t/t3321-notes-stripspace.sh: test_expect_success 'add notes with \"-m\" and \"-C\",\n     +test_expect_success 'add note by specifying \"-c\", \"--stripspace\" is the default behavior' '\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >expect <<-EOF &&\n    -+\t\tfirst-line\n    ++\tfirst-line\n     +\n    -+\t\tsecond-line\n    ++\tsecond-line\n     +\tEOF\n     +\n     +\techo \"initial-line\" | git hash-object -w --stdin >blob &&\n    @@ t/t3321-notes-stripspace.sh: test_expect_success 'add notes with \"-m\" and \"-C\",\n     +test_expect_success 'add note by specifying \"-c\" with \"--no-stripspace\"' '\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >expect <<-EOF &&\n    -+\t\t${LF}first-line${MULTI_LF}second-line${LF}\n    ++\t${LF}first-line${MULTI_LF}second-line${LF}\n     +\tEOF\n     +\n     +\techo \"initial-line\" | git hash-object -w --stdin >blob &&\n    @@ t/t3321-notes-stripspace.sh: test_expect_success 'add notes with \"-m\" and \"-C\",\n     +test_expect_success 'edit note by specifying \"-c\", \"--stripspace\" is the default behavior' '\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >expect <<-EOF &&\n    -+\t\tfirst-line\n    ++\tfirst-line\n     +\n    -+\t\tsecond-line\n    ++\tsecond-line\n     +\tEOF\n     +\n     +\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes edit &&\n    @@ t/t3321-notes-stripspace.sh: test_expect_success 'add notes with \"-m\" and \"-C\",\n     +test_expect_success 'edit note by specifying \"-c\" with \"--no-stripspace\"' '\n     +\ttest_when_finished \"git notes remove\" &&\n     +\tcat >expect <<-EOF &&\n    -+\t\t${LF}first-line${MULTI_LF}second-line${LF}\n    ++\t${LF}first-line${MULTI_LF}second-line${LF}\n     +\tEOF\n     +\n     +\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes add --no-stripspace &&\n-- \n2.40.0.358.g2947072e.dirty\n\n"},{"id":"476260","messageId":"4ad7840584191d6bbb3fcba72887aa569c797a15.1682671758.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1682671758.git.dyroneteng@gmail.com","subject":"[PATCH v9 2/6] notes.c: use designated initializers for clarity","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-04-28T09:23:30Z","receivedAt":"2023-04-28T09:24:02Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\nThe \"struct note_data d = { 0, 0, NULL, STRBUF_INIT };\" style could be\nreplaced with designated initializer for clarity.\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/notes.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex c501c6ee..9d8ca795 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -405,7 +405,7 @@ static int add(int argc, const char **argv, const char *prefix)\n \tstruct notes_tree *t;\n \tstruct object_id object, new_note;\n \tconst struct object_id *note;\n-\tstruct note_data d = { 0, 0, NULL, STRBUF_INIT };\n+\tstruct note_data d = { .buf = STRBUF_INIT };\n \tstruct option options[] = {\n \t\tOPT_CALLBACK_F('m', \"message\", &d, N_(\"message\"),\n \t\t\tN_(\"note contents as a string\"), PARSE_OPT_NONEG,\n@@ -571,7 +571,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \tconst struct object_id *note;\n \tchar *logmsg;\n \tconst char * const *usage;\n-\tstruct note_data d = { 0, 0, NULL, STRBUF_INIT };\n+\tstruct note_data d = { .buf = STRBUF_INIT };\n \tstruct option options[] = {\n \t\tOPT_CALLBACK_F('m', \"message\", &d, N_(\"message\"),\n \t\t\tN_(\"note contents as a string\"), PARSE_OPT_NONEG,\n-- \n2.40.0.358.g2947072e.dirty\n\n"},{"id":"476261","messageId":"c2fc2091d2afbb5911220bb55574fae11cf1c1dc.1682671758.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1682671758.git.dyroneteng@gmail.com","subject":"[PATCH v9 3/6] t3321: add test cases about the notes stripspace behavior","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-04-28T09:23:31Z","receivedAt":"2023-04-28T09:24:05Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n t/t3321-notes-stripspace.sh | 291 ++++++++++++++++++++++++++++++++++++\n 1 file changed, 291 insertions(+)\n create mode 100755 t/t3321-notes-stripspace.sh\n\ndiff --git a/t/t3321-notes-stripspace.sh b/t/t3321-notes-stripspace.sh\nnew file mode 100755\nindex 00000000..89977873\n--- /dev/null\n+++ b/t/t3321-notes-stripspace.sh\n@@ -0,0 +1,291 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2023 Teng Long\n+#\n+\n+test_description='Test commit notes with stripspace behavior'\n+\n+. ./test-lib.sh\n+\n+MULTI_LF=\"$LF$LF$LF\"\n+write_script fake_editor <<\\EOF\n+echo \"$MSG\" >\"$1\"\n+echo \"$MSG\" >&2\n+EOF\n+GIT_EDITOR=./fake_editor\n+export GIT_EDITOR\n+\n+test_expect_success 'setup the commit' '\n+\ttest_commit 1st\n+'\n+\n+test_expect_success 'add note by editor' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes add  &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying single \"-m\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tgit notes add -m \"${LF}first-line${MULTI_LF}second-line${LF}\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying multiple \"-m\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tgit notes add -m \"${LF}\" \\\n+\t\t      -m \"first-line\" \\\n+\t\t      -m \"${MULTI_LF}\" \\\n+\t\t      -m \"second-line\" \\\n+\t\t      -m \"${LF}\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+\n+test_expect_success 'append note by editor' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tgit notes add -m \"first-line\" &&\n+\tMSG=\"${MULTI_LF}second-line${LF}\" git notes append  &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append note by specifying single \"-m\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tgit notes add -m \"${LF}first-line\" &&\n+\tgit notes append -m \"${MULTI_LF}second-line${LF}\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append note by specifying multiple \"-m\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tgit notes add -m \"${LF}first-line\" &&\n+\tgit notes append -m \"${MULTI_LF}\" \\\n+\t\t      -m \"second-line\" \\\n+\t\t      -m \"${LF}\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying single \"-F\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tcat >note-file <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add -F note-file &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add notes by specifying multiple \"-F\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfile-1-first-line\n+\n+\tfile-1-second-line\n+\n+\tfile-2-first-line\n+\n+\tfile-2-second-line\n+\tEOF\n+\n+\tcat >note-file-1 <<-EOF &&\n+\t${LF}\n+\tfile-1-first-line\n+\t${MULTI_LF}\n+\tfile-1-second-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file-2 <<-EOF &&\n+\t${LF}\n+\tfile-2-first-line\n+\t${MULTI_LF}\n+\tfile-2-second-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add -F note-file-1 -F note-file-2 &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append note by specifying single \"-F\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tinitial-line\n+\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tcat >note-file <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add -m \"initial-line\" &&\n+\tgit notes append -F note-file &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append notes by specifying multiple \"-F\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tinitial-line\n+\n+\tfile-1-first-line\n+\n+\tfile-1-second-line\n+\n+\tfile-2-first-line\n+\n+\tfile-2-second-line\n+\tEOF\n+\n+\tcat >note-file-1 <<-EOF &&\n+\t${LF}\n+\tfile-1-first-line\n+\t${MULTI_LF}\n+\tfile-1-second-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file-2 <<-EOF &&\n+\t${LF}\n+\tfile-2-first-line\n+\t${MULTI_LF}\n+\tfile-2-second-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add -m \"initial-line\" &&\n+\tgit notes append -F note-file-1 -F note-file-2 &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add notes with empty messages' '\n+\trev=$(git rev-parse HEAD) &&\n+\tgit notes add -m \"${LF}\" \\\n+\t\t      -m \"${MULTI_LF}\" \\\n+\t\t      -m \"${LF}\" >actual 2>&1 &&\n+\ttest_i18ngrep \"Removing note for object\" actual\n+'\n+\n+test_expect_success 'add note by specifying \"-C\" , do not stripspace is the default behavior' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tcat expect | git hash-object -w --stdin >blob &&\n+\tgit notes add -C $(cat blob) &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add notes with \"-C\" and \"-m\", \"-m\" will stripspace all together' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >data <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\n+\tthird-line\n+\tEOF\n+\n+\tcat data | git hash-object -w --stdin >blob &&\n+\tgit notes add -C $(cat blob) -m \"third-line\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add notes with \"-m\" and \"-C\", \"-C\" will not stripspace all together' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >data <<-EOF &&\n+\n+\tsecond-line\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\t${LF}\n+\tsecond-line\n+\tEOF\n+\n+\tcat data | git hash-object -w --stdin >blob &&\n+\tgit notes add -m \"first-line\" -C $(cat blob)  &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \n2.40.0.358.g2947072e.dirty\n\n"},{"id":"476262","messageId":"ed930ef4f795f30792bc14d9c1939484e4976db8.1682671758.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1682671758.git.dyroneteng@gmail.com","subject":"[PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-04-28T09:23:32Z","receivedAt":"2023-04-28T09:24:05Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\nWhen adding new notes or appending to an existing notes, we will\ninsert a blank line between the paragraphs, like:\n\n     $ git notes add -m foo -m bar\n     $ git notes show HEAD\n     foo\n\n     bar\n\nThe default behavour sometimes is not enough, the user may want\nto use a custom delimiter between paragraphs, like when\nspecifying '-m', '-F', '-C', '-c' options. So this commit\nintroduce a new '--separator' option for 'git notes add' and\n'git notes append', for example when executing:\n\n    $ git notes add -m foo -m bar --separator=\"-\"\n    $ git notes show HEAD\n    foo\n    -\n    bar\n\na newline is added to the value given to --separator if it\ndoes not end with one already. So when executing:\n\n      $ git notes add -m foo -m bar --separator=\"-\"\nand\n      $ export LF=\"\n      \"\n      $ git notes add -m foo -m bar --separator=\"-$LF\"\n\nBoth the two exections produce the same result.\n\nThe reason we use a \"strbuf\" array to concat but not \"string_list\", is\nthat the binary file content may contain '\\0' in the middle, this will\ncause the corrupt result if using a string to save.\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n Documentation/git-notes.txt |  21 ++++--\n builtin/notes.c             | 108 ++++++++++++++++++++++++-------\n t/t3301-notes.sh            | 126 ++++++++++++++++++++++++++++++++++++\n 3 files changed, 226 insertions(+), 29 deletions(-)\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex efbc10f0..59980b21 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -9,9 +9,9 @@ SYNOPSIS\n --------\n [verse]\n 'git notes' [list [<object>]]\n-'git notes' add [-f] [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n+'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n 'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )\n-'git notes' append [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n+'git notes' append [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n 'git notes' edit [--allow-empty] [<object>]\n 'git notes' show [<object>]\n 'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>\n@@ -65,7 +65,9 @@ add::\n \tHowever, if you're using `add` interactively (using an editor\n \tto supply the notes contents), then - instead of aborting -\n \tthe existing notes will be opened in the editor (like the `edit`\n-\tsubcommand).\n+\tsubcommand). If you specify multiple `-m` and `-F`, a blank\n+\tline will be inserted between the messages. Use the `--separator`\n+\toption to insert other delimiters.\n \n copy::\n \tCopy the notes for the first object onto the second object (defaults to\n@@ -85,8 +87,12 @@ corresponding <to-object>.  (The optional `<rest>` is ignored so that\n the command can read the input given to the `post-rewrite` hook.)\n \n append::\n-\tAppend to the notes of an existing object (defaults to HEAD).\n-\tCreates a new notes object if needed.\n+\tAppend new message(s) given by `-m` or `-F` options to an\n+\texisting note, or add them as a new note if one does not\n+\texist, for the object (defaults to HEAD).  When appending to\n+\tan existing note, a blank line is added before each new\n+\tmessage as an inter-paragraph separator.  The separator can\n+\tbe customized with the `--separator` option.\n \n edit::\n \tEdit the notes for a given object (defaults to HEAD).\n@@ -159,6 +165,11 @@ OPTIONS\n \tAllow an empty note object to be stored. The default behavior is\n \tto automatically remove empty notes.\n \n+--separator <paragraph-break>::\n+\tSpecify a string used as a custom inter-paragraph separator\n+\t(a newline is added at the end as needed).  Defaults to a\n+\tblank line.\n+\n --ref <ref>::\n \tManipulate the notes tree in <ref>.  This overrides\n \t`GIT_NOTES_REF` and the \"core.notesRef\" configuration.  The ref\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 9d8ca795..3215bce1 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -8,6 +8,7 @@\n  */\n \n #include \"cache.h\"\n+#include \"alloc.h\"\n #include \"config.h\"\n #include \"builtin.h\"\n #include \"gettext.h\"\n@@ -27,11 +28,12 @@\n #include \"worktree.h\"\n #include \"write-or-die.h\"\n \n+static char *separator = \"\\n\";\n static const char * const git_notes_usage[] = {\n \tN_(\"git notes [--ref <notes-ref>] [list [<object>]]\"),\n-\tN_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n+\tN_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n \tN_(\"git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>\"),\n-\tN_(\"git notes [--ref <notes-ref>] append [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n+\tN_(\"git notes [--ref <notes-ref>] append [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n \tN_(\"git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]\"),\n \tN_(\"git notes [--ref <notes-ref>] show [<object>]\"),\n \tN_(\"git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>\"),\n@@ -99,11 +101,19 @@ static const char * const git_notes_get_ref_usage[] = {\n static const char note_template[] =\n \tN_(\"Write/edit the notes for the following object:\");\n \n+struct note_msg {\n+\tint stripspace;\n+\tstruct strbuf buf;\n+};\n+\n struct note_data {\n \tint given;\n \tint use_editor;\n \tchar *edit_path;\n \tstruct strbuf buf;\n+\tstruct note_msg **messages;\n+\tsize_t msg_nr;\n+\tsize_t msg_alloc;\n };\n \n static void free_note_data(struct note_data *d)\n@@ -113,6 +123,12 @@ static void free_note_data(struct note_data *d)\n \t\tfree(d->edit_path);\n \t}\n \tstrbuf_release(&d->buf);\n+\n+\twhile (d->msg_nr--) {\n+\t\tstrbuf_release(&d->messages[d->msg_nr]->buf);\n+\t\tfree(d->messages[d->msg_nr]);\n+\t}\n+\tfree(d->messages);\n }\n \n static int list_each_note(const struct object_id *object_oid,\n@@ -213,65 +229,96 @@ static void write_note_data(struct note_data *d, struct object_id *oid)\n \t}\n }\n \n+static void insert_separator(struct strbuf *message, size_t pos)\n+{\n+\tif (separator[strlen(separator) - 1] == '\\n')\n+\t\tstrbuf_addstr(message, separator);\n+\telse\n+\t\tstrbuf_insertf(message, pos, \"%s%s\", separator, \"\\n\");\n+}\n+\n+static void concat_messages(struct note_data *d)\n+{\n+\tstruct strbuf msg = STRBUF_INIT;\n+\n+\tsize_t i;\n+\tfor (i = 0; i < d->msg_nr ; i++) {\n+\t\tif (d->buf.len)\n+\t\t\tinsert_separator(&d->buf, d->buf.len);\n+\t\tstrbuf_add(&msg, d->messages[i]->buf.buf, d->messages[i]->buf.len);\n+\t\tstrbuf_addbuf(&d->buf, &msg);\n+\t\tif (d->messages[i]->stripspace)\n+\t\t\tstrbuf_stripspace(&d->buf, 0);\n+\t\tstrbuf_reset(&msg);\n+\t}\n+\tstrbuf_release(&msg);\n+}\n+\n static int parse_msg_arg(const struct option *opt, const char *arg, int unset)\n {\n \tstruct note_data *d = opt->value;\n+\tstruct note_msg *msg = xmalloc(sizeof(*msg));\n \n \tBUG_ON_OPT_NEG(unset);\n \n-\tif (d->buf.len)\n-\t\tstrbuf_addch(&d->buf, '\\n');\n-\tstrbuf_addstr(&d->buf, arg);\n-\tstrbuf_stripspace(&d->buf, 0);\n-\n-\td->given = 1;\n+\tstrbuf_init(&msg->buf, strlen(arg));\n+\tstrbuf_addstr(&msg->buf, arg);\n+\tALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);\n+\td->messages[d->msg_nr - 1] = msg;\n+\tmsg->stripspace = 1;\n \treturn 0;\n }\n \n static int parse_file_arg(const struct option *opt, const char *arg, int unset)\n {\n \tstruct note_data *d = opt->value;\n+\tstruct note_msg *msg = xmalloc(sizeof(*msg));\n \n \tBUG_ON_OPT_NEG(unset);\n \n-\tif (d->buf.len)\n-\t\tstrbuf_addch(&d->buf, '\\n');\n+\tstrbuf_init(&msg->buf , 0);\n \tif (!strcmp(arg, \"-\")) {\n-\t\tif (strbuf_read(&d->buf, 0, 1024) < 0)\n+\t\tif (strbuf_read(&msg->buf, 0, 1024) < 0)\n \t\t\tdie_errno(_(\"cannot read '%s'\"), arg);\n-\t} else if (strbuf_read_file(&d->buf, arg, 1024) < 0)\n+\t} else if (strbuf_read_file(&msg->buf, arg, 1024) < 0)\n \t\tdie_errno(_(\"could not open or read '%s'\"), arg);\n-\tstrbuf_stripspace(&d->buf, 0);\n \n-\td->given = 1;\n+\tALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);\n+\td->messages[d->msg_nr - 1] = msg;\n+\tmsg->stripspace = 1;\n \treturn 0;\n }\n \n static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)\n {\n \tstruct note_data *d = opt->value;\n-\tchar *buf;\n+\tstruct note_msg *msg = xmalloc(sizeof(*msg));\n+\tchar *value;\n \tstruct object_id object;\n \tenum object_type type;\n \tunsigned long len;\n \n \tBUG_ON_OPT_NEG(unset);\n \n-\tif (d->buf.len)\n-\t\tstrbuf_addch(&d->buf, '\\n');\n-\n+\tstrbuf_init(&msg->buf, 0);\n \tif (repo_get_oid(the_repository, arg, &object))\n \t\tdie(_(\"failed to resolve '%s' as a valid ref.\"), arg);\n-\tif (!(buf = repo_read_object_file(the_repository, &object, &type, &len)))\n+\tif (!(value = repo_read_object_file(the_repository, &object, &type, &len)))\n \t\tdie(_(\"failed to read object '%s'.\"), arg);\n \tif (type != OBJ_BLOB) {\n-\t\tfree(buf);\n+\t\tstrbuf_release(&msg->buf);\n+\t\tfree(value);\n+\t\tfree(msg);\n \t\tdie(_(\"cannot read note data from non-blob object '%s'.\"), arg);\n \t}\n-\tstrbuf_add(&d->buf, buf, len);\n-\tfree(buf);\n \n-\td->given = 1;\n+\tstrbuf_add(&msg->buf, value, len);\n+\tfree(value);\n+\n+\tmsg->buf.len = len;\n+\tALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);\n+\td->messages[d->msg_nr - 1] = msg;\n+\tmsg->stripspace = 0;\n \treturn 0;\n }\n \n@@ -406,6 +453,7 @@ static int add(int argc, const char **argv, const char *prefix)\n \tstruct object_id object, new_note;\n \tconst struct object_id *note;\n \tstruct note_data d = { .buf = STRBUF_INIT };\n+\n \tstruct option options[] = {\n \t\tOPT_CALLBACK_F('m', \"message\", &d, N_(\"message\"),\n \t\t\tN_(\"note contents as a string\"), PARSE_OPT_NONEG,\n@@ -422,6 +470,8 @@ static int add(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"allow-empty\", &allow_empty,\n \t\t\tN_(\"allow storing empty note\")),\n \t\tOPT__FORCE(&force, N_(\"replace existing notes\"), PARSE_OPT_NOCOMPLETE),\n+\t\tOPT_STRING(0, \"separator\", &separator, N_(\"separator\"),\n+\t\t\tN_(\"insert <paragraph-break> between paragraphs\")),\n \t\tOPT_END()\n \t};\n \n@@ -433,6 +483,10 @@ static int add(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(git_notes_add_usage, options);\n \t}\n \n+\tif (d.msg_nr)\n+\t\tconcat_messages(&d);\n+\td.given = !!d.buf.len;\n+\n \tobject_ref = argc > 1 ? argv[1] : \"HEAD\";\n \n \tif (repo_get_oid(the_repository, object_ref, &object))\n@@ -587,6 +641,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \t\t\tparse_reuse_arg),\n \t\tOPT_BOOL(0, \"allow-empty\", &allow_empty,\n \t\t\tN_(\"allow storing empty note\")),\n+\t\tOPT_STRING(0, \"separator\", &separator, N_(\"separator\"),\n+\t\t\tN_(\"insert <paragraph-break> between paragraphs\")),\n \t\tOPT_END()\n \t};\n \tint edit = !strcmp(argv[0], \"edit\");\n@@ -600,6 +656,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(usage, options);\n \t}\n \n+\tif (d.msg_nr)\n+\t\tconcat_messages(&d);\n+\td.given = !!d.buf.len;\n+\n \tif (d.given && edit)\n \t\tfprintf(stderr, _(\"The -m/-F/-c/-C options have been deprecated \"\n \t\t\t\"for the 'edit' subcommand.\\n\"\n@@ -623,7 +683,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \t\t\t\t\t\t       &type, &size);\n \n \t\tif (d.buf.len && prev_buf && size)\n-\t\t\tstrbuf_insertstr(&d.buf, 0, \"\\n\");\n+\t\t\tinsert_separator(&d.buf, 0);\n \t\tif (prev_buf && size)\n \t\t\tstrbuf_insert(&d.buf, 0, prev_buf, size);\n \t\tfree(prev_buf);\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex 3288aaec..dbadcf13 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -362,6 +362,7 @@ test_expect_success 'do not create empty note with -m \"\"' '\n '\n \n test_expect_success 'create note with combination of -m and -F' '\n+\ttest_when_finished git notes remove HEAD &&\n \tcat >expect-combine_m_and_F <<-EOF &&\n \t\tfoo\n \n@@ -380,6 +381,25 @@ test_expect_success 'create note with combination of -m and -F' '\n \ttest_cmp expect-combine_m_and_F actual\n '\n \n+test_expect_success 'create note with combination of -m and -F and --separator' '\n+\tcat >expect-combine_m_and_F <<-\\EOF &&\n+\tfoo\n+\t-------\n+\txyzzy\n+\t-------\n+\tbar\n+\t-------\n+\tzyxxy\n+\t-------\n+\tbaz\n+\tEOF\n+\techo \"xyzzy\" >note_a &&\n+\techo \"zyxxy\" >note_b &&\n+\tgit notes add -m \"foo\" -F note_a -m \"bar\" -F note_b -m \"baz\" --separator \"-------\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect-combine_m_and_F actual\n+'\n+\n test_expect_success 'remove note with \"git notes remove\"' '\n \tgit notes remove HEAD^ &&\n \tgit notes remove &&\n@@ -521,6 +541,85 @@ test_expect_success 'listing non-existing notes fails' '\n \ttest_must_be_empty actual\n '\n \n+test_expect_success 'append: specify an empty separator' '\n+\ttest_when_finished git notes remove HEAD &&\n+\tcat >expect <<-\\EOF &&\n+\tnotes-1\n+\n+\tnotes-2\n+\tEOF\n+\n+\tgit notes add -m \"notes-1\" &&\n+\tgit notes append --separator=\"\" -m \"notes-2\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append: specify separator with line break' '\n+\ttest_when_finished git notes remove HEAD &&\n+\tcat >expect <<-\\EOF &&\n+\tnotes-1\n+\t-------\n+\tnotes-2\n+\tEOF\n+\n+\tgit notes add -m \"notes-1\" &&\n+\tgit notes append --separator=\"-------$LF\" -m \"notes-2\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append: specify separator without line break' '\n+\ttest_when_finished git notes remove HEAD &&\n+\tcat >expect <<-\\EOF &&\n+\tnotes-1\n+\t-------\n+\tnotes-2\n+\tEOF\n+\n+\tgit notes add -m \"notes-1\" &&\n+\tgit notes append --separator=\"-------\" -m \"notes-2\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append: specify separator with multiple messages' '\n+\ttest_when_finished git notes remove HEAD &&\n+\tcat >expect <<-\\EOF &&\n+\tnotes-1\n+\t-------\n+\tnotes-2\n+\t-------\n+\tnotes-3\n+\tEOF\n+\n+\tgit notes add -m \"notes-1\" &&\n+\tgit notes append --separator=\"-------\" -m \"notes-2\" -m \"notes-3\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append note with combination of -m and -F and --separator' '\n+\ttest_when_finished git notes remove HEAD &&\n+\tcat >expect-combine_m_and_F <<-\\EOF &&\n+\tm-notes-1\n+\t-------\n+\tf-notes-1\n+\t-------\n+\tm-notes-2\n+\t-------\n+\tf-notes-2\n+\t-------\n+\tm-notes-3\n+\tEOF\n+\n+\techo \"f-notes-1\" >note_a &&\n+\techo \"f-notes-2\" >note_b &&\n+\tgit notes append -m \"m-notes-1\" -F note_a -m \"m-notes-2\" -F note_b -m \"m-notes-3\" --separator \"-------\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect-combine_m_and_F actual\n+'\n+\n test_expect_success 'append to existing note with \"git notes append\"' '\n \tcat >expect <<-EOF &&\n \t\tInitial set of notes\n@@ -818,6 +917,33 @@ test_expect_success 'create note from blob with \"git notes add -C\" reuses blob i\n \ttest_cmp blob actual\n '\n \n+test_expect_success 'create note from blob with \"-C\", also specify \"-m\", \"-F\" and \"--separator\"' '\n+\t# 8th will be reuseed in following tests, so rollback when the test is done\n+\ttest_when_finished \"git notes remove && git notes add -C $(cat blob)\" &&\n+\tcommit=$(git rev-parse HEAD) &&\n+\tcat >expect <<-EOF &&\n+\t\tcommit $commit\n+\t\tAuthor: A U Thor <author@example.com>\n+\t\tDate:   Thu Apr 7 15:20:13 2005 -0700\n+\n+\t\t${indent}8th\n+\n+\t\tNotes:\n+\t\t${indent}This is a blob object\n+\t\t${indent}-------\n+\t\t${indent}This is created by -m\n+\t\t${indent}-------\n+\t\t${indent}This is created by -F\n+\tEOF\n+\n+\tgit notes remove &&\n+\techo \"This is a blob object\" | git hash-object -w --stdin >blob &&\n+\techo \"This is created by -F\" >note_a &&\n+\tgit notes add -C $(cat blob) -m \"This is created by -m\" -F note_a --separator=\"-------\" &&\n+\tgit log -1 >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'create note from other note with \"git notes add -c\"' '\n \ttest_commit 9th &&\n \tcommit=$(git rev-parse HEAD) &&\n-- \n2.40.0.358.g2947072e.dirty\n\n"},{"id":"476263","messageId":"eea2246f44a3adfc4888db93975854448271032b.1682671758.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1682671758.git.dyroneteng@gmail.com","subject":"[PATCH v9 5/6] notes.c: append separator instead of insert by pos","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-04-28T09:23:33Z","receivedAt":"2023-04-28T09:24:08Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\nRename \"insert_separator\" to \"append_separator\" and also remove the\n\"postion\" argument, this serves two purpose:\n\nThe first is that when specifying more than one \"-m\" ( like \"-F\", etc)\nto \"git notes add\" or \"git notes append\", the order of them matters,\nwhich means we need to append the each separator and message in turn,\nso we don't have to make the caller specify the position, the \"append\"\noperation is enough and clear.\n\nThe second is that when we execute the \"git notes append\" subcommand,\nwe need to combine the \"prev_note\" and \"current_note\" to get the\nfinal result. Before, we inserted a newline character at the beginning\nof \"current_note\". Now, we will append a newline to the end of\n\"prev_note\" instead, this will give the consisitent results.\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n builtin/notes.c | 19 +++++++++++--------\n 1 file changed, 11 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 3215bce1..e32f2453 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -229,12 +229,12 @@ static void write_note_data(struct note_data *d, struct object_id *oid)\n \t}\n }\n \n-static void insert_separator(struct strbuf *message, size_t pos)\n+static void append_separator(struct strbuf *message)\n {\n \tif (separator[strlen(separator) - 1] == '\\n')\n \t\tstrbuf_addstr(message, separator);\n \telse\n-\t\tstrbuf_insertf(message, pos, \"%s%s\", separator, \"\\n\");\n+\t\tstrbuf_addf(message, \"%s%s\", separator, \"\\n\");\n }\n \n static void concat_messages(struct note_data *d)\n@@ -244,7 +244,7 @@ static void concat_messages(struct note_data *d)\n \tsize_t i;\n \tfor (i = 0; i < d->msg_nr ; i++) {\n \t\tif (d->buf.len)\n-\t\t\tinsert_separator(&d->buf, d->buf.len);\n+\t\t\tappend_separator(&d->buf);\n \t\tstrbuf_add(&msg, d->messages[i]->buf.buf, d->messages[i]->buf.len);\n \t\tstrbuf_addbuf(&d->buf, &msg);\n \t\tif (d->messages[i]->stripspace)\n@@ -679,14 +679,17 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \t\t/* Append buf to previous note contents */\n \t\tunsigned long size;\n \t\tenum object_type type;\n-\t\tchar *prev_buf = repo_read_object_file(the_repository, note,\n-\t\t\t\t\t\t       &type, &size);\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tchar *prev_buf = repo_read_object_file(the_repository, note, &type, &size);\n \n-\t\tif (d.buf.len && prev_buf && size)\n-\t\t\tinsert_separator(&d.buf, 0);\n \t\tif (prev_buf && size)\n-\t\t\tstrbuf_insert(&d.buf, 0, prev_buf, size);\n+\t\t\tstrbuf_add(&buf, prev_buf, size);\n+\t\tif (d.buf.len && prev_buf && size)\n+\t\t\tappend_separator(&buf);\n+\t\tstrbuf_insert(&d.buf, 0, buf.buf, buf.len);\n+\n \t\tfree(prev_buf);\n+\t\tstrbuf_release(&buf);\n \t}\n \n \tif (d.buf.len || allow_empty) {\n-- \n2.40.0.358.g2947072e.dirty\n\n"},{"id":"476264","messageId":"20063beab1893535f03e13b75058d501ed9f2faa.1682671758.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1682671758.git.dyroneteng@gmail.com","subject":"[PATCH v9 6/6] notes.c: introduce \"--[no-]stripspace\" option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-04-28T09:23:34Z","receivedAt":"2023-04-28T09:24:25Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\nThis commit introduces a new option \"--[no-]stripspace\" to git notes\nappend, git notes edit, and git notes add. This option allows users to\ncontrol whether the note message need to stripped out.\n\nFor the consideration of backward compatibility, let's look at the\nbehavior about \"stripspace\" in \"git notes\" command:\n\n1. \"Edit Message\" case: using the default editor to edit the note\nmessage.\n\n    In \"edit\" case, the edited message will always be stripped out, the\n    implementation which can be found in the \"prepare_note_data()\". In\n    addition, the \"-c\" option supports to reuse an existing blob as a\n    note message, then open the editor to make a further edition on it,\n    the edited message will be stripped.\n\n    This commit doesn't change the default behavior of \"edit\" case by\n    using an enum \"notes_stripspace\", only when \"--no-stripspace\" option\n    is specified, the note message will not be stripped out. If you do\n    not specify the option or you specify \"--stripspace\", clearly, the\n    note message will be stripped out.\n\n2. \"Assign Message\" case: using the \"-m\"/\"-F\"/\"-C\" option to specify the\nnote message.\n\n    In \"assign\" case, when specify message by \"-m\" or \"-F\", the message\n    will be stripped out by default, but when specify message by \"-C\",\n    the message will be copied verbatim, in other word, the message will\n    not be stripped out. One more thing need to note is \"the order of\n    the options matter\", that is, if you specify \"-C\" before \"-m\" or\n    \"-F\", the reused message by \"-C\" will be stripped out together,\n    because everytime concat \"-m\" or \"-F\" message, the concated message\n    will be stripped together. Oppositely, if you specify \"-m\" or \"-F\"\n    before \"-C\", the reused message by \"-C\" will not be stripped out.\n\n    This commit doesn't change the default behavior of \"assign\" case by\n    extending the \"stripspace\" field in \"struct note_msg\", so we can\n    distinguish the different behavior of \"-m\"/\"-F\" and \"-C\" options\n    when we need to parse and concat the message.\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n Documentation/git-notes.txt |  25 ++-\n builtin/notes.c             |  30 +++-\n t/t3321-notes-stripspace.sh | 296 +++++++++++++++++++++++++++++++++++-\n 3 files changed, 332 insertions(+), 19 deletions(-)\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex 59980b21..5f3a9479 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -9,10 +9,10 @@ SYNOPSIS\n --------\n [verse]\n 'git notes' [list [<object>]]\n-'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n+'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n 'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )\n-'git notes' append [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n-'git notes' edit [--allow-empty] [<object>]\n+'git notes' append [--allow-empty] [--separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n+'git notes' edit [--allow-empty] [<object>] [--[no-]stripspace]\n 'git notes' show [<object>]\n 'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>\n 'git notes' merge --commit [-v | -q]\n@@ -141,20 +141,26 @@ OPTIONS\n \tIf multiple `-m` options are given, their values\n \tare concatenated as separate paragraphs.\n \tLines starting with `#` and empty lines other than a\n-\tsingle line between paragraphs will be stripped out.\n+\tsingle line between paragraphs will be stripped out,\n+\tif you wish to keep them verbatim, use `--no-stripspace`.\n \n -F <file>::\n --file=<file>::\n \tTake the note message from the given file.  Use '-' to\n \tread the note message from the standard input.\n \tLines starting with `#` and empty lines other than a\n-\tsingle line between paragraphs will be stripped out.\n+\tsingle line between paragraphs will be stripped out,\n+\tif you wish to keep them verbatim, use with\n+\t`--no-stripspace` option.\n \n -C <object>::\n --reuse-message=<object>::\n \tTake the given blob object (for example, another note) as the\n \tnote message. (Use `git notes copy <object>` instead to\n-\tcopy notes between objects.)\n+\tcopy notes between objects.).  By default, message will be\n+\tcopied verbatim, but if you wish to strip out the lines\n+\tstarting with `#` and empty lines other than a single line\n+\tbetween paragraphs, use with`--stripspace` option.\n \n -c <object>::\n --reedit-message=<object>::\n@@ -170,6 +176,13 @@ OPTIONS\n \t(a newline is added at the end as needed).  Defaults to a\n \tblank line.\n \n+--[no-]stripspace::\n+\tStrip leading and trailing whitespace from the note message.\n+\tAlso strip out empty lines other than a single line between\n+\tparagraphs. For lines starting with `#` will be stripped out\n+\tin non-editor cases like \"-m\", \"-F\" and \"-C\", but not in\n+\teditor case like \"git notes edit\", \"-c\", etc.\n+\n --ref <ref>::\n \tManipulate the notes tree in <ref>.  This overrides\n \t`GIT_NOTES_REF` and the \"core.notesRef\" configuration.  The ref\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex e32f2453..6eede305 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -101,14 +101,21 @@ static const char * const git_notes_get_ref_usage[] = {\n static const char note_template[] =\n \tN_(\"Write/edit the notes for the following object:\");\n \n+enum notes_stripspace {\n+\tUNSPECIFIED = -1,\n+\tNO_STRIPSPACE = 0,\n+\tSTRIPSPACE = 1,\n+};\n+\n struct note_msg {\n-\tint stripspace;\n+\tenum notes_stripspace stripspace;\n \tstruct strbuf buf;\n };\n \n struct note_data {\n \tint given;\n \tint use_editor;\n+\tint stripspace;\n \tchar *edit_path;\n \tstruct strbuf buf;\n \tstruct note_msg **messages;\n@@ -213,7 +220,8 @@ static void prepare_note_data(const struct object_id *object, struct note_data *\n \t\tif (launch_editor(d->edit_path, &d->buf, NULL)) {\n \t\t\tdie(_(\"please supply the note contents using either -m or -F option\"));\n \t\t}\n-\t\tstrbuf_stripspace(&d->buf, 1);\n+\t\tif (d->stripspace)\n+\t\t\tstrbuf_stripspace(&d->buf, 1);\n \t}\n }\n \n@@ -247,7 +255,9 @@ static void concat_messages(struct note_data *d)\n \t\t\tappend_separator(&d->buf);\n \t\tstrbuf_add(&msg, d->messages[i]->buf.buf, d->messages[i]->buf.len);\n \t\tstrbuf_addbuf(&d->buf, &msg);\n-\t\tif (d->messages[i]->stripspace)\n+\t\tif ((d->stripspace == UNSPECIFIED &&\n+\t\t     d->messages[i]->stripspace == STRIPSPACE) ||\n+\t\t    d->stripspace == STRIPSPACE)\n \t\t\tstrbuf_stripspace(&d->buf, 0);\n \t\tstrbuf_reset(&msg);\n \t}\n@@ -265,7 +275,7 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)\n \tstrbuf_addstr(&msg->buf, arg);\n \tALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);\n \td->messages[d->msg_nr - 1] = msg;\n-\tmsg->stripspace = 1;\n+\tmsg->stripspace = STRIPSPACE;\n \treturn 0;\n }\n \n@@ -285,7 +295,7 @@ static int parse_file_arg(const struct option *opt, const char *arg, int unset)\n \n \tALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);\n \td->messages[d->msg_nr - 1] = msg;\n-\tmsg->stripspace = 1;\n+\tmsg->stripspace = STRIPSPACE;\n \treturn 0;\n }\n \n@@ -318,7 +328,7 @@ static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)\n \tmsg->buf.len = len;\n \tALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);\n \td->messages[d->msg_nr - 1] = msg;\n-\tmsg->stripspace = 0;\n+\tmsg->stripspace = NO_STRIPSPACE;\n \treturn 0;\n }\n \n@@ -452,7 +462,7 @@ static int add(int argc, const char **argv, const char *prefix)\n \tstruct notes_tree *t;\n \tstruct object_id object, new_note;\n \tconst struct object_id *note;\n-\tstruct note_data d = { .buf = STRBUF_INIT };\n+\tstruct note_data d = { .buf = STRBUF_INIT, .stripspace = UNSPECIFIED };\n \n \tstruct option options[] = {\n \t\tOPT_CALLBACK_F('m', \"message\", &d, N_(\"message\"),\n@@ -472,6 +482,8 @@ static int add(int argc, const char **argv, const char *prefix)\n \t\tOPT__FORCE(&force, N_(\"replace existing notes\"), PARSE_OPT_NOCOMPLETE),\n \t\tOPT_STRING(0, \"separator\", &separator, N_(\"separator\"),\n \t\t\tN_(\"insert <paragraph-break> between paragraphs\")),\n+\t\tOPT_BOOL(0, \"stripspace\", &d.stripspace,\n+\t\t\tN_(\"remove unnecessary whitespace\")),\n \t\tOPT_END()\n \t};\n \n@@ -625,7 +637,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \tconst struct object_id *note;\n \tchar *logmsg;\n \tconst char * const *usage;\n-\tstruct note_data d = { .buf = STRBUF_INIT };\n+\tstruct note_data d = { .buf = STRBUF_INIT, .stripspace = UNSPECIFIED };\n \tstruct option options[] = {\n \t\tOPT_CALLBACK_F('m', \"message\", &d, N_(\"message\"),\n \t\t\tN_(\"note contents as a string\"), PARSE_OPT_NONEG,\n@@ -643,6 +655,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"allow storing empty note\")),\n \t\tOPT_STRING(0, \"separator\", &separator, N_(\"separator\"),\n \t\t\tN_(\"insert <paragraph-break> between paragraphs\")),\n+\t\tOPT_BOOL(0, \"stripspace\", &d.stripspace,\n+\t\t\tN_(\"remove unnecessary whitespace\")),\n \t\tOPT_END()\n \t};\n \tint edit = !strcmp(argv[0], \"edit\");\ndiff --git a/t/t3321-notes-stripspace.sh b/t/t3321-notes-stripspace.sh\nindex 89977873..028d825e 100755\n--- a/t/t3321-notes-stripspace.sh\n+++ b/t/t3321-notes-stripspace.sh\n@@ -32,7 +32,7 @@ test_expect_success 'add note by editor' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'add note by specifying single \"-m\"' '\n+test_expect_success 'add note by specifying single \"-m\", \"--stripspace\" is the default behavior' '\n \ttest_when_finished \"git notes remove\" &&\n \tcat >expect <<-EOF &&\n \tfirst-line\n@@ -42,10 +42,26 @@ test_expect_success 'add note by specifying single \"-m\"' '\n \n \tgit notes add -m \"${LF}first-line${MULTI_LF}second-line${LF}\" &&\n \tgit notes show >actual &&\n+\ttest_cmp expect actual &&\n+\tgit notes remove &&\n+\tgit notes add --stripspace -m \"${LF}first-line${MULTI_LF}second-line${LF}\" &&\n+\tgit notes show >actual &&\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'add note by specifying multiple \"-m\"' '\n+test_expect_success 'add note by specifying single \"-m\" and \"--no-stripspace\" ' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\t${LF}first-line${MULTI_LF}second-line\n+\tEOF\n+\n+\tgit notes add --no-stripspace \\\n+\t\t      -m \"${LF}first-line${MULTI_LF}second-line${LF}\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying multiple \"-m\", \"--stripspace\" is the default behavior' '\n \ttest_when_finished \"git notes remove\" &&\n \tcat >expect <<-EOF &&\n \tfirst-line\n@@ -59,9 +75,156 @@ test_expect_success 'add note by specifying multiple \"-m\"' '\n \t\t      -m \"second-line\" \\\n \t\t      -m \"${LF}\" &&\n \tgit notes show >actual &&\n+\ttest_cmp expect actual &&\n+\tgit notes remove &&\n+\tgit notes add --stripspace -m \"${LF}\" \\\n+\t\t      -m \"first-line\" \\\n+\t\t      -m \"${MULTI_LF}\" \\\n+\t\t      -m \"second-line\" \\\n+\t\t      -m \"${LF}\" &&\n+\tgit notes show >actual &&\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'add notes by specifying multiple \"-m\" and \"--no-stripspace\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line${LF}\n+\tEOF\n+\n+\tgit notes add --no-stripspace \\\n+\t\t      -m \"${LF}\" \\\n+\t\t      -m \"first-line\" \\\n+\t\t      -m \"${MULTI_LF}\" \\\n+\t\t      -m \"second-line\" \\\n+\t\t      -m \"${LF}\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying single \"-F\", \"--stripspace\" is the default behavior' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tcat >note-file <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add -F note-file &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual &&\n+\tgit notes remove &&\n+\tgit notes add --stripspace -F note-file &&\n+\tgit notes show >actual\n+'\n+\n+test_expect_success 'add note by specifying single \"-F\" and \"--no-stripspace\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add --no-stripspace -F note-file &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying multiple \"-F\", \"--stripspace\" is the default behavior' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfile-1-first-line\n+\n+\tfile-1-second-line\n+\n+\tfile-2-first-line\n+\n+\tfile-2-second-line\n+\tEOF\n+\n+\tcat >note-file-1 <<-EOF &&\n+\t${LF}\n+\tfile-1-first-line\n+\t${MULTI_LF}\n+\tfile-1-second-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file-2 <<-EOF &&\n+\t${LF}\n+\tfile-2-first-line\n+\t${MULTI_LF}\n+\tfile-2-second-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add -F note-file-1 -F note-file-2 &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual &&\n+\tgit notes remove &&\n+\tgit notes add --stripspace -F note-file-1 -F note-file-2 &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying multiple \"-F\" with \"--no-stripspace\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\t${LF}\n+\tfile-1-first-line\n+\t${MULTI_LF}\n+\tfile-1-second-line\n+\t${LF}\n+\n+\t${LF}\n+\tfile-2-first-line\n+\t${MULTI_LF}\n+\tfile-2-second-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file-1 <<-EOF &&\n+\t${LF}\n+\tfile-1-first-line\n+\t${MULTI_LF}\n+\tfile-1-second-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file-2 <<-EOF &&\n+\t${LF}\n+\tfile-2-first-line\n+\t${MULTI_LF}\n+\tfile-2-second-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add --no-stripspace -F note-file-1 -F note-file-2 &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n \n test_expect_success 'append note by editor' '\n \ttest_when_finished \"git notes remove\" &&\n@@ -221,6 +384,45 @@ test_expect_success 'append notes by specifying multiple \"-F\"' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'append note by specifying multiple \"-F\" with \"--no-stripspace\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tinitial-line\n+\t${LF}${LF}\n+\tfile-1-first-line\n+\t${MULTI_LF}\n+\tfile-1-second-line\n+\t${LF}\n+\n+\t${LF}\n+\tfile-2-first-line\n+\t${MULTI_LF}\n+\tfile-2-second-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file-1 <<-EOF &&\n+\t${LF}\n+\tfile-1-first-line\n+\t${MULTI_LF}\n+\tfile-1-second-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file-2 <<-EOF &&\n+\t${LF}\n+\tfile-2-first-line\n+\t${MULTI_LF}\n+\tfile-2-second-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add -m \"initial-line\" &&\n+\tgit notes append --no-stripspace -F note-file-1 -F note-file-2 &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'add notes with empty messages' '\n \trev=$(git rev-parse HEAD) &&\n \tgit notes add -m \"${LF}\" \\\n@@ -229,7 +431,7 @@ test_expect_success 'add notes with empty messages' '\n \ttest_i18ngrep \"Removing note for object\" actual\n '\n \n-test_expect_success 'add note by specifying \"-C\" , do not stripspace is the default behavior' '\n+test_expect_success 'add note by specifying \"-C\", \"--no-stripspace\" is the default behavior' '\n \ttest_when_finished \"git notes remove\" &&\n \tcat >expect <<-EOF &&\n \t${LF}\n@@ -242,10 +444,36 @@ test_expect_success 'add note by specifying \"-C\" , do not stripspace is the defa\n \tcat expect | git hash-object -w --stdin >blob &&\n \tgit notes add -C $(cat blob) &&\n \tgit notes show >actual &&\n+\ttest_cmp expect actual &&\n+\tgit notes remove &&\n+\tgit notes add --no-stripspace -C $(cat blob) &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'reuse note by specifying \"-C\" and \"--stripspace\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >data <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tcat data | git hash-object -w --stdin >blob &&\n+\tgit notes add --stripspace -C $(cat blob) &&\n+\tgit notes show >actual &&\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'add notes with \"-C\" and \"-m\", \"-m\" will stripspace all together' '\n+test_expect_success 'reuse with \"-C\" and add note with \"-m\", \"-m\" will stripspace all together' '\n \ttest_when_finished \"git notes remove\" &&\n \tcat >data <<-EOF &&\n \t${LF}\n@@ -269,7 +497,7 @@ test_expect_success 'add notes with \"-C\" and \"-m\", \"-m\" will stripspace all toge\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'add notes with \"-m\" and \"-C\", \"-C\" will not stripspace all together' '\n+test_expect_success 'add note with \"-m\" and reuse note with \"-C\", \"-C\" will not stripspace all together' '\n \ttest_when_finished \"git notes remove\" &&\n \tcat >data <<-EOF &&\n \n@@ -288,4 +516,62 @@ test_expect_success 'add notes with \"-m\" and \"-C\", \"-C\" will not stripspace all\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'add note by specifying \"-c\", \"--stripspace\" is the default behavior' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\techo \"initial-line\" | git hash-object -w --stdin >blob &&\n+\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes add -c $(cat blob) &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual &&\n+\tgit notes remove &&\n+\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes add --stripspace -c $(cat blob) &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying \"-c\" with \"--no-stripspace\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\t${LF}first-line${MULTI_LF}second-line${LF}\n+\tEOF\n+\n+\techo \"initial-line\" | git hash-object -w --stdin >blob &&\n+\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes add --no-stripspace -c $(cat blob) &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'edit note by specifying \"-c\", \"--stripspace\" is the default behavior' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes edit &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual &&\n+\tgit notes remove &&\n+\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes edit --stripspace &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'edit note by specifying \"-c\" with \"--no-stripspace\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\t${LF}first-line${MULTI_LF}second-line${LF}\n+\tEOF\n+\n+\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes add --no-stripspace &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.40.0.358.g2947072e.dirty\n\n"},{"id":"476294","messageId":"xmqqsfcjbuud.fsf@gitster.g","threadId":"59667","inReplyTo":"ed930ef4f795f30792bc14d9c1939484e4976db8.1682671758.git.dyroneteng@gmail.com","subject":"Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-04-28T20:44:10Z","receivedAt":"2023-04-28T20:44:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Teng Long <dyroneteng@gmail.com> writes:\n\n> +static char *separator = \"\\n\";\n\nThe only two ways this pointer gains a non-NULL value are with this\ninitialization and parsing the command line \"--separator=<value>\"\noption with OPT_STRING().  Neither of them allocate new storage but\npoints an existing string that we do not \"own\" (and cannot free)\nwith the pointer.  So it probably is safer to make it a pointer to a\nconst string, i.e.\n\n\tstatic const char *separator = \"\\n\";\n\n> @@ -213,65 +229,96 @@ static void write_note_data(struct note_data *d, struct object_id *oid)\n>  \t}\n>  }\n>  \n> +static void insert_separator(struct strbuf *message, size_t pos)\n> +{\n> +\tif (separator[strlen(separator) - 1] == '\\n')\n> +\t\tstrbuf_addstr(message, separator);\n> +\telse\n> +\t\tstrbuf_insertf(message, pos, \"%s%s\", separator, \"\\n\");\n> +}\n> +\n> +static void concat_messages(struct note_data *d)\n> +{\n> +\tstruct strbuf msg = STRBUF_INIT;\n> +\n> +\tsize_t i;\n> +\tfor (i = 0; i < d->msg_nr ; i++) {\n\nWrong placement of the blank line that separates the declaration and\nthe first statement.\n\n"},{"id":"476295","messageId":"xmqqmt2rbur4.fsf@gitster.g","threadId":"59667","inReplyTo":"cover.1682671758.git.dyroneteng@gmail.com","subject":"Re: [PATCH v9 0/6] notes.c: introduce \"--separator\" option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-04-28T20:46:07Z","receivedAt":"2023-04-28T20:46:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Teng Long <dyroneteng@gmail.com> writes:\n\n> From: Teng Long <dyroneteng@gmail.com>\n\nIt may help newcomers to briefly mention what the purpose of this\nseries is here.  Not everybody necessarily has followed previous\niterations, especially when it comes to a series with this many\niterations.\n\n> Thanks.\n>\n> Teng Long (6):\n>   notes.c: cleanup 'strbuf_grow' call in 'append_edit'\n>   notes.c: use designated initializers for clarity\n>   t3321: add test cases about the notes stripspace behavior\n>   notes.c: introduce '--separator=<paragraph-break>' option\n>   notes.c: append separator instead of insert by pos\n>   notes.c: introduce \"--[no-]stripspace\" option\n\nLooks quite well done.  Will replace.\n\nThanks.\n"},{"id":"476392","messageId":"xmqq4jov3cu3.fsf@gitster.g","threadId":"59667","inReplyTo":"cover.1682671758.git.dyroneteng@gmail.com","subject":"Re: [PATCH v9 0/6] notes.c: introduce \"--separator\" option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-05-01T22:29:24Z","receivedAt":"2023-05-01T22:29:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Teng Long <dyroneteng@gmail.com> writes:\n\n> From: Teng Long <dyroneteng@gmail.com>\n>\n> Diff since v8:\n>\n> 1. test case: Uniform indent format, as recommended by Junio C Hamano.\n>\n> 2. [4/6] make the static var \"separator\" initialized as \"\\n\", simplify\n>    the code in \"insert_separator(...)\".\n>\n> 3. [4/6] I don't change the other parts about the \"struct note_msg\", the\n>    stripspace way (Junio suggest to consider about the stripspace each messge\n>    individually, but I found it will break the compatibility about \"-C\",\n>    which can be found the case of 'reuse with \"-C\" and add note with \"-m\",\n>    \"-m\" will stripspace all together').\n> 4. [5/6] Optimized the commit message and replace \"strbuf_insert*(...)\" with\n>    \"strbuf_add*(...)\".\n>\n> 5. [6/6] As Junio replied, I'm not sure whether the \"-C\" problem (When the\n>    \"-C\" argument is used with \"-m/-F\", the order of \"-C\" in the options will\n>    affect the result of stripspace differently,) is need to be fixed or keep\n>    as is, I choose to do not break the old behaviour (In fact, I hope to fix\n>    this issue in another patch, if at all, and let this long-tailed patchset\n>    to mature faster, maybe).\n\nI am inclined to say that we should declare victory and merge this\ndown to 'next' soonish, unless somebody spots a big hole in the\nlogic, or finds a nicer way to \"solve\" the \"-C problem\" (to which I\nsuspect there is no clean solution, as the original behaviour is\nmore or less inconsistent---that is probably because the feature was\ndesigned assuming that nobody will combine -C with other options).\n\nThanks.\n"},{"id":"476670","messageId":"20230506091205.57746-1-tenglong.tl@alibaba-inc.com","threadId":"59667","inReplyTo":"xmqqsfcjbuud.fsf@gitster.g","subject":"Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-06T09:12:04Z","receivedAt":"2023-05-06T09:12:19Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n>> +static char *separator = \"\\n\";\n>\n>The only two ways this pointer gains a non-NULL value are with this\n>initialization and parsing the command line \"--separator=<value>\"\n>option with OPT_STRING().  Neither of them allocate new storage but\n>points an existing string that we do not \"own\" (and cannot free)\n>with the pointer.  So it probably is safer to make it a pointer to a\n>const string, i.e.\n>\n>\tstatic const char *separator = \"\\n\";\n>\n>> @@ -213,65 +229,96 @@ static void write_note_data(struct note_data *d, struct object_id *oid)\n>>  \t}\n>>  }\n>>  \n>> +static void insert_separator(struct strbuf *message, size_t pos)\n>> +{\n>> +\tif (separator[strlen(separator) - 1] == '\\n')\n>> +\t\tstrbuf_addstr(message, separator);\n>> +\telse\n>> +\t\tstrbuf_insertf(message, pos, \"%s%s\", separator, \"\\n\");\n>> +}\n>> +\n>> +static void concat_messages(struct note_data *d)\n>> +{\n>> +\tstruct strbuf msg = STRBUF_INIT;\n>> +\n>> +\tsize_t i;\n>> +\tfor (i = 0; i < d->msg_nr ; i++) {\n>\n>Wrong placement of the blank line that separates the declaration and\n>the first statement.\n\nThanks for your advice and detailed explanation. I have noticed\nin \"what's cooking in git\" that this patchset is being planed to\nmerge into 'next', so I display the diff here for the convenience\nof applying, or please let me know if a new patch is required.\n\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 8905298b..6eede305 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -28,7 +28,7 @@\n #include \"worktree.h\"\n #include \"write-or-die.h\"\n \n-static const char *separator = \"\\n\";\n+static char *separator = \"\\n\";\n static const char * const git_notes_usage[] = {\n        N_(\"git notes [--ref <notes-ref>] [list [<object>]]\"),\n        N_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n@@ -248,8 +248,8 @@ static void append_separator(struct strbuf *message)\n static void concat_messages(struct note_data *d)\n {\n        struct strbuf msg = STRBUF_INIT;\n-       size_t i;\n \n+       size_t i;\n        for (i = 0; i < d->msg_nr ; i++) {\n                if (d->buf.len)\n                        append_separator(&d->buf);\n"},{"id":"476671","messageId":"20230506092204.57914-1-tenglong.tl@alibaba-inc.com","threadId":"59667","inReplyTo":"20230506091205.57746-1-tenglong.tl@alibaba-inc.com","subject":"Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-06T09:22:04Z","receivedAt":"2023-05-06T09:22:16Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":">diff --git a/builtin/notes.c b/builtin/notes.c\n>index 8905298b..6eede305 100644\n>--- a/builtin/notes.c\n>+++ b/builtin/notes.c\n>@@ -28,7 +28,7 @@\n> #include \"worktree.h\"\n> #include \"write-or-die.h\"\n> \n>-static const char *separator = \"\\n\";\n>+static char *separator = \"\\n\";\n> static const char * const git_notes_usage[] = {\n>        N_(\"git notes [--ref <notes-ref>] [list [<object>]]\"),\n>        N_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n>@@ -248,8 +248,8 @@ static void append_separator(struct strbuf *message)\n> static void concat_messages(struct note_data *d)\n> {\n>        struct strbuf msg = STRBUF_INIT;\n>-       size_t i;\n> \n>+       size_t i;\n>        for (i = 0; i < d->msg_nr ; i++) {\n>                if (d->buf.len)\n>                        append_separator(&d->buf);\n\nSorry, the direction is reversed wrongly.\n\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 6eede305..8905298b 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -28,7 +28,7 @@\n #include \"worktree.h\"\n #include \"write-or-die.h\"\n \n-static char *separator = \"\\n\";\n+static const char *separator = \"\\n\";\n static const char * const git_notes_usage[] = {\n        N_(\"git notes [--ref <notes-ref>] [list [<object>]]\"),\n        N_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n@@ -248,8 +248,8 @@ static void append_separator(struct strbuf *message)\n static void concat_messages(struct note_data *d)\n {\n        struct strbuf msg = STRBUF_INIT;\n-\n        size_t i;\n+\n        for (i = 0; i < d->msg_nr ; i++) {\n                if (d->buf.len)\n                        append_separator(&d->buf);\n"},{"id":"476985","messageId":"2908b005-9478-4c59-ae8e-150be44a15a9@app.fastmail.com","threadId":"59667","inReplyTo":"ed930ef4f795f30792bc14d9c1939484e4976db8.1682671758.git.dyroneteng@gmail.com","subject":"Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option","fromName":"Kristoffer Haugsbakk","fromEmail":"code@khaugsbakk.name","sentAt":"2023-05-10T19:19:46Z","receivedAt":"2023-05-10T19:21:47Z","isPatch":true,"sender":{"key":"code@khaugsbakk.name","avatar":"https://avatars.githubusercontent.com/u/2229597?v=4"},"body":"I realize that this series is going to be merged to `master`.[1] I was\ntrying out this new change since I might have some use for it when the\nnext version is released.\n\nOn Fri, Apr 28, 2023, at 11:23, Teng Long wrote:\n> From: Teng Long <dyroneteng@gmail.com>\n>\n> When adding new notes or appending to an existing notes, we will\n> insert a blank line between the paragraphs, like:\n\nThe test case[2] `append: specify an empty separator` demonstrates that\n`--separator=\"\"` is the same as the default behavior, namely to add a\nblank line. It has (according to the commit messages) been like this\nsince v5 of this patch.[3]\n\nv4 of this patch special-cased `--separator=\"\"` to mean “no\nseparator”. And this was the same behavior as the original\n`--no-blankline`,[4] which eventually mutated into `--separator`.\n\nWhy was this changed to act the same as the default behavior (add a\nblank line)? I can’t seem to find a note for it on the cover letter of\nv5 or in the relevant replies.\n\nIt seemed that v4 of this patch (with special-cased empty argument) was\nperhaps based on Eric Sunshine’s suggestion:[5]\n\n> Taking a step back, perhaps think of this in terms of \"separator\". The\n> default behavior is to insert \"\\n\" as a separator between notes. If\n> you add a --separator option, then users could supply their own\n> separator, such as \"----\\n\" or, in your case, \"\" to suppress the blank\n> line.\n\n(And then reiterated in a v4 email [6])\n\nWas the idea perhaps to eventually (separately) add a separate option\nwhich functions like `--each-message-is-line-not-paragraph`, like what\nwas mentioned in [7]?\n\nMaybe I’ve missed something. (I probably have.)\n\n[1] https://lore.kernel.org/git/xmqqpm785erg.fsf@gitster.g/T/#md9b20801457c3eb24dc0e793f5dfbeae2f2707fd\n[2] On `next`, 74a8c73209 (Sync with 'master', 2023-05-09)\n[3] https://lore.kernel.org/git/a74c96d6dd23f2f1df6d3492093f3fd27451e24c.1676551077.git.dyroneteng@gmail.com/\n\n   Commit message on v4:\n\n   >  * --separator='': we specify an empty separator which means will\n   >  append the message directly without inserting any separator at\n   >  first.\n\n   Commit message on v5:\n\n   > * --separator='': we specify an empty separator which has the same\n   > behavour with --separator='\\n' and or not specified the option.\n\n[4] https://lore.kernel.org/git/20221013055654.39628-1-tenglong.tl@alibaba-inc.com/\n[5] https://lore.kernel.org/git/CAPig+cRcezSp4Rqt1Y9bD-FT6+7b0g9qHfbGRx65AOnw2FQXKg@mail.gmail.com/\n[6] https://lore.kernel.org/git/CAPig+cSF7Fp3oM4TRU1QbiSzTeKNd1qGtqU7goPc1r-p4g8mkg@mail.gmail.com/\n[7] https://lore.kernel.org/git/xmqqh6yh3nk4.fsf@gitster.g/\n\n-- \nKristoffer Haugsbakk\n"},{"id":"477129","messageId":"20230512040746.2069-1-tenglong.tl@alibaba-inc.com","threadId":"59667","inReplyTo":"2908b005-9478-4c59-ae8e-150be44a15a9@app.fastmail.com","subject":"Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-12T04:07:46Z","receivedAt":"2023-05-12T04:07:59Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"\"Kristoffer Haugsbakk\" <code@khaugsbakk.name> writes:\n\n>I realize that this series is going to be merged to `master`.[1] I was\n>trying out this new change since I might have some use for it when the\n>next version is released.\n\nI'm glad there are use cases for the topic.\n\n>The test case[2] `append: specify an empty separator` demonstrates that\n>`--separator=\"\"` is the same as the default behavior, namely to add a\n>blank line. It has (according to the commit messages) been like this\n>since v5 of this patch.[3]\n>\n>v4 of this patch special-cased `--separator=\"\"` to mean “no\n>separator”. And this was the same behavior as the original\n>`--no-blankline`,[4] which eventually mutated into `--separator`.\n>\n>Why was this changed to act the same as the default behavior (add a\n>blank line)? I can’t seem to find a note for it on the cover letter of\n>v5 or in the relevant replies.\n>\n>It seemed that v4 of this patch (with special-cased empty argument) was\n>perhaps based on Eric Sunshine’s suggestion:[5]\n>\n>> Taking a step back, perhaps think of this in terms of \"separator\". The\n>> default behavior is to insert \"\\n\" as a separator between notes. If\n>> you add a --separator option, then users could supply their own\n>> separator, such as \"----\\n\" or, in your case, \"\" to suppress the blank\n>> line.\n>\n>(And then reiterated in a v4 email [6])\n\nThere is indeed this change in the process, I did not express it clearly in the\npatch, sorry,  allow me to explain it:\n\nInitially, I wanted to support separator insertion with --[no-]blankline. It was\nintuitive and clear in logic. If it is specified as --blankline, newline would\nbe append, if it is --no-blankline, newline would not be append, and --blankline\nwould be the default behavior.\n\nThe problem with this is that \"blankline\" might not be a good name because it\ndoesn't seem to express the meaning of the separator or paragraph-break, and\nsince we're going to support separators, it might be more thorough to support a\ncustom separator, which is a better solution I think.\n\nReturning to the issue of -separator = \"\", my thought is that I want\n--separator=\"\" to behave the same way as\n--separator=\"<any-string-without-a-trailing-\\n'>\" when deals a string which does\nnot contains a trailing newline. This will eliminate one more implicit logic and\nmake behavior more consistent.\n\n>Was the idea perhaps to eventually (separately) add a separate option\n>which functions like `--each-message-is-line-not-paragraph`, like what\n>was mentioned in [7]?\n\nI think --no-separator maybe a better name, means that not any separator will be\nappend between two paragraphs even a newline.\n\nThanks.\n\n"},{"id":"477162","messageId":"cc3775ba-bcfe-4984-a391-b37c0382f50d@app.fastmail.com","threadId":"59667","inReplyTo":"20230512040746.2069-1-tenglong.tl@alibaba-inc.com","subject":"Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option","fromName":"Kristoffer Haugsbakk","fromEmail":"code@khaugsbakk.name","sentAt":"2023-05-12T07:29:50Z","receivedAt":"2023-05-12T07:30:17Z","isPatch":true,"sender":{"key":"code@khaugsbakk.name","avatar":"https://avatars.githubusercontent.com/u/2229597?v=4"},"body":"Hi Teng Long and thanks for the explanation.\n\nOn Fri, May 12, 2023, at 06:07, Teng Long wrote:\n> I think --no-separator maybe a better name, means that not any separator will be\n> append between two paragraphs even a newline.\n\nThat makes sense. Using `--no-separator` seems very consistent with\nother commands.\n\n-- \nKristoffer Haugsbakk\n"},{"id":"477386","messageId":"xmqqttwcus7p.fsf@gitster.g","threadId":"59667","inReplyTo":"20230512040746.2069-1-tenglong.tl@alibaba-inc.com","subject":"Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-05-16T17:00:26Z","receivedAt":"2023-05-16T17:01:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Teng Long <dyroneteng@gmail.com> writes:\n\n> Returning to the issue of -separator = \"\", my thought is that I want\n> --separator=\"\" to behave the same way as\n> --separator=\"<any-string-without-a-trailing-\\n'>\" when deals a string which does\n> not contains a trailing newline. This will eliminate one more implicit logic and\n> make behavior more consistent.\n\nAn obvious alternative would be to use the string given to the\n\"--separator\" option literally, I guess, and you can add your own\nnewline if you want to.  But most of the time you would not want to\nhave an incomplete line, so appending the newline at the end by\ndefault does make sense.\n\nSpecial casing an empty value to \"--separator\" as \"nothing\" does\nmake sort-of sense.  Using a blank line as the inter-paragraph\nseparator is the default, and there is not much use in the\n\"--separator=''\" that becomes \"--separator=$'\\012'\" automatically.\n\n> I think --no-separator maybe a better name, means that not any separator will be\n> append between two paragraphs even a newline.\n\nIt would work as well.  Is it something we can safely add before\nmerging the topic to upcoming -rc1?\n\nThanks.\n"},{"id":"477427","messageId":"20230517035812.4210-1-tenglong.tl@alibaba-inc.com","threadId":"59667","inReplyTo":"xmqqttwcus7p.fsf@gitster.g","subject":"Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-17T03:58:12Z","receivedAt":"2023-05-17T03:58:25Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n>> I think --no-separator maybe a better name, means that not any separator will be\n>> append between two paragraphs even a newline.\n>\n>It would work as well.  Is it something we can safely add before\n>merging the topic to upcoming -rc1?\n\nI noticed rc-1 will be released at this Friday, I will post a new patch\nabout \"--no-separator\" today(UTC/GMT+08:00).\n\nThanks.\n"},{"id":"477450","messageId":"xmqq5y8rrn11.fsf@gitster.g","threadId":"59667","inReplyTo":"20230517035812.4210-1-tenglong.tl@alibaba-inc.com","subject":"Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-05-17T15:32:58Z","receivedAt":"2023-05-17T15:34:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Teng Long <dyroneteng@gmail.com> writes:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>>> I think --no-separator maybe a better name, means that not any separator will be\n>>> append between two paragraphs even a newline.\n>>\n>>It would work as well.  Is it something we can safely add before\n>>merging the topic to upcoming -rc1?\n>\n> I noticed rc-1 will be released at this Friday, I will post a new patch\n> about \"--no-separator\" today(UTC/GMT+08:00).\n\nThanks.\n"},{"id":"477513","messageId":"cover.1684411136.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1682671758.git.dyroneteng@gmail.com","subject":"[PATCH v10 0/6] notes.c: introduce \"--separator\" option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-18T12:02:05Z","receivedAt":"2023-05-18T12:02:30Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\nDiff since v9:\n\n1. [4/6] support `--no-separator` which means not to add any paragraph-breaks.\n2. [4/6] Fix the problems by the Junio's suggestion [1]\n\n[1] https://public-inbox.org/git/xmqqsfcjbuud.fsf@gitster.g/\n\nThanks.\n\nTeng Long (6):\n  notes.c: cleanup 'strbuf_grow' call in 'append_edit'\n  notes.c: use designated initializers for clarity\n  t3321: add test cases about the notes stripspace behavior\n  notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]'\n    option\n  notes.c: append separator instead of insert by pos\n  notes.c: introduce \"--[no-]stripspace\" option\n\n Documentation/git-notes.txt |  43 ++-\n builtin/notes.c             | 157 +++++++---\n t/t3301-notes.sh            | 169 +++++++++++\n t/t3321-notes-stripspace.sh | 577 ++++++++++++++++++++++++++++++++++++\n 4 files changed, 904 insertions(+), 42 deletions(-)\n create mode 100755 t/t3321-notes-stripspace.sh\n\nRange-diff against v9:\n1:  0634434e = 1:  0634434e notes.c: cleanup 'strbuf_grow' call in 'append_edit'\n2:  4ad78405 = 2:  4ad78405 notes.c: use designated initializers for clarity\n3:  c2fc2091 = 3:  c2fc2091 t3321: add test cases about the notes stripspace behavior\n4:  ed930ef4 ! 4:  820dda04 notes.c: introduce '--separator=<paragraph-break>' option\n    @@ Metadata\n     Author: Teng Long <dyroneteng@gmail.com>\n     \n      ## Commit message ##\n    -    notes.c: introduce '--separator=<paragraph-break>' option\n    +    notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option\n     \n         When adding new notes or appending to an existing notes, we will\n         insert a blank line between the paragraphs, like:\n    @@ Commit message\n     \n         Both the two exections produce the same result.\n     \n    +    Alternatively, if you do not want any new paragraph\n    +    separators, even a newline by default, you can specify\n    +    '--no-separator'.\n    +\n         The reason we use a \"strbuf\" array to concat but not \"string_list\", is\n         that the binary file content may contain '\\0' in the middle, this will\n         cause the corrupt result if using a string to save.\n    @@ Documentation/git-notes.txt: SYNOPSIS\n      [verse]\n      'git notes' [list [<object>]]\n     -'git notes' add [-f] [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n    -+'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n    ++'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n      'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )\n     -'git notes' append [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n    -+'git notes' append [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n    ++'git notes' append [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n      'git notes' edit [--allow-empty] [<object>]\n      'git notes' show [<object>]\n      'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>\n    @@ Documentation/git-notes.txt: OPTIONS\n      \tAllow an empty note object to be stored. The default behavior is\n      \tto automatically remove empty notes.\n      \n    -+--separator <paragraph-break>::\n    ++--[no-]separator, --separator=<paragraph-break>::\n     +\tSpecify a string used as a custom inter-paragraph separator\n    -+\t(a newline is added at the end as needed).  Defaults to a\n    -+\tblank line.\n    ++\t(a newline is added at the end as needed). If `--no-separator`, no\n    ++\tseparators will be added between paragraphs.  Defaults to a blank\n    ++\tline.\n     +\n      --ref <ref>::\n      \tManipulate the notes tree in <ref>.  This overrides\n    @@ builtin/notes.c\n      #include \"worktree.h\"\n      #include \"write-or-die.h\"\n      \n    -+static char *separator = \"\\n\";\n    ++static const char *separator = \"\\n\";\n      static const char * const git_notes_usage[] = {\n      \tN_(\"git notes [--ref <notes-ref>] [list [<object>]]\"),\n     -\tN_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n    -+\tN_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n    ++\tN_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n      \tN_(\"git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>\"),\n     -\tN_(\"git notes [--ref <notes-ref>] append [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n    -+\tN_(\"git notes [--ref <notes-ref>] append [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n    ++\tN_(\"git notes [--ref <notes-ref>] append [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n      \tN_(\"git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]\"),\n      \tN_(\"git notes [--ref <notes-ref>] show [<object>]\"),\n      \tN_(\"git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>\"),\n    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_\n      \n     +static void insert_separator(struct strbuf *message, size_t pos)\n     +{\n    -+\tif (separator[strlen(separator) - 1] == '\\n')\n    -+\t\tstrbuf_addstr(message, separator);\n    ++\tif (!separator)\n    ++\t\treturn;\n    ++\telse if (separator[strlen(separator) - 1] == '\\n')\n    ++\t\tstrbuf_insertstr(message, pos, separator);\n     +\telse\n     +\t\tstrbuf_insertf(message, pos, \"%s%s\", separator, \"\\n\");\n     +}\n    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_\n     +static void concat_messages(struct note_data *d)\n     +{\n     +\tstruct strbuf msg = STRBUF_INIT;\n    -+\n     +\tsize_t i;\n    ++\n     +\tfor (i = 0; i < d->msg_nr ; i++) {\n     +\t\tif (d->buf.len)\n     +\t\t\tinsert_separator(&d->buf, d->buf.len);\n    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_\n      \treturn 0;\n      }\n      \n    +@@ builtin/notes.c: static int parse_reedit_arg(const struct option *opt, const char *arg, int unset\n    + \treturn parse_reuse_arg(opt, arg, unset);\n    + }\n    + \n    ++static int parse_separator_arg(const struct option *opt, const char *arg,\n    ++\t\t\t       int unset)\n    ++{\n    ++\tif (unset)\n    ++\t\t*(const char **)opt->value = NULL;\n    ++\telse\n    ++\t\t*(const char **)opt->value = arg ? arg : \"\\n\";\n    ++\treturn 0;\n    ++}\n    ++\n    + static int notes_copy_from_stdin(int force, const char *rewrite_cmd)\n    + {\n    + \tstruct strbuf buf = STRBUF_INIT;\n     @@ builtin/notes.c: static int add(int argc, const char **argv, const char *prefix)\n      \tstruct object_id object, new_note;\n      \tconst struct object_id *note;\n    @@ builtin/notes.c: static int add(int argc, const char **argv, const char *prefix)\n      \t\tOPT_BOOL(0, \"allow-empty\", &allow_empty,\n      \t\t\tN_(\"allow storing empty note\")),\n      \t\tOPT__FORCE(&force, N_(\"replace existing notes\"), PARSE_OPT_NOCOMPLETE),\n    -+\t\tOPT_STRING(0, \"separator\", &separator, N_(\"separator\"),\n    -+\t\t\tN_(\"insert <paragraph-break> between paragraphs\")),\n    ++\t\tOPT_CALLBACK_F(0, \"separator\", &separator,\n    ++\t\t\tN_(\"<paragraph-break>\"),\n    ++\t\t\tN_(\"insert <paragraph-break> between paragraphs\"),\n    ++\t\t\tPARSE_OPT_OPTARG, parse_separator_arg),\n      \t\tOPT_END()\n      \t};\n      \n    @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char\n      \t\t\tparse_reuse_arg),\n      \t\tOPT_BOOL(0, \"allow-empty\", &allow_empty,\n      \t\t\tN_(\"allow storing empty note\")),\n    -+\t\tOPT_STRING(0, \"separator\", &separator, N_(\"separator\"),\n    -+\t\t\tN_(\"insert <paragraph-break> between paragraphs\")),\n    ++\t\tOPT_CALLBACK_F(0, \"separator\", &separator,\n    ++\t\t\tN_(\"<paragraph-break>\"),\n    ++\t\t\tN_(\"insert <paragraph-break> between paragraphs\"),\n    ++\t\t\tPARSE_OPT_OPTARG, parse_separator_arg),\n      \t\tOPT_END()\n      \t};\n      \tint edit = !strcmp(argv[0], \"edit\");\n    @@ t/t3301-notes.sh: test_expect_success 'create note with combination of -m and -F\n      '\n      \n     +test_expect_success 'create note with combination of -m and -F and --separator' '\n    ++\ttest_when_finished git notes remove HEAD &&\n     +\tcat >expect-combine_m_and_F <<-\\EOF &&\n     +\tfoo\n     +\t-------\n    @@ t/t3301-notes.sh: test_expect_success 'create note with combination of -m and -F\n     +\tEOF\n     +\techo \"xyzzy\" >note_a &&\n     +\techo \"zyxxy\" >note_b &&\n    -+\tgit notes add -m \"foo\" -F note_a -m \"bar\" -F note_b -m \"baz\" --separator \"-------\" &&\n    ++\tgit notes add -m \"foo\" -F note_a -m \"bar\" -F note_b -m \"baz\" --separator=\"-------\" &&\n    ++\tgit notes show >actual &&\n    ++\ttest_cmp expect-combine_m_and_F actual\n    ++'\n    ++\n    ++test_expect_success 'create note with combination of -m and -F and --no-separator' '\n    ++\tcat >expect-combine_m_and_F <<-\\EOF &&\n    ++\tfoo\n    ++\txyzzy\n    ++\tbar\n    ++\tzyxxy\n    ++\tbaz\n    ++\tEOF\n    ++\techo \"xyzzy\" >note_a &&\n    ++\techo \"zyxxy\" >note_b &&\n    ++\tgit notes add -m \"foo\" -F note_a -m \"bar\" -F note_b -m \"baz\" --no-separator &&\n     +\tgit notes show >actual &&\n     +\ttest_cmp expect-combine_m_and_F actual\n     +'\n    @@ t/t3301-notes.sh: test_expect_success 'listing non-existing notes fails' '\n      \ttest_must_be_empty actual\n      '\n      \n    -+test_expect_success 'append: specify an empty separator' '\n    ++test_expect_success 'append: specify a separator with an empty arg' '\n     +\ttest_when_finished git notes remove HEAD &&\n     +\tcat >expect <<-\\EOF &&\n     +\tnotes-1\n    @@ t/t3301-notes.sh: test_expect_success 'listing non-existing notes fails' '\n     +\ttest_cmp expect actual\n     +'\n     +\n    ++test_expect_success 'append: specify a separator without arg' '\n    ++\ttest_when_finished git notes remove HEAD &&\n    ++\tcat >expect <<-\\EOF &&\n    ++\tnotes-1\n    ++\n    ++\tnotes-2\n    ++\tEOF\n    ++\n    ++\tgit notes add -m \"notes-1\" &&\n    ++\tgit notes append --separator -m \"notes-2\" &&\n    ++\tgit notes show >actual &&\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++test_expect_success 'append: specify as --no-separator' '\n    ++\ttest_when_finished git notes remove HEAD &&\n    ++\tcat >expect <<-\\EOF &&\n    ++\tnotes-1\n    ++\tnotes-2\n    ++\tEOF\n    ++\n    ++\tgit notes add -m \"notes-1\" &&\n    ++\tgit notes append --no-separator -m \"notes-2\" &&\n    ++\tgit notes show >actual &&\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n     +test_expect_success 'append: specify separator with line break' '\n     +\ttest_when_finished git notes remove HEAD &&\n     +\tcat >expect <<-\\EOF &&\n    @@ t/t3301-notes.sh: test_expect_success 'listing non-existing notes fails' '\n     +\n     +\techo \"f-notes-1\" >note_a &&\n     +\techo \"f-notes-2\" >note_b &&\n    -+\tgit notes append -m \"m-notes-1\" -F note_a -m \"m-notes-2\" -F note_b -m \"m-notes-3\" --separator \"-------\" &&\n    ++\tgit notes append -m \"m-notes-1\" -F note_a -m \"m-notes-2\" -F note_b -m \"m-notes-3\" --separator=\"-------\" &&\n     +\tgit notes show >actual &&\n     +\ttest_cmp expect-combine_m_and_F actual\n     +'\n5:  eea2246f ! 5:  76c93f19 notes.c: append separator instead of insert by pos\n    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_\n     -static void insert_separator(struct strbuf *message, size_t pos)\n     +static void append_separator(struct strbuf *message)\n      {\n    - \tif (separator[strlen(separator) - 1] == '\\n')\n    - \t\tstrbuf_addstr(message, separator);\n    + \tif (!separator)\n    + \t\treturn;\n    + \telse if (separator[strlen(separator) - 1] == '\\n')\n    +-\t\tstrbuf_insertstr(message, pos, separator);\n    ++\t\tstrbuf_addstr(message, separator);\n      \telse\n     -\t\tstrbuf_insertf(message, pos, \"%s%s\", separator, \"\\n\");\n     +\t\tstrbuf_addf(message, \"%s%s\", separator, \"\\n\");\n    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_\n      \n      static void concat_messages(struct note_data *d)\n     @@ builtin/notes.c: static void concat_messages(struct note_data *d)\n    - \tsize_t i;\n    + \n      \tfor (i = 0; i < d->msg_nr ; i++) {\n      \t\tif (d->buf.len)\n     -\t\t\tinsert_separator(&d->buf, d->buf.len);\n6:  20063bea ! 6:  d65f067c notes.c: introduce \"--[no-]stripspace\" option\n    @@ Documentation/git-notes.txt: SYNOPSIS\n      --------\n      [verse]\n      'git notes' [list [<object>]]\n    --'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n    -+'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n    +-'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n    ++'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n      'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )\n    --'git notes' append [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n    +-'git notes' append [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n     -'git notes' edit [--allow-empty] [<object>]\n    -+'git notes' append [--allow-empty] [--separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n    ++'git notes' append [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n     +'git notes' edit [--allow-empty] [<object>] [--[no-]stripspace]\n      'git notes' show [<object>]\n      'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>\n    @@ Documentation/git-notes.txt: OPTIONS\n      -c <object>::\n      --reedit-message=<object>::\n     @@ Documentation/git-notes.txt: OPTIONS\n    - \t(a newline is added at the end as needed).  Defaults to a\n    - \tblank line.\n    + \tseparators will be added between paragraphs.  Defaults to a blank\n    + \tline.\n      \n     +--[no-]stripspace::\n     +\tStrip leading and trailing whitespace from the note message.\n    @@ Documentation/git-notes.txt: OPTIONS\n      \t`GIT_NOTES_REF` and the \"core.notesRef\" configuration.  The ref\n     \n      ## builtin/notes.c ##\n    +@@\n    + static const char *separator = \"\\n\";\n    + static const char * const git_notes_usage[] = {\n    + \tN_(\"git notes [--ref <notes-ref>] [list [<object>]]\"),\n    +-\tN_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n    ++\tN_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [--[no-]stripspace] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n    + \tN_(\"git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>\"),\n    +-\tN_(\"git notes [--ref <notes-ref>] append [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n    ++\tN_(\"git notes [--ref <notes-ref>] append [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [--[no-]stripspace] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n    + \tN_(\"git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]\"),\n    + \tN_(\"git notes [--ref <notes-ref>] show [<object>]\"),\n    + \tN_(\"git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>\"),\n     @@ builtin/notes.c: static const char * const git_notes_get_ref_usage[] = {\n      static const char note_template[] =\n      \tN_(\"Write/edit the notes for the following object:\");\n    @@ builtin/notes.c: static int add(int argc, const char **argv, const char *prefix)\n      \tstruct option options[] = {\n      \t\tOPT_CALLBACK_F('m', \"message\", &d, N_(\"message\"),\n     @@ builtin/notes.c: static int add(int argc, const char **argv, const char *prefix)\n    - \t\tOPT__FORCE(&force, N_(\"replace existing notes\"), PARSE_OPT_NOCOMPLETE),\n    - \t\tOPT_STRING(0, \"separator\", &separator, N_(\"separator\"),\n    - \t\t\tN_(\"insert <paragraph-break> between paragraphs\")),\n    + \t\t\tN_(\"<paragraph-break>\"),\n    + \t\t\tN_(\"insert <paragraph-break> between paragraphs\"),\n    + \t\t\tPARSE_OPT_OPTARG, parse_separator_arg),\n     +\t\tOPT_BOOL(0, \"stripspace\", &d.stripspace,\n     +\t\t\tN_(\"remove unnecessary whitespace\")),\n      \t\tOPT_END()\n    @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char\n      \t\tOPT_CALLBACK_F('m', \"message\", &d, N_(\"message\"),\n      \t\t\tN_(\"note contents as a string\"), PARSE_OPT_NONEG,\n     @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char *prefix)\n    - \t\t\tN_(\"allow storing empty note\")),\n    - \t\tOPT_STRING(0, \"separator\", &separator, N_(\"separator\"),\n    - \t\t\tN_(\"insert <paragraph-break> between paragraphs\")),\n    + \t\t\tN_(\"<paragraph-break>\"),\n    + \t\t\tN_(\"insert <paragraph-break> between paragraphs\"),\n    + \t\t\tPARSE_OPT_OPTARG, parse_separator_arg),\n     +\t\tOPT_BOOL(0, \"stripspace\", &d.stripspace,\n     +\t\t\tN_(\"remove unnecessary whitespace\")),\n      \t\tOPT_END()\n-- \n2.40.0.356.g67a1c1d0\n\n"},{"id":"477514","messageId":"0634434e2a3e40ef4af5013349202491d55fe1d7.1684411136.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1684411136.git.dyroneteng@gmail.com","subject":"[PATCH v10 1/6] notes.c: cleanup 'strbuf_grow' call in 'append_edit'","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-18T12:02:06Z","receivedAt":"2023-05-18T12:02:31Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\nLet's cleanup the unnecessary 'strbuf_grow' call in 'append_edit'. This\n\"strbuf_grow(&d.buf, size + 1);\" is prepared for insert a blank line if\nneeded, but actually when inserting, \"strbuf_insertstr(&d.buf, 0,\n\"\\n\");\" will do the \"grow\" for us.\n\n348f199b (builtin-notes: Refactor handling of -F option to allow\ncombining -m and -F, 2010-02-13) added these to mimic the code\nintroduced by 2347fae5 (builtin-notes: Add \"append\" subcommand for\nappending to note objects, 2010-02-13) that reads in previous note\nbefore the message.  And the resulting code with explicit sizing is\ncarried to this day.\n\nIn the context of reading an existing note in, exact sizing may have\nmade sense, but because the resulting note needs cleansing with\nstripspace() when appending with this option, such an exact sizing\ndoes not buy us all that much in practice.\n\nIt may help avoiding overallocation due to ALLOC_GROW() slop, but\nnobody can feed so many long messages for it to matter from the\ncommand line.\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nHelped-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/notes.c | 2 --\n 1 file changed, 2 deletions(-)\n\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 4ff44f1e..c501c6ee 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -219,7 +219,6 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)\n \n \tBUG_ON_OPT_NEG(unset);\n \n-\tstrbuf_grow(&d->buf, strlen(arg) + 2);\n \tif (d->buf.len)\n \t\tstrbuf_addch(&d->buf, '\\n');\n \tstrbuf_addstr(&d->buf, arg);\n@@ -623,7 +622,6 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \t\tchar *prev_buf = repo_read_object_file(the_repository, note,\n \t\t\t\t\t\t       &type, &size);\n \n-\t\tstrbuf_grow(&d.buf, size + 1);\n \t\tif (d.buf.len && prev_buf && size)\n \t\t\tstrbuf_insertstr(&d.buf, 0, \"\\n\");\n \t\tif (prev_buf && size)\n-- \n2.40.0.356.g67a1c1d0\n\n"},{"id":"477515","messageId":"4ad7840584191d6bbb3fcba72887aa569c797a15.1684411136.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1684411136.git.dyroneteng@gmail.com","subject":"[PATCH v10 2/6] notes.c: use designated initializers for clarity","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-18T12:02:07Z","receivedAt":"2023-05-18T12:02:42Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\nThe \"struct note_data d = { 0, 0, NULL, STRBUF_INIT };\" style could be\nreplaced with designated initializer for clarity.\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/notes.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex c501c6ee..9d8ca795 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -405,7 +405,7 @@ static int add(int argc, const char **argv, const char *prefix)\n \tstruct notes_tree *t;\n \tstruct object_id object, new_note;\n \tconst struct object_id *note;\n-\tstruct note_data d = { 0, 0, NULL, STRBUF_INIT };\n+\tstruct note_data d = { .buf = STRBUF_INIT };\n \tstruct option options[] = {\n \t\tOPT_CALLBACK_F('m', \"message\", &d, N_(\"message\"),\n \t\t\tN_(\"note contents as a string\"), PARSE_OPT_NONEG,\n@@ -571,7 +571,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \tconst struct object_id *note;\n \tchar *logmsg;\n \tconst char * const *usage;\n-\tstruct note_data d = { 0, 0, NULL, STRBUF_INIT };\n+\tstruct note_data d = { .buf = STRBUF_INIT };\n \tstruct option options[] = {\n \t\tOPT_CALLBACK_F('m', \"message\", &d, N_(\"message\"),\n \t\t\tN_(\"note contents as a string\"), PARSE_OPT_NONEG,\n-- \n2.40.0.356.g67a1c1d0\n\n"},{"id":"477516","messageId":"c2fc2091d2afbb5911220bb55574fae11cf1c1dc.1684411136.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1684411136.git.dyroneteng@gmail.com","subject":"[PATCH v10 3/6] t3321: add test cases about the notes stripspace behavior","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-18T12:02:08Z","receivedAt":"2023-05-18T12:02:44Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n t/t3321-notes-stripspace.sh | 291 ++++++++++++++++++++++++++++++++++++\n 1 file changed, 291 insertions(+)\n create mode 100755 t/t3321-notes-stripspace.sh\n\ndiff --git a/t/t3321-notes-stripspace.sh b/t/t3321-notes-stripspace.sh\nnew file mode 100755\nindex 00000000..89977873\n--- /dev/null\n+++ b/t/t3321-notes-stripspace.sh\n@@ -0,0 +1,291 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2023 Teng Long\n+#\n+\n+test_description='Test commit notes with stripspace behavior'\n+\n+. ./test-lib.sh\n+\n+MULTI_LF=\"$LF$LF$LF\"\n+write_script fake_editor <<\\EOF\n+echo \"$MSG\" >\"$1\"\n+echo \"$MSG\" >&2\n+EOF\n+GIT_EDITOR=./fake_editor\n+export GIT_EDITOR\n+\n+test_expect_success 'setup the commit' '\n+\ttest_commit 1st\n+'\n+\n+test_expect_success 'add note by editor' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes add  &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying single \"-m\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tgit notes add -m \"${LF}first-line${MULTI_LF}second-line${LF}\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying multiple \"-m\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tgit notes add -m \"${LF}\" \\\n+\t\t      -m \"first-line\" \\\n+\t\t      -m \"${MULTI_LF}\" \\\n+\t\t      -m \"second-line\" \\\n+\t\t      -m \"${LF}\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+\n+test_expect_success 'append note by editor' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tgit notes add -m \"first-line\" &&\n+\tMSG=\"${MULTI_LF}second-line${LF}\" git notes append  &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append note by specifying single \"-m\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tgit notes add -m \"${LF}first-line\" &&\n+\tgit notes append -m \"${MULTI_LF}second-line${LF}\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append note by specifying multiple \"-m\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tgit notes add -m \"${LF}first-line\" &&\n+\tgit notes append -m \"${MULTI_LF}\" \\\n+\t\t      -m \"second-line\" \\\n+\t\t      -m \"${LF}\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying single \"-F\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tcat >note-file <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add -F note-file &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add notes by specifying multiple \"-F\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfile-1-first-line\n+\n+\tfile-1-second-line\n+\n+\tfile-2-first-line\n+\n+\tfile-2-second-line\n+\tEOF\n+\n+\tcat >note-file-1 <<-EOF &&\n+\t${LF}\n+\tfile-1-first-line\n+\t${MULTI_LF}\n+\tfile-1-second-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file-2 <<-EOF &&\n+\t${LF}\n+\tfile-2-first-line\n+\t${MULTI_LF}\n+\tfile-2-second-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add -F note-file-1 -F note-file-2 &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append note by specifying single \"-F\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tinitial-line\n+\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tcat >note-file <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add -m \"initial-line\" &&\n+\tgit notes append -F note-file &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append notes by specifying multiple \"-F\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tinitial-line\n+\n+\tfile-1-first-line\n+\n+\tfile-1-second-line\n+\n+\tfile-2-first-line\n+\n+\tfile-2-second-line\n+\tEOF\n+\n+\tcat >note-file-1 <<-EOF &&\n+\t${LF}\n+\tfile-1-first-line\n+\t${MULTI_LF}\n+\tfile-1-second-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file-2 <<-EOF &&\n+\t${LF}\n+\tfile-2-first-line\n+\t${MULTI_LF}\n+\tfile-2-second-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add -m \"initial-line\" &&\n+\tgit notes append -F note-file-1 -F note-file-2 &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add notes with empty messages' '\n+\trev=$(git rev-parse HEAD) &&\n+\tgit notes add -m \"${LF}\" \\\n+\t\t      -m \"${MULTI_LF}\" \\\n+\t\t      -m \"${LF}\" >actual 2>&1 &&\n+\ttest_i18ngrep \"Removing note for object\" actual\n+'\n+\n+test_expect_success 'add note by specifying \"-C\" , do not stripspace is the default behavior' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tcat expect | git hash-object -w --stdin >blob &&\n+\tgit notes add -C $(cat blob) &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add notes with \"-C\" and \"-m\", \"-m\" will stripspace all together' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >data <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\n+\tthird-line\n+\tEOF\n+\n+\tcat data | git hash-object -w --stdin >blob &&\n+\tgit notes add -C $(cat blob) -m \"third-line\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add notes with \"-m\" and \"-C\", \"-C\" will not stripspace all together' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >data <<-EOF &&\n+\n+\tsecond-line\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\t${LF}\n+\tsecond-line\n+\tEOF\n+\n+\tcat data | git hash-object -w --stdin >blob &&\n+\tgit notes add -m \"first-line\" -C $(cat blob)  &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \n2.40.0.356.g67a1c1d0\n\n"},{"id":"477517","messageId":"76c93f192cd2040eab39353afba0eaa32a07dab8.1684411136.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1684411136.git.dyroneteng@gmail.com","subject":"[PATCH v10 5/6] notes.c: append separator instead of insert by pos","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-18T12:02:10Z","receivedAt":"2023-05-18T12:02:46Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\nRename \"insert_separator\" to \"append_separator\" and also remove the\n\"postion\" argument, this serves two purpose:\n\nThe first is that when specifying more than one \"-m\" ( like \"-F\", etc)\nto \"git notes add\" or \"git notes append\", the order of them matters,\nwhich means we need to append the each separator and message in turn,\nso we don't have to make the caller specify the position, the \"append\"\noperation is enough and clear.\n\nThe second is that when we execute the \"git notes append\" subcommand,\nwe need to combine the \"prev_note\" and \"current_note\" to get the\nfinal result. Before, we inserted a newline character at the beginning\nof \"current_note\". Now, we will append a newline to the end of\n\"prev_note\" instead, this will give the consisitent results.\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n builtin/notes.c | 21 ++++++++++++---------\n 1 file changed, 12 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 7aa2f923..84c010fe 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -229,14 +229,14 @@ static void write_note_data(struct note_data *d, struct object_id *oid)\n \t}\n }\n \n-static void insert_separator(struct strbuf *message, size_t pos)\n+static void append_separator(struct strbuf *message)\n {\n \tif (!separator)\n \t\treturn;\n \telse if (separator[strlen(separator) - 1] == '\\n')\n-\t\tstrbuf_insertstr(message, pos, separator);\n+\t\tstrbuf_addstr(message, separator);\n \telse\n-\t\tstrbuf_insertf(message, pos, \"%s%s\", separator, \"\\n\");\n+\t\tstrbuf_addf(message, \"%s%s\", separator, \"\\n\");\n }\n \n static void concat_messages(struct note_data *d)\n@@ -246,7 +246,7 @@ static void concat_messages(struct note_data *d)\n \n \tfor (i = 0; i < d->msg_nr ; i++) {\n \t\tif (d->buf.len)\n-\t\t\tinsert_separator(&d->buf, d->buf.len);\n+\t\t\tappend_separator(&d->buf);\n \t\tstrbuf_add(&msg, d->messages[i]->buf.buf, d->messages[i]->buf.len);\n \t\tstrbuf_addbuf(&d->buf, &msg);\n \t\tif (d->messages[i]->stripspace)\n@@ -695,14 +695,17 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \t\t/* Append buf to previous note contents */\n \t\tunsigned long size;\n \t\tenum object_type type;\n-\t\tchar *prev_buf = repo_read_object_file(the_repository, note,\n-\t\t\t\t\t\t       &type, &size);\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tchar *prev_buf = repo_read_object_file(the_repository, note, &type, &size);\n \n-\t\tif (d.buf.len && prev_buf && size)\n-\t\t\tinsert_separator(&d.buf, 0);\n \t\tif (prev_buf && size)\n-\t\t\tstrbuf_insert(&d.buf, 0, prev_buf, size);\n+\t\t\tstrbuf_add(&buf, prev_buf, size);\n+\t\tif (d.buf.len && prev_buf && size)\n+\t\t\tappend_separator(&buf);\n+\t\tstrbuf_insert(&d.buf, 0, buf.buf, buf.len);\n+\n \t\tfree(prev_buf);\n+\t\tstrbuf_release(&buf);\n \t}\n \n \tif (d.buf.len || allow_empty) {\n-- \n2.40.0.356.g67a1c1d0\n\n"},{"id":"477518","messageId":"820dda0458994fdf7ff37870736ce6ed7871720c.1684411136.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1684411136.git.dyroneteng@gmail.com","subject":"[PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-18T12:02:09Z","receivedAt":"2023-05-18T12:02:47Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\nWhen adding new notes or appending to an existing notes, we will\ninsert a blank line between the paragraphs, like:\n\n     $ git notes add -m foo -m bar\n     $ git notes show HEAD\n     foo\n\n     bar\n\nThe default behavour sometimes is not enough, the user may want\nto use a custom delimiter between paragraphs, like when\nspecifying '-m', '-F', '-C', '-c' options. So this commit\nintroduce a new '--separator' option for 'git notes add' and\n'git notes append', for example when executing:\n\n    $ git notes add -m foo -m bar --separator=\"-\"\n    $ git notes show HEAD\n    foo\n    -\n    bar\n\na newline is added to the value given to --separator if it\ndoes not end with one already. So when executing:\n\n      $ git notes add -m foo -m bar --separator=\"-\"\nand\n      $ export LF=\"\n      \"\n      $ git notes add -m foo -m bar --separator=\"-$LF\"\n\nBoth the two exections produce the same result.\n\nAlternatively, if you do not want any new paragraph\nseparators, even a newline by default, you can specify\n'--no-separator'.\n\nThe reason we use a \"strbuf\" array to concat but not \"string_list\", is\nthat the binary file content may contain '\\0' in the middle, this will\ncause the corrupt result if using a string to save.\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n Documentation/git-notes.txt |  22 +++--\n builtin/notes.c             | 124 +++++++++++++++++++++-----\n t/t3301-notes.sh            | 169 ++++++++++++++++++++++++++++++++++++\n 3 files changed, 286 insertions(+), 29 deletions(-)\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex efbc10f0..56d25a79 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -9,9 +9,9 @@ SYNOPSIS\n --------\n [verse]\n 'git notes' [list [<object>]]\n-'git notes' add [-f] [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n+'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n 'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )\n-'git notes' append [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n+'git notes' append [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n 'git notes' edit [--allow-empty] [<object>]\n 'git notes' show [<object>]\n 'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>\n@@ -65,7 +65,9 @@ add::\n \tHowever, if you're using `add` interactively (using an editor\n \tto supply the notes contents), then - instead of aborting -\n \tthe existing notes will be opened in the editor (like the `edit`\n-\tsubcommand).\n+\tsubcommand). If you specify multiple `-m` and `-F`, a blank\n+\tline will be inserted between the messages. Use the `--separator`\n+\toption to insert other delimiters.\n \n copy::\n \tCopy the notes for the first object onto the second object (defaults to\n@@ -85,8 +87,12 @@ corresponding <to-object>.  (The optional `<rest>` is ignored so that\n the command can read the input given to the `post-rewrite` hook.)\n \n append::\n-\tAppend to the notes of an existing object (defaults to HEAD).\n-\tCreates a new notes object if needed.\n+\tAppend new message(s) given by `-m` or `-F` options to an\n+\texisting note, or add them as a new note if one does not\n+\texist, for the object (defaults to HEAD).  When appending to\n+\tan existing note, a blank line is added before each new\n+\tmessage as an inter-paragraph separator.  The separator can\n+\tbe customized with the `--separator` option.\n \n edit::\n \tEdit the notes for a given object (defaults to HEAD).\n@@ -159,6 +165,12 @@ OPTIONS\n \tAllow an empty note object to be stored. The default behavior is\n \tto automatically remove empty notes.\n \n+--[no-]separator, --separator=<paragraph-break>::\n+\tSpecify a string used as a custom inter-paragraph separator\n+\t(a newline is added at the end as needed). If `--no-separator`, no\n+\tseparators will be added between paragraphs.  Defaults to a blank\n+\tline.\n+\n --ref <ref>::\n \tManipulate the notes tree in <ref>.  This overrides\n \t`GIT_NOTES_REF` and the \"core.notesRef\" configuration.  The ref\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 9d8ca795..7aa2f923 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -8,6 +8,7 @@\n  */\n \n #include \"cache.h\"\n+#include \"alloc.h\"\n #include \"config.h\"\n #include \"builtin.h\"\n #include \"gettext.h\"\n@@ -27,11 +28,12 @@\n #include \"worktree.h\"\n #include \"write-or-die.h\"\n \n+static const char *separator = \"\\n\";\n static const char * const git_notes_usage[] = {\n \tN_(\"git notes [--ref <notes-ref>] [list [<object>]]\"),\n-\tN_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n+\tN_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n \tN_(\"git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>\"),\n-\tN_(\"git notes [--ref <notes-ref>] append [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n+\tN_(\"git notes [--ref <notes-ref>] append [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n \tN_(\"git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]\"),\n \tN_(\"git notes [--ref <notes-ref>] show [<object>]\"),\n \tN_(\"git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>\"),\n@@ -99,11 +101,19 @@ static const char * const git_notes_get_ref_usage[] = {\n static const char note_template[] =\n \tN_(\"Write/edit the notes for the following object:\");\n \n+struct note_msg {\n+\tint stripspace;\n+\tstruct strbuf buf;\n+};\n+\n struct note_data {\n \tint given;\n \tint use_editor;\n \tchar *edit_path;\n \tstruct strbuf buf;\n+\tstruct note_msg **messages;\n+\tsize_t msg_nr;\n+\tsize_t msg_alloc;\n };\n \n static void free_note_data(struct note_data *d)\n@@ -113,6 +123,12 @@ static void free_note_data(struct note_data *d)\n \t\tfree(d->edit_path);\n \t}\n \tstrbuf_release(&d->buf);\n+\n+\twhile (d->msg_nr--) {\n+\t\tstrbuf_release(&d->messages[d->msg_nr]->buf);\n+\t\tfree(d->messages[d->msg_nr]);\n+\t}\n+\tfree(d->messages);\n }\n \n static int list_each_note(const struct object_id *object_oid,\n@@ -213,65 +229,98 @@ static void write_note_data(struct note_data *d, struct object_id *oid)\n \t}\n }\n \n+static void insert_separator(struct strbuf *message, size_t pos)\n+{\n+\tif (!separator)\n+\t\treturn;\n+\telse if (separator[strlen(separator) - 1] == '\\n')\n+\t\tstrbuf_insertstr(message, pos, separator);\n+\telse\n+\t\tstrbuf_insertf(message, pos, \"%s%s\", separator, \"\\n\");\n+}\n+\n+static void concat_messages(struct note_data *d)\n+{\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tsize_t i;\n+\n+\tfor (i = 0; i < d->msg_nr ; i++) {\n+\t\tif (d->buf.len)\n+\t\t\tinsert_separator(&d->buf, d->buf.len);\n+\t\tstrbuf_add(&msg, d->messages[i]->buf.buf, d->messages[i]->buf.len);\n+\t\tstrbuf_addbuf(&d->buf, &msg);\n+\t\tif (d->messages[i]->stripspace)\n+\t\t\tstrbuf_stripspace(&d->buf, 0);\n+\t\tstrbuf_reset(&msg);\n+\t}\n+\tstrbuf_release(&msg);\n+}\n+\n static int parse_msg_arg(const struct option *opt, const char *arg, int unset)\n {\n \tstruct note_data *d = opt->value;\n+\tstruct note_msg *msg = xmalloc(sizeof(*msg));\n \n \tBUG_ON_OPT_NEG(unset);\n \n-\tif (d->buf.len)\n-\t\tstrbuf_addch(&d->buf, '\\n');\n-\tstrbuf_addstr(&d->buf, arg);\n-\tstrbuf_stripspace(&d->buf, 0);\n-\n-\td->given = 1;\n+\tstrbuf_init(&msg->buf, strlen(arg));\n+\tstrbuf_addstr(&msg->buf, arg);\n+\tALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);\n+\td->messages[d->msg_nr - 1] = msg;\n+\tmsg->stripspace = 1;\n \treturn 0;\n }\n \n static int parse_file_arg(const struct option *opt, const char *arg, int unset)\n {\n \tstruct note_data *d = opt->value;\n+\tstruct note_msg *msg = xmalloc(sizeof(*msg));\n \n \tBUG_ON_OPT_NEG(unset);\n \n-\tif (d->buf.len)\n-\t\tstrbuf_addch(&d->buf, '\\n');\n+\tstrbuf_init(&msg->buf , 0);\n \tif (!strcmp(arg, \"-\")) {\n-\t\tif (strbuf_read(&d->buf, 0, 1024) < 0)\n+\t\tif (strbuf_read(&msg->buf, 0, 1024) < 0)\n \t\t\tdie_errno(_(\"cannot read '%s'\"), arg);\n-\t} else if (strbuf_read_file(&d->buf, arg, 1024) < 0)\n+\t} else if (strbuf_read_file(&msg->buf, arg, 1024) < 0)\n \t\tdie_errno(_(\"could not open or read '%s'\"), arg);\n-\tstrbuf_stripspace(&d->buf, 0);\n \n-\td->given = 1;\n+\tALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);\n+\td->messages[d->msg_nr - 1] = msg;\n+\tmsg->stripspace = 1;\n \treturn 0;\n }\n \n static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)\n {\n \tstruct note_data *d = opt->value;\n-\tchar *buf;\n+\tstruct note_msg *msg = xmalloc(sizeof(*msg));\n+\tchar *value;\n \tstruct object_id object;\n \tenum object_type type;\n \tunsigned long len;\n \n \tBUG_ON_OPT_NEG(unset);\n \n-\tif (d->buf.len)\n-\t\tstrbuf_addch(&d->buf, '\\n');\n-\n+\tstrbuf_init(&msg->buf, 0);\n \tif (repo_get_oid(the_repository, arg, &object))\n \t\tdie(_(\"failed to resolve '%s' as a valid ref.\"), arg);\n-\tif (!(buf = repo_read_object_file(the_repository, &object, &type, &len)))\n+\tif (!(value = repo_read_object_file(the_repository, &object, &type, &len)))\n \t\tdie(_(\"failed to read object '%s'.\"), arg);\n \tif (type != OBJ_BLOB) {\n-\t\tfree(buf);\n+\t\tstrbuf_release(&msg->buf);\n+\t\tfree(value);\n+\t\tfree(msg);\n \t\tdie(_(\"cannot read note data from non-blob object '%s'.\"), arg);\n \t}\n-\tstrbuf_add(&d->buf, buf, len);\n-\tfree(buf);\n \n-\td->given = 1;\n+\tstrbuf_add(&msg->buf, value, len);\n+\tfree(value);\n+\n+\tmsg->buf.len = len;\n+\tALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);\n+\td->messages[d->msg_nr - 1] = msg;\n+\tmsg->stripspace = 0;\n \treturn 0;\n }\n \n@@ -283,6 +332,16 @@ static int parse_reedit_arg(const struct option *opt, const char *arg, int unset\n \treturn parse_reuse_arg(opt, arg, unset);\n }\n \n+static int parse_separator_arg(const struct option *opt, const char *arg,\n+\t\t\t       int unset)\n+{\n+\tif (unset)\n+\t\t*(const char **)opt->value = NULL;\n+\telse\n+\t\t*(const char **)opt->value = arg ? arg : \"\\n\";\n+\treturn 0;\n+}\n+\n static int notes_copy_from_stdin(int force, const char *rewrite_cmd)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -406,6 +465,7 @@ static int add(int argc, const char **argv, const char *prefix)\n \tstruct object_id object, new_note;\n \tconst struct object_id *note;\n \tstruct note_data d = { .buf = STRBUF_INIT };\n+\n \tstruct option options[] = {\n \t\tOPT_CALLBACK_F('m', \"message\", &d, N_(\"message\"),\n \t\t\tN_(\"note contents as a string\"), PARSE_OPT_NONEG,\n@@ -422,6 +482,10 @@ static int add(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"allow-empty\", &allow_empty,\n \t\t\tN_(\"allow storing empty note\")),\n \t\tOPT__FORCE(&force, N_(\"replace existing notes\"), PARSE_OPT_NOCOMPLETE),\n+\t\tOPT_CALLBACK_F(0, \"separator\", &separator,\n+\t\t\tN_(\"<paragraph-break>\"),\n+\t\t\tN_(\"insert <paragraph-break> between paragraphs\"),\n+\t\t\tPARSE_OPT_OPTARG, parse_separator_arg),\n \t\tOPT_END()\n \t};\n \n@@ -433,6 +497,10 @@ static int add(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(git_notes_add_usage, options);\n \t}\n \n+\tif (d.msg_nr)\n+\t\tconcat_messages(&d);\n+\td.given = !!d.buf.len;\n+\n \tobject_ref = argc > 1 ? argv[1] : \"HEAD\";\n \n \tif (repo_get_oid(the_repository, object_ref, &object))\n@@ -587,6 +655,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \t\t\tparse_reuse_arg),\n \t\tOPT_BOOL(0, \"allow-empty\", &allow_empty,\n \t\t\tN_(\"allow storing empty note\")),\n+\t\tOPT_CALLBACK_F(0, \"separator\", &separator,\n+\t\t\tN_(\"<paragraph-break>\"),\n+\t\t\tN_(\"insert <paragraph-break> between paragraphs\"),\n+\t\t\tPARSE_OPT_OPTARG, parse_separator_arg),\n \t\tOPT_END()\n \t};\n \tint edit = !strcmp(argv[0], \"edit\");\n@@ -600,6 +672,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(usage, options);\n \t}\n \n+\tif (d.msg_nr)\n+\t\tconcat_messages(&d);\n+\td.given = !!d.buf.len;\n+\n \tif (d.given && edit)\n \t\tfprintf(stderr, _(\"The -m/-F/-c/-C options have been deprecated \"\n \t\t\t\"for the 'edit' subcommand.\\n\"\n@@ -623,7 +699,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \t\t\t\t\t\t       &type, &size);\n \n \t\tif (d.buf.len && prev_buf && size)\n-\t\t\tstrbuf_insertstr(&d.buf, 0, \"\\n\");\n+\t\t\tinsert_separator(&d.buf, 0);\n \t\tif (prev_buf && size)\n \t\t\tstrbuf_insert(&d.buf, 0, prev_buf, size);\n \t\tfree(prev_buf);\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex 3288aaec..d734000d 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -362,6 +362,7 @@ test_expect_success 'do not create empty note with -m \"\"' '\n '\n \n test_expect_success 'create note with combination of -m and -F' '\n+\ttest_when_finished git notes remove HEAD &&\n \tcat >expect-combine_m_and_F <<-EOF &&\n \t\tfoo\n \n@@ -380,6 +381,41 @@ test_expect_success 'create note with combination of -m and -F' '\n \ttest_cmp expect-combine_m_and_F actual\n '\n \n+test_expect_success 'create note with combination of -m and -F and --separator' '\n+\ttest_when_finished git notes remove HEAD &&\n+\tcat >expect-combine_m_and_F <<-\\EOF &&\n+\tfoo\n+\t-------\n+\txyzzy\n+\t-------\n+\tbar\n+\t-------\n+\tzyxxy\n+\t-------\n+\tbaz\n+\tEOF\n+\techo \"xyzzy\" >note_a &&\n+\techo \"zyxxy\" >note_b &&\n+\tgit notes add -m \"foo\" -F note_a -m \"bar\" -F note_b -m \"baz\" --separator=\"-------\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect-combine_m_and_F actual\n+'\n+\n+test_expect_success 'create note with combination of -m and -F and --no-separator' '\n+\tcat >expect-combine_m_and_F <<-\\EOF &&\n+\tfoo\n+\txyzzy\n+\tbar\n+\tzyxxy\n+\tbaz\n+\tEOF\n+\techo \"xyzzy\" >note_a &&\n+\techo \"zyxxy\" >note_b &&\n+\tgit notes add -m \"foo\" -F note_a -m \"bar\" -F note_b -m \"baz\" --no-separator &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect-combine_m_and_F actual\n+'\n+\n test_expect_success 'remove note with \"git notes remove\"' '\n \tgit notes remove HEAD^ &&\n \tgit notes remove &&\n@@ -521,6 +557,112 @@ test_expect_success 'listing non-existing notes fails' '\n \ttest_must_be_empty actual\n '\n \n+test_expect_success 'append: specify a separator with an empty arg' '\n+\ttest_when_finished git notes remove HEAD &&\n+\tcat >expect <<-\\EOF &&\n+\tnotes-1\n+\n+\tnotes-2\n+\tEOF\n+\n+\tgit notes add -m \"notes-1\" &&\n+\tgit notes append --separator=\"\" -m \"notes-2\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append: specify a separator without arg' '\n+\ttest_when_finished git notes remove HEAD &&\n+\tcat >expect <<-\\EOF &&\n+\tnotes-1\n+\n+\tnotes-2\n+\tEOF\n+\n+\tgit notes add -m \"notes-1\" &&\n+\tgit notes append --separator -m \"notes-2\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append: specify as --no-separator' '\n+\ttest_when_finished git notes remove HEAD &&\n+\tcat >expect <<-\\EOF &&\n+\tnotes-1\n+\tnotes-2\n+\tEOF\n+\n+\tgit notes add -m \"notes-1\" &&\n+\tgit notes append --no-separator -m \"notes-2\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append: specify separator with line break' '\n+\ttest_when_finished git notes remove HEAD &&\n+\tcat >expect <<-\\EOF &&\n+\tnotes-1\n+\t-------\n+\tnotes-2\n+\tEOF\n+\n+\tgit notes add -m \"notes-1\" &&\n+\tgit notes append --separator=\"-------$LF\" -m \"notes-2\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append: specify separator without line break' '\n+\ttest_when_finished git notes remove HEAD &&\n+\tcat >expect <<-\\EOF &&\n+\tnotes-1\n+\t-------\n+\tnotes-2\n+\tEOF\n+\n+\tgit notes add -m \"notes-1\" &&\n+\tgit notes append --separator=\"-------\" -m \"notes-2\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append: specify separator with multiple messages' '\n+\ttest_when_finished git notes remove HEAD &&\n+\tcat >expect <<-\\EOF &&\n+\tnotes-1\n+\t-------\n+\tnotes-2\n+\t-------\n+\tnotes-3\n+\tEOF\n+\n+\tgit notes add -m \"notes-1\" &&\n+\tgit notes append --separator=\"-------\" -m \"notes-2\" -m \"notes-3\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append note with combination of -m and -F and --separator' '\n+\ttest_when_finished git notes remove HEAD &&\n+\tcat >expect-combine_m_and_F <<-\\EOF &&\n+\tm-notes-1\n+\t-------\n+\tf-notes-1\n+\t-------\n+\tm-notes-2\n+\t-------\n+\tf-notes-2\n+\t-------\n+\tm-notes-3\n+\tEOF\n+\n+\techo \"f-notes-1\" >note_a &&\n+\techo \"f-notes-2\" >note_b &&\n+\tgit notes append -m \"m-notes-1\" -F note_a -m \"m-notes-2\" -F note_b -m \"m-notes-3\" --separator=\"-------\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect-combine_m_and_F actual\n+'\n+\n test_expect_success 'append to existing note with \"git notes append\"' '\n \tcat >expect <<-EOF &&\n \t\tInitial set of notes\n@@ -818,6 +960,33 @@ test_expect_success 'create note from blob with \"git notes add -C\" reuses blob i\n \ttest_cmp blob actual\n '\n \n+test_expect_success 'create note from blob with \"-C\", also specify \"-m\", \"-F\" and \"--separator\"' '\n+\t# 8th will be reuseed in following tests, so rollback when the test is done\n+\ttest_when_finished \"git notes remove && git notes add -C $(cat blob)\" &&\n+\tcommit=$(git rev-parse HEAD) &&\n+\tcat >expect <<-EOF &&\n+\t\tcommit $commit\n+\t\tAuthor: A U Thor <author@example.com>\n+\t\tDate:   Thu Apr 7 15:20:13 2005 -0700\n+\n+\t\t${indent}8th\n+\n+\t\tNotes:\n+\t\t${indent}This is a blob object\n+\t\t${indent}-------\n+\t\t${indent}This is created by -m\n+\t\t${indent}-------\n+\t\t${indent}This is created by -F\n+\tEOF\n+\n+\tgit notes remove &&\n+\techo \"This is a blob object\" | git hash-object -w --stdin >blob &&\n+\techo \"This is created by -F\" >note_a &&\n+\tgit notes add -C $(cat blob) -m \"This is created by -m\" -F note_a --separator=\"-------\" &&\n+\tgit log -1 >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'create note from other note with \"git notes add -c\"' '\n \ttest_commit 9th &&\n \tcommit=$(git rev-parse HEAD) &&\n-- \n2.40.0.356.g67a1c1d0\n\n"},{"id":"477519","messageId":"d65f067cb216bb8ce6824f8567c2ac5d958ec54c.1684411136.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1684411136.git.dyroneteng@gmail.com","subject":"[PATCH v10 6/6] notes.c: introduce \"--[no-]stripspace\" option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-18T12:02:11Z","receivedAt":"2023-05-18T12:02:48Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\nThis commit introduces a new option \"--[no-]stripspace\" to git notes\nappend, git notes edit, and git notes add. This option allows users to\ncontrol whether the note message need to stripped out.\n\nFor the consideration of backward compatibility, let's look at the\nbehavior about \"stripspace\" in \"git notes\" command:\n\n1. \"Edit Message\" case: using the default editor to edit the note\nmessage.\n\n    In \"edit\" case, the edited message will always be stripped out, the\n    implementation which can be found in the \"prepare_note_data()\". In\n    addition, the \"-c\" option supports to reuse an existing blob as a\n    note message, then open the editor to make a further edition on it,\n    the edited message will be stripped.\n\n    This commit doesn't change the default behavior of \"edit\" case by\n    using an enum \"notes_stripspace\", only when \"--no-stripspace\" option\n    is specified, the note message will not be stripped out. If you do\n    not specify the option or you specify \"--stripspace\", clearly, the\n    note message will be stripped out.\n\n2. \"Assign Message\" case: using the \"-m\"/\"-F\"/\"-C\" option to specify the\nnote message.\n\n    In \"assign\" case, when specify message by \"-m\" or \"-F\", the message\n    will be stripped out by default, but when specify message by \"-C\",\n    the message will be copied verbatim, in other word, the message will\n    not be stripped out. One more thing need to note is \"the order of\n    the options matter\", that is, if you specify \"-C\" before \"-m\" or\n    \"-F\", the reused message by \"-C\" will be stripped out together,\n    because everytime concat \"-m\" or \"-F\" message, the concated message\n    will be stripped together. Oppositely, if you specify \"-m\" or \"-F\"\n    before \"-C\", the reused message by \"-C\" will not be stripped out.\n\n    This commit doesn't change the default behavior of \"assign\" case by\n    extending the \"stripspace\" field in \"struct note_msg\", so we can\n    distinguish the different behavior of \"-m\"/\"-F\" and \"-C\" options\n    when we need to parse and concat the message.\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n Documentation/git-notes.txt |  25 ++-\n builtin/notes.c             |  34 +++--\n t/t3321-notes-stripspace.sh | 296 +++++++++++++++++++++++++++++++++++-\n 3 files changed, 334 insertions(+), 21 deletions(-)\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex 56d25a79..bc1bfa37 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -9,10 +9,10 @@ SYNOPSIS\n --------\n [verse]\n 'git notes' [list [<object>]]\n-'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n+'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n 'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )\n-'git notes' append [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n-'git notes' edit [--allow-empty] [<object>]\n+'git notes' append [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n+'git notes' edit [--allow-empty] [<object>] [--[no-]stripspace]\n 'git notes' show [<object>]\n 'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>\n 'git notes' merge --commit [-v | -q]\n@@ -141,20 +141,26 @@ OPTIONS\n \tIf multiple `-m` options are given, their values\n \tare concatenated as separate paragraphs.\n \tLines starting with `#` and empty lines other than a\n-\tsingle line between paragraphs will be stripped out.\n+\tsingle line between paragraphs will be stripped out,\n+\tif you wish to keep them verbatim, use `--no-stripspace`.\n \n -F <file>::\n --file=<file>::\n \tTake the note message from the given file.  Use '-' to\n \tread the note message from the standard input.\n \tLines starting with `#` and empty lines other than a\n-\tsingle line between paragraphs will be stripped out.\n+\tsingle line between paragraphs will be stripped out,\n+\tif you wish to keep them verbatim, use with\n+\t`--no-stripspace` option.\n \n -C <object>::\n --reuse-message=<object>::\n \tTake the given blob object (for example, another note) as the\n \tnote message. (Use `git notes copy <object>` instead to\n-\tcopy notes between objects.)\n+\tcopy notes between objects.).  By default, message will be\n+\tcopied verbatim, but if you wish to strip out the lines\n+\tstarting with `#` and empty lines other than a single line\n+\tbetween paragraphs, use with`--stripspace` option.\n \n -c <object>::\n --reedit-message=<object>::\n@@ -171,6 +177,13 @@ OPTIONS\n \tseparators will be added between paragraphs.  Defaults to a blank\n \tline.\n \n+--[no-]stripspace::\n+\tStrip leading and trailing whitespace from the note message.\n+\tAlso strip out empty lines other than a single line between\n+\tparagraphs. For lines starting with `#` will be stripped out\n+\tin non-editor cases like \"-m\", \"-F\" and \"-C\", but not in\n+\teditor case like \"git notes edit\", \"-c\", etc.\n+\n --ref <ref>::\n \tManipulate the notes tree in <ref>.  This overrides\n \t`GIT_NOTES_REF` and the \"core.notesRef\" configuration.  The ref\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 84c010fe..61e0b227 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -31,9 +31,9 @@\n static const char *separator = \"\\n\";\n static const char * const git_notes_usage[] = {\n \tN_(\"git notes [--ref <notes-ref>] [list [<object>]]\"),\n-\tN_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n+\tN_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [--[no-]stripspace] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n \tN_(\"git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>\"),\n-\tN_(\"git notes [--ref <notes-ref>] append [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n+\tN_(\"git notes [--ref <notes-ref>] append [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [--[no-]stripspace] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n \tN_(\"git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]\"),\n \tN_(\"git notes [--ref <notes-ref>] show [<object>]\"),\n \tN_(\"git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>\"),\n@@ -101,14 +101,21 @@ static const char * const git_notes_get_ref_usage[] = {\n static const char note_template[] =\n \tN_(\"Write/edit the notes for the following object:\");\n \n+enum notes_stripspace {\n+\tUNSPECIFIED = -1,\n+\tNO_STRIPSPACE = 0,\n+\tSTRIPSPACE = 1,\n+};\n+\n struct note_msg {\n-\tint stripspace;\n+\tenum notes_stripspace stripspace;\n \tstruct strbuf buf;\n };\n \n struct note_data {\n \tint given;\n \tint use_editor;\n+\tint stripspace;\n \tchar *edit_path;\n \tstruct strbuf buf;\n \tstruct note_msg **messages;\n@@ -213,7 +220,8 @@ static void prepare_note_data(const struct object_id *object, struct note_data *\n \t\tif (launch_editor(d->edit_path, &d->buf, NULL)) {\n \t\t\tdie(_(\"please supply the note contents using either -m or -F option\"));\n \t\t}\n-\t\tstrbuf_stripspace(&d->buf, 1);\n+\t\tif (d->stripspace)\n+\t\t\tstrbuf_stripspace(&d->buf, 1);\n \t}\n }\n \n@@ -249,7 +257,9 @@ static void concat_messages(struct note_data *d)\n \t\t\tappend_separator(&d->buf);\n \t\tstrbuf_add(&msg, d->messages[i]->buf.buf, d->messages[i]->buf.len);\n \t\tstrbuf_addbuf(&d->buf, &msg);\n-\t\tif (d->messages[i]->stripspace)\n+\t\tif ((d->stripspace == UNSPECIFIED &&\n+\t\t     d->messages[i]->stripspace == STRIPSPACE) ||\n+\t\t    d->stripspace == STRIPSPACE)\n \t\t\tstrbuf_stripspace(&d->buf, 0);\n \t\tstrbuf_reset(&msg);\n \t}\n@@ -267,7 +277,7 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)\n \tstrbuf_addstr(&msg->buf, arg);\n \tALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);\n \td->messages[d->msg_nr - 1] = msg;\n-\tmsg->stripspace = 1;\n+\tmsg->stripspace = STRIPSPACE;\n \treturn 0;\n }\n \n@@ -287,7 +297,7 @@ static int parse_file_arg(const struct option *opt, const char *arg, int unset)\n \n \tALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);\n \td->messages[d->msg_nr - 1] = msg;\n-\tmsg->stripspace = 1;\n+\tmsg->stripspace = STRIPSPACE;\n \treturn 0;\n }\n \n@@ -320,7 +330,7 @@ static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)\n \tmsg->buf.len = len;\n \tALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);\n \td->messages[d->msg_nr - 1] = msg;\n-\tmsg->stripspace = 0;\n+\tmsg->stripspace = NO_STRIPSPACE;\n \treturn 0;\n }\n \n@@ -464,7 +474,7 @@ static int add(int argc, const char **argv, const char *prefix)\n \tstruct notes_tree *t;\n \tstruct object_id object, new_note;\n \tconst struct object_id *note;\n-\tstruct note_data d = { .buf = STRBUF_INIT };\n+\tstruct note_data d = { .buf = STRBUF_INIT, .stripspace = UNSPECIFIED };\n \n \tstruct option options[] = {\n \t\tOPT_CALLBACK_F('m', \"message\", &d, N_(\"message\"),\n@@ -486,6 +496,8 @@ static int add(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"<paragraph-break>\"),\n \t\t\tN_(\"insert <paragraph-break> between paragraphs\"),\n \t\t\tPARSE_OPT_OPTARG, parse_separator_arg),\n+\t\tOPT_BOOL(0, \"stripspace\", &d.stripspace,\n+\t\t\tN_(\"remove unnecessary whitespace\")),\n \t\tOPT_END()\n \t};\n \n@@ -639,7 +651,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \tconst struct object_id *note;\n \tchar *logmsg;\n \tconst char * const *usage;\n-\tstruct note_data d = { .buf = STRBUF_INIT };\n+\tstruct note_data d = { .buf = STRBUF_INIT, .stripspace = UNSPECIFIED };\n \tstruct option options[] = {\n \t\tOPT_CALLBACK_F('m', \"message\", &d, N_(\"message\"),\n \t\t\tN_(\"note contents as a string\"), PARSE_OPT_NONEG,\n@@ -659,6 +671,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"<paragraph-break>\"),\n \t\t\tN_(\"insert <paragraph-break> between paragraphs\"),\n \t\t\tPARSE_OPT_OPTARG, parse_separator_arg),\n+\t\tOPT_BOOL(0, \"stripspace\", &d.stripspace,\n+\t\t\tN_(\"remove unnecessary whitespace\")),\n \t\tOPT_END()\n \t};\n \tint edit = !strcmp(argv[0], \"edit\");\ndiff --git a/t/t3321-notes-stripspace.sh b/t/t3321-notes-stripspace.sh\nindex 89977873..028d825e 100755\n--- a/t/t3321-notes-stripspace.sh\n+++ b/t/t3321-notes-stripspace.sh\n@@ -32,7 +32,7 @@ test_expect_success 'add note by editor' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'add note by specifying single \"-m\"' '\n+test_expect_success 'add note by specifying single \"-m\", \"--stripspace\" is the default behavior' '\n \ttest_when_finished \"git notes remove\" &&\n \tcat >expect <<-EOF &&\n \tfirst-line\n@@ -42,10 +42,26 @@ test_expect_success 'add note by specifying single \"-m\"' '\n \n \tgit notes add -m \"${LF}first-line${MULTI_LF}second-line${LF}\" &&\n \tgit notes show >actual &&\n+\ttest_cmp expect actual &&\n+\tgit notes remove &&\n+\tgit notes add --stripspace -m \"${LF}first-line${MULTI_LF}second-line${LF}\" &&\n+\tgit notes show >actual &&\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'add note by specifying multiple \"-m\"' '\n+test_expect_success 'add note by specifying single \"-m\" and \"--no-stripspace\" ' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\t${LF}first-line${MULTI_LF}second-line\n+\tEOF\n+\n+\tgit notes add --no-stripspace \\\n+\t\t      -m \"${LF}first-line${MULTI_LF}second-line${LF}\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying multiple \"-m\", \"--stripspace\" is the default behavior' '\n \ttest_when_finished \"git notes remove\" &&\n \tcat >expect <<-EOF &&\n \tfirst-line\n@@ -59,9 +75,156 @@ test_expect_success 'add note by specifying multiple \"-m\"' '\n \t\t      -m \"second-line\" \\\n \t\t      -m \"${LF}\" &&\n \tgit notes show >actual &&\n+\ttest_cmp expect actual &&\n+\tgit notes remove &&\n+\tgit notes add --stripspace -m \"${LF}\" \\\n+\t\t      -m \"first-line\" \\\n+\t\t      -m \"${MULTI_LF}\" \\\n+\t\t      -m \"second-line\" \\\n+\t\t      -m \"${LF}\" &&\n+\tgit notes show >actual &&\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'add notes by specifying multiple \"-m\" and \"--no-stripspace\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line${LF}\n+\tEOF\n+\n+\tgit notes add --no-stripspace \\\n+\t\t      -m \"${LF}\" \\\n+\t\t      -m \"first-line\" \\\n+\t\t      -m \"${MULTI_LF}\" \\\n+\t\t      -m \"second-line\" \\\n+\t\t      -m \"${LF}\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying single \"-F\", \"--stripspace\" is the default behavior' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tcat >note-file <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add -F note-file &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual &&\n+\tgit notes remove &&\n+\tgit notes add --stripspace -F note-file &&\n+\tgit notes show >actual\n+'\n+\n+test_expect_success 'add note by specifying single \"-F\" and \"--no-stripspace\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add --no-stripspace -F note-file &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying multiple \"-F\", \"--stripspace\" is the default behavior' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfile-1-first-line\n+\n+\tfile-1-second-line\n+\n+\tfile-2-first-line\n+\n+\tfile-2-second-line\n+\tEOF\n+\n+\tcat >note-file-1 <<-EOF &&\n+\t${LF}\n+\tfile-1-first-line\n+\t${MULTI_LF}\n+\tfile-1-second-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file-2 <<-EOF &&\n+\t${LF}\n+\tfile-2-first-line\n+\t${MULTI_LF}\n+\tfile-2-second-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add -F note-file-1 -F note-file-2 &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual &&\n+\tgit notes remove &&\n+\tgit notes add --stripspace -F note-file-1 -F note-file-2 &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying multiple \"-F\" with \"--no-stripspace\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\t${LF}\n+\tfile-1-first-line\n+\t${MULTI_LF}\n+\tfile-1-second-line\n+\t${LF}\n+\n+\t${LF}\n+\tfile-2-first-line\n+\t${MULTI_LF}\n+\tfile-2-second-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file-1 <<-EOF &&\n+\t${LF}\n+\tfile-1-first-line\n+\t${MULTI_LF}\n+\tfile-1-second-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file-2 <<-EOF &&\n+\t${LF}\n+\tfile-2-first-line\n+\t${MULTI_LF}\n+\tfile-2-second-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add --no-stripspace -F note-file-1 -F note-file-2 &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n \n test_expect_success 'append note by editor' '\n \ttest_when_finished \"git notes remove\" &&\n@@ -221,6 +384,45 @@ test_expect_success 'append notes by specifying multiple \"-F\"' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'append note by specifying multiple \"-F\" with \"--no-stripspace\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tinitial-line\n+\t${LF}${LF}\n+\tfile-1-first-line\n+\t${MULTI_LF}\n+\tfile-1-second-line\n+\t${LF}\n+\n+\t${LF}\n+\tfile-2-first-line\n+\t${MULTI_LF}\n+\tfile-2-second-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file-1 <<-EOF &&\n+\t${LF}\n+\tfile-1-first-line\n+\t${MULTI_LF}\n+\tfile-1-second-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file-2 <<-EOF &&\n+\t${LF}\n+\tfile-2-first-line\n+\t${MULTI_LF}\n+\tfile-2-second-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add -m \"initial-line\" &&\n+\tgit notes append --no-stripspace -F note-file-1 -F note-file-2 &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'add notes with empty messages' '\n \trev=$(git rev-parse HEAD) &&\n \tgit notes add -m \"${LF}\" \\\n@@ -229,7 +431,7 @@ test_expect_success 'add notes with empty messages' '\n \ttest_i18ngrep \"Removing note for object\" actual\n '\n \n-test_expect_success 'add note by specifying \"-C\" , do not stripspace is the default behavior' '\n+test_expect_success 'add note by specifying \"-C\", \"--no-stripspace\" is the default behavior' '\n \ttest_when_finished \"git notes remove\" &&\n \tcat >expect <<-EOF &&\n \t${LF}\n@@ -242,10 +444,36 @@ test_expect_success 'add note by specifying \"-C\" , do not stripspace is the defa\n \tcat expect | git hash-object -w --stdin >blob &&\n \tgit notes add -C $(cat blob) &&\n \tgit notes show >actual &&\n+\ttest_cmp expect actual &&\n+\tgit notes remove &&\n+\tgit notes add --no-stripspace -C $(cat blob) &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'reuse note by specifying \"-C\" and \"--stripspace\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >data <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tcat data | git hash-object -w --stdin >blob &&\n+\tgit notes add --stripspace -C $(cat blob) &&\n+\tgit notes show >actual &&\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'add notes with \"-C\" and \"-m\", \"-m\" will stripspace all together' '\n+test_expect_success 'reuse with \"-C\" and add note with \"-m\", \"-m\" will stripspace all together' '\n \ttest_when_finished \"git notes remove\" &&\n \tcat >data <<-EOF &&\n \t${LF}\n@@ -269,7 +497,7 @@ test_expect_success 'add notes with \"-C\" and \"-m\", \"-m\" will stripspace all toge\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'add notes with \"-m\" and \"-C\", \"-C\" will not stripspace all together' '\n+test_expect_success 'add note with \"-m\" and reuse note with \"-C\", \"-C\" will not stripspace all together' '\n \ttest_when_finished \"git notes remove\" &&\n \tcat >data <<-EOF &&\n \n@@ -288,4 +516,62 @@ test_expect_success 'add notes with \"-m\" and \"-C\", \"-C\" will not stripspace all\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'add note by specifying \"-c\", \"--stripspace\" is the default behavior' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\techo \"initial-line\" | git hash-object -w --stdin >blob &&\n+\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes add -c $(cat blob) &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual &&\n+\tgit notes remove &&\n+\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes add --stripspace -c $(cat blob) &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying \"-c\" with \"--no-stripspace\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\t${LF}first-line${MULTI_LF}second-line${LF}\n+\tEOF\n+\n+\techo \"initial-line\" | git hash-object -w --stdin >blob &&\n+\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes add --no-stripspace -c $(cat blob) &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'edit note by specifying \"-c\", \"--stripspace\" is the default behavior' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes edit &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual &&\n+\tgit notes remove &&\n+\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes edit --stripspace &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'edit note by specifying \"-c\" with \"--no-stripspace\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\t${LF}first-line${MULTI_LF}second-line${LF}\n+\tEOF\n+\n+\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes add --no-stripspace &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.40.0.356.g67a1c1d0\n\n"},{"id":"477523","messageId":"3f86a553-246a-4626-b1bd-bacd8148318a@app.fastmail.com","threadId":"59667","inReplyTo":"cover.1684411136.git.dyroneteng@gmail.com","subject":"Re: [PATCH v10 0/6] notes.c: introduce \"--separator\" option","fromName":"Kristoffer Haugsbakk","fromEmail":"code@khaugsbakk.name","sentAt":"2023-05-18T13:56:23Z","receivedAt":"2023-05-18T13:56:49Z","isPatch":true,"sender":{"key":"code@khaugsbakk.name","avatar":"https://avatars.githubusercontent.com/u/2229597?v=4"},"body":"On Thu, May 18, 2023, at 14:02, Teng Long wrote:\n> From: Teng Long <dyroneteng@gmail.com>\n>\n> Diff since v9:\n>\n> 1. [4/6] support `--no-separator` which means not to add any paragraph-breaks.\n\nNice to see. :) I think this will be useful. Specifically:\n\nJohan Herland wrote (2010):[1]\n> BTW, since I started talking about git notes, people on this list have found\n> more and more interesting use cases for them:\n>\n> […]\n>\n> - Help in bug tracking with header-like lines such as:\n>     - Causes-Bug: #12345\n>     - Fixes-Bug: #54321\n>\n> - Store after-the-fact \"Acked-By\", \"Reviewed-By\", etc. annotations\n\nThis switch is nice for that since you you don’t really want\nblank lines for those kinds of format.\n\nCheers\n\n[1]: https://lore.kernel.org/git/201001201148.11701.johan@herland.net/\n\n-- \nKristoffer Haugsbakk\n"},{"id":"477524","messageId":"51690df7-1788-469f-aabe-84fc7a576951@app.fastmail.com","threadId":"59667","inReplyTo":"820dda0458994fdf7ff37870736ce6ed7871720c.1684411136.git.dyroneteng@gmail.com","subject":"Re: [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option","fromName":"Kristoffer Haugsbakk","fromEmail":"code@khaugsbakk.name","sentAt":"2023-05-18T14:34:11Z","receivedAt":"2023-05-18T14:34:38Z","isPatch":true,"sender":{"key":"code@khaugsbakk.name","avatar":"https://avatars.githubusercontent.com/u/2229597?v=4"},"body":"Hi\n\nOn Thu, May 18, 2023, at 14:02, Teng Long wrote:\n> From: Teng Long <dyroneteng@gmail.com>\n>\n> When adding new notes or appending to an existing notes, we will\n> insert a blank line between the paragraphs, like:\n\nI was wondering why it acts this way. So of minor historical note (may\nbe obvious to everyone else): it was in order to mirror how `git commit\n-m` works. From commit d9246d4303f (Teach \"-m <msg>\" and \"-F <file>\" to\n\"git notes edit\", 2009-10-09):\n\n> Teach \"-m <msg>\" and \"-F <file>\" to \"git notes edit\"\n>\n> The \"-m\" and \"-F\" options are already the established method\n> (in both git-commit and git-tag) to specify a commit/tag message\n> without invoking the editor. This patch teaches \"git notes edit\"\n> to respect the same options for specifying a notes message without\n> invoking the editor.\n>\n> Multiple \"-m\" and/or \"-F\" options are concatenated as separate\n> paragraphs.\n\n-- \nKristoffer Haugsbakk\n"},{"id":"477525","messageId":"xmqqfs7tvfc7.fsf@gitster.g","threadId":"59667","inReplyTo":"cover.1684411136.git.dyroneteng@gmail.com","subject":"Re: [PATCH v10 0/6] notes.c: introduce \"--separator\" option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-05-18T15:17:44Z","receivedAt":"2023-05-18T15:17:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Teng Long <dyroneteng@gmail.com> writes:\n\n> From: Teng Long <dyroneteng@gmail.com>\n>\n> Diff since v9:\n>\n> 1. [4/6] support `--no-separator` which means not to add any paragraph-breaks.\n> 2. [4/6] Fix the problems by the Junio's suggestion [1]\n>\n> [1] https://public-inbox.org/git/xmqqsfcjbuud.fsf@gitster.g/\n\nThanks for an update, but it was a bit unexpected to see a full\nreroll instead of a new patch to add --no-separator support on top\nof what has already been queued in 'next' for the past 10+ days.\n\n\n\n\n\n"},{"id":"477579","messageId":"20230519005447.GA2955320@coredump.intra.peff.net","threadId":"59667","inReplyTo":"820dda0458994fdf7ff37870736ce6ed7871720c.1684411136.git.dyroneteng@gmail.com","subject":"Re: [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2023-05-19T00:54:47Z","receivedAt":"2023-05-19T00:55:14Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, May 18, 2023 at 08:02:09PM +0800, Teng Long wrote:\n\n> +static void insert_separator(struct strbuf *message, size_t pos)\n> +{\n> +\tif (!separator)\n> +\t\treturn;\n> +\telse if (separator[strlen(separator) - 1] == '\\n')\n> +\t\tstrbuf_insertstr(message, pos, separator);\n> +\telse\n> +\t\tstrbuf_insertf(message, pos, \"%s%s\", separator, \"\\n\");\n> +}\n\nThis function causes UBSan to complain on 'next' (though curiously only\nwith clang, not with gcc[1]). The version in next seems to be from your\nv9, but it's largely the same except for the \"if (!separator)\"\ncondition.\n\nThe problem is in the middle condition here. If \"separator\" is non-NULL,\nbut is an empty string, then strlen() will return 0, and we will look at\nthe out-of-bounds byte just before the string.\n\nWe'd probably want something like this:\n\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 3215bce19b..a46d6dac5c 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -231,7 +231,8 @@ static void write_note_data(struct note_data *d, struct object_id *oid)\n \n static void insert_separator(struct strbuf *message, size_t pos)\n {\n-\tif (separator[strlen(separator) - 1] == '\\n')\n+\tsize_t sep_len = strlen(separator);\n+\tif (sep_len && separator[sep_len - 1] == '\\n')\n \t\tstrbuf_addstr(message, separator);\n \telse\n \t\tstrbuf_insertf(message, pos, \"%s%s\", separator, \"\\n\");\n\nto fix it, though I am not 100% clear on what is supposed to happen for\nan empty separator here.\n\nI was also confused that applying the fix on top of the culprit in\n'next', 3993a53a13 (notes.c: introduce '--separator=<paragraph-break>'\noption, 2023-04-28), still leads to test failures in t3301. But I think\nthat is independent of this fix. It fails even without my patch above\n(and without UBSan) in test 66, \"append: specify separator with line\nbreak\". But the failure goes away in the following patch, ad3d1f8feb\n(notes.c: append separator instead of insert by pos, 2023-04-28).\n\nI haven't been following this series enough to know what's going on, but\nyou may want to figure out where the failure is coming from in\n3993a53a13. If the change in ad3d1f8feb is merely papering over it, then\nwe'd need to find and fix the true cause. If the bug is really fixed by\nad3d1f8feb, we might want to squash those two together to avoid broken\nbisections.\n\n-Peff\n\n[1] To reproduce, I did:\n\n      git checkout 3993a53a13\n      make SANITIZE=address,undefined CC=clang\n      cd t && ./t3301-notes.sh -v -i\n\n    I'm using clang-14 on a Debian machine.\n"},{"id":"477618","messageId":"20230520102224.53522-1-tenglong.tl@alibaba-inc.com","threadId":"59667","inReplyTo":"3f86a553-246a-4626-b1bd-bacd8148318a@app.fastmail.com","subject":"Re: [PATCH v10 0/6] notes.c: introduce \"--separator\" option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-20T10:22:24Z","receivedAt":"2023-05-20T10:22:36Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"\"Kristoffer Haugsbakk\" <code@khaugsbakk.name> writes:\n\n#On Thu, May 18, 2023, at 14:02, Teng Long wrote:\n#> From: Teng Long <dyroneteng@gmail.com>\n#>\n#> Diff since v9:\n#>\n#> 1. [4/6] support `--no-separator` which means not to add any paragraph-breaks.\n#\n#Nice to see. :) I think this will be useful. Specifically:\n#\n#Johan Herland wrote (2010):[1]\n#> BTW, since I started talking about git notes, people on this list have found\n#> more and more interesting use cases for them:\n#>\n#> […]\n#>\n#> - Help in bug tracking with header-like lines such as:\n#>     - Causes-Bug: #12345\n#>     - Fixes-Bug: #54321\n#>\n#> - Store after-the-fact \"Acked-By\", \"Reviewed-By\", etc. annotations\n#\n#This switch is nice for that since you you don’t really want\n#blank lines for those kinds of format.\n#\n#Cheers\n#\n#[1]: https://lore.kernel.org/git/201001201148.11701.johan@herland.net/\n\nThanks. It's because of you that this function was incubated and possibly\nborn.\n"},{"id":"477619","messageId":"20230520104159.53670-1-tenglong.tl@alibaba-inc.com","threadId":"59667","inReplyTo":"51690df7-1788-469f-aabe-84fc7a576951@app.fastmail.com","subject":"Re: [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-20T10:41:59Z","receivedAt":"2023-05-20T10:42:13Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"\"Kristoffer Haugsbakk\" <code@khaugsbakk.name> writes:\n\n> > From: Teng Long <dyroneteng@gmail.com>\n> >\n> > When adding new notes or appending to an existing notes, we will\n> > insert a blank line between the paragraphs, like:\n> \n> I was wondering why it acts this way. So of minor historical note (may\n> be obvious to everyone else): it was in order to mirror how `git commit\n> -m` works. From commit d9246d4303f (Teach \"-m <msg>\" and \"-F <file>\" to\n> \"git notes edit\", 2009-10-09):\n\nYes, I noticed it too, but I'm not sure whether there exists the same\nrequirement in `git commit -m <message> -F <file> ...`, `git commit` is\nusually a more frequently used subcommand, firstly introduced it to\n`git notes` maybe is a good idea, but I'm not going to touch `git commit`\nyet, unless we find someone who has a strong need.\n\nThanks.\n"},{"id":"477620","messageId":"20230520105912.54001-1-tenglong.tl@alibaba-inc.com","threadId":"59667","inReplyTo":"xmqqfs7tvfc7.fsf@gitster.g","subject":"Re: [PATCH v10 0/6] notes.c: introduce \"--separator\" option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-20T10:59:12Z","receivedAt":"2023-05-20T10:59:25Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> > From: Teng Long <dyroneteng@gmail.com>\n> >\n> > Diff since v9:\n> >\n> > 1. [4/6] support `--no-separator` which means not to add any paragraph-breaks.\n> > 2. [4/6] Fix the problems by the Junio's suggestion [1]\n> >\n> > [1] https://public-inbox.org/git/xmqqsfcjbuud.fsf@gitster.g/\n> \n> Thanks for an update, but it was a bit unexpected to see a full\n> reroll instead of a new patch to add --no-separator support on top\n> of what has already been queued in 'next' for the past 10+ days.\n\nSorry, so if the topic already queued in 'next', make new patch\nbase on it is preferred, if not queued yet, wo should work on\nkeeping commit be atomic on it's duty.\n\nThanks.\n"},{"id":"477621","messageId":"e82748ad-e73f-4c67-9bc8-16107eeaf058@app.fastmail.com","threadId":"59667","inReplyTo":"20230520104159.53670-1-tenglong.tl@alibaba-inc.com","subject":"Re: [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option","fromName":"Kristoffer Haugsbakk","fromEmail":"code@khaugsbakk.name","sentAt":"2023-05-20T16:12:53Z","receivedAt":"2023-05-20T16:13:43Z","isPatch":true,"sender":{"key":"code@khaugsbakk.name","avatar":"https://avatars.githubusercontent.com/u/2229597?v=4"},"body":"Hi Teng Long\n\nOn Sat, May 20, 2023, at 12:41, Teng Long wrote:\n> Yes, I noticed it too, but I'm not sure whether there exists the same\n> requirement in `git commit -m <message> -F <file> ...`, `git commit` is\n> usually a more frequently used subcommand, firstly introduced it to\n> `git notes` maybe is a good idea, but I'm not going to touch `git commit`\n> yet, unless we find someone who has a strong need.\n\nYep, you’re correct. My intention wasn’t to imply that git-commit(1)\nneeded something like this, rather to explain (mostly to myself) why\ngit-notes(1) learned a sort of git-commit(1) option in the first place.\n\n`git commit -m` with its multiple paragraphs behavior makes perfect\nsense, since you are writing paragraphs of text, anyway. However, Git\nnotes are used for other things than that—like line-delimited data—so a\n`--[no-]-separator[=]` option makes great sense for git-notes(1) and\ncomplements it well.\n\nI wanted to figure out why `git notes append -m` works like it does\nsince I’ve been idly wondering about it, and I have seen others around\nthe Web who have been a little puzzled as well.\n\nCheers\n\n-- \nKristoffer Haugsbakk\n"},{"id":"477746","messageId":"20230527071721.24285-1-tenglong.tl@alibaba-inc.com","threadId":"59667","inReplyTo":"20230519005447.GA2955320@coredump.intra.peff.net","subject":"Re: [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-27T07:17:21Z","receivedAt":"2023-05-27T07:17:32Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> > +static void insert_separator(struct strbuf *message, size_t pos)\n> > +{\n> > +\tif (!separator)\n> > +\t\treturn;\n> > +\telse if (separator[strlen(separator) - 1] == '\\n')\n> > +\t\tstrbuf_insertstr(message, pos, separator);\n> > +\telse\n> > +\t\tstrbuf_insertf(message, pos, \"%s%s\", separator, \"\\n\");\n> > +}\n\n> This function causes UBSan to complain on 'next' (though curiously only\n> with clang, not with gcc[1]). The version in next seems to be from your\n> v9, but it's largely the same except for the \"if (!separator)\"\n> condition.\n> \n> The problem is in the middle condition here. If \"separator\" is non-NULL,\n> but is an empty string, then strlen() will return 0, and we will look at\n> the out-of-bounds byte just before the string.\n\nYou definitely correct, will fix.\n\n> This function causes UBSan to complain on 'next' (though curiously only\n> with clang, not with gcc[1]). The version in next seems to be from your\n> v9, but it's largely the same except for the \"if (!separator)\"\n> condition.\n> \n> The problem is in the middle condition here. If \"separator\" is non-NULL,\n> but is an empty string, then strlen() will return 0, and we will look at\n> the out-of-bounds byte just before the string.\n> \n> We'd probably want something like this:\n> \n> diff --git a/builtin/notes.c b/builtin/notes.c\n> index 3215bce19b..a46d6dac5c 100644\n> --- a/builtin/notes.c\n> +++ b/builtin/notes.c\n> @@ -231,7 +231,8 @@ static void write_note_data(struct note_data *d, struct object_id *oid)\n>  \n>  static void insert_separator(struct strbuf *message, size_t pos)\n>  {\n> -\tif (separator[strlen(separator) - 1] == '\\n')\n> +\tsize_t sep_len = strlen(separator);\n> +\tif (sep_len && separator[sep_len - 1] == '\\n')\n>  \t\tstrbuf_addstr(message, separator);\n>  \telse\n>  \t\tstrbuf_insertf(message, pos, \"%s%s\", separator, \"\\n\");\n> \n> to fix it, though I am not 100% clear on what is supposed to happen for\n> an empty separator here.\n\nIt's supposed to be the same behaviour with not to specify the option, which\nis the default behaviour(to use a '\\n' as the separator).\n\nThe diff looks good to me, will apply.\n\n> I was also confused that applying the fix on top of the culprit in\n> 'next', 3993a53a13 (notes.c: introduce '--separator=<paragraph-break>'\n> option, 2023-04-28), still leads to test failures in t3301. But I think\n> that is independent of this fix. It fails even without my patch above\n> (and without UBSan) in test 66, \"append: specify separator with line\n> break\". But the failure goes away in the following patch, ad3d1f8feb\n> (notes.c: append separator instead of insert by pos, 2023-04-28).\n\nYes, that's a problem which be taken in patch v9 4/6[1] at insert_separator(...)\n, we should use strbuf_insert* api here, otherwise will always do append\nbut not to do insert with the position, finally break the test.\n\nIn the v9 5/6 patch[2], I tried to remove the postion to simply the logic from\ninsert with position to just append, and this patch cover the test case failure\nin 4/6.\n\n> I haven't been following this series enough to know what's going on, but\n> you may want to figure out where the failure is coming from in\n> 3993a53a13. If the change in ad3d1f8feb is merely papering over it, then\n> we'd need to find and fix the true cause. If the bug is really fixed by\n> ad3d1f8feb, we might want to squash those two together to avoid broken\n> bisections.\n\nSure, we should avoid that, will fix.\n\n> [1] To reproduce, I did:\n> \n>       git checkout 3993a53a13\n>       make SANITIZE=address,undefined CC=clang\n>       cd t && ./t3301-notes.sh -v -i\n> \n>     I'm using clang-14 on a Debian machine.\n\nDo you always do the 'make' with 'SANITIZE=address,undefined', should I\nfollow that approach, may I ask you to give some advices about it?\n\nThanks.\n\n[1] https://public-inbox.org/git/ed930ef4f795f30792bc14d9c1939484e4976db8.1682671758.git.dyroneteng@gmail.com/\n[2] https://public-inbox.org/git/eea2246f44a3adfc4888db93975854448271032b.1682671758.git.dyroneteng@gmail.com/\n"},{"id":"477747","messageId":"cover.1685174011.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1684411136.git.dyroneteng@gmail.com","subject":"[PATCH v11 0/7] notes.c: introduce \"--separator\"","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-27T07:57:47Z","receivedAt":"2023-05-27T07:58:09Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\n1.  \"--[no-]separator\" to an independent patch which suggested by Junio[1].\n2.  fix \"UBSan\" problem which found by Peff[2].\n\n[1] https://public-inbox.org/git/xmqqfs7tvfc7.fsf@gitster.g/\n[2] https://public-inbox.org/git/20230519005447.GA2955320@coredump.intra.peff.net/\n\nThanks.\n\nTeng Long (7):\n  notes.c: cleanup 'strbuf_grow' call in 'append_edit'\n  notes.c: use designated initializers for clarity\n  t3321: add test cases about the notes stripspace behavior\n  notes.c: introduce '--separator=<paragraph-break>' option\n  notes.c: append separator instead of insert by pos\n  notes.c: introduce \"--[no-]stripspace\" option\n  notes: introduce \"--no-separator\" option\n\n Documentation/git-notes.txt |  43 ++-\n builtin/notes.c             | 159 +++++++---\n t/t3301-notes.sh            | 169 +++++++++++\n t/t3321-notes-stripspace.sh | 577 ++++++++++++++++++++++++++++++++++++\n 4 files changed, 906 insertions(+), 42 deletions(-)\n create mode 100755 t/t3321-notes-stripspace.sh\n\nRange-diff against v10:\n1:  0634434e = 1:  0634434e notes.c: cleanup 'strbuf_grow' call in 'append_edit'\n2:  4ad78405 = 2:  4ad78405 notes.c: use designated initializers for clarity\n3:  c2fc2091 = 3:  c2fc2091 t3321: add test cases about the notes stripspace behavior\n4:  820dda04 ! 4:  367cb1d4 notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option\n    @@ Metadata\n     Author: Teng Long <dyroneteng@gmail.com>\n     \n      ## Commit message ##\n    -    notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option\n    +    notes.c: introduce '--separator=<paragraph-break>' option\n     \n         When adding new notes or appending to an existing notes, we will\n         insert a blank line between the paragraphs, like:\n    @@ Commit message\n     \n         Both the two exections produce the same result.\n     \n    -    Alternatively, if you do not want any new paragraph\n    -    separators, even a newline by default, you can specify\n    -    '--no-separator'.\n    -\n         The reason we use a \"strbuf\" array to concat but not \"string_list\", is\n         that the binary file content may contain '\\0' in the middle, this will\n         cause the corrupt result if using a string to save.\n    @@ Documentation/git-notes.txt: SYNOPSIS\n      [verse]\n      'git notes' [list [<object>]]\n     -'git notes' add [-f] [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n    -+'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n    ++'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n      'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )\n     -'git notes' append [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n    -+'git notes' append [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n    ++'git notes' append [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n      'git notes' edit [--allow-empty] [<object>]\n      'git notes' show [<object>]\n      'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>\n    @@ Documentation/git-notes.txt: OPTIONS\n      \tAllow an empty note object to be stored. The default behavior is\n      \tto automatically remove empty notes.\n      \n    -+--[no-]separator, --separator=<paragraph-break>::\n    ++--separator <paragraph-break>::\n     +\tSpecify a string used as a custom inter-paragraph separator\n    -+\t(a newline is added at the end as needed). If `--no-separator`, no\n    -+\tseparators will be added between paragraphs.  Defaults to a blank\n    -+\tline.\n    ++\t(a newline is added at the end as needed).  Defaults to a\n    ++\tblank line.\n     +\n      --ref <ref>::\n      \tManipulate the notes tree in <ref>.  This overrides\n    @@ builtin/notes.c\n      #include \"worktree.h\"\n      #include \"write-or-die.h\"\n      \n    -+static const char *separator = \"\\n\";\n    ++static char *separator = \"\\n\";\n      static const char * const git_notes_usage[] = {\n      \tN_(\"git notes [--ref <notes-ref>] [list [<object>]]\"),\n     -\tN_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n    -+\tN_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n    ++\tN_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n      \tN_(\"git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>\"),\n     -\tN_(\"git notes [--ref <notes-ref>] append [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n    -+\tN_(\"git notes [--ref <notes-ref>] append [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n    ++\tN_(\"git notes [--ref <notes-ref>] append [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n      \tN_(\"git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]\"),\n      \tN_(\"git notes [--ref <notes-ref>] show [<object>]\"),\n      \tN_(\"git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>\"),\n    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_\n      \n     +static void insert_separator(struct strbuf *message, size_t pos)\n     +{\n    -+\tif (!separator)\n    -+\t\treturn;\n    -+\telse if (separator[strlen(separator) - 1] == '\\n')\n    ++\tsize_t sep_len = strlen(separator);\n    ++\tif (sep_len && separator[sep_len - 1] == '\\n')\n     +\t\tstrbuf_insertstr(message, pos, separator);\n     +\telse\n     +\t\tstrbuf_insertf(message, pos, \"%s%s\", separator, \"\\n\");\n    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_\n     +static void concat_messages(struct note_data *d)\n     +{\n     +\tstruct strbuf msg = STRBUF_INIT;\n    -+\tsize_t i;\n     +\n    ++\tsize_t i;\n     +\tfor (i = 0; i < d->msg_nr ; i++) {\n     +\t\tif (d->buf.len)\n     +\t\t\tinsert_separator(&d->buf, d->buf.len);\n    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_\n      \treturn 0;\n      }\n      \n    -@@ builtin/notes.c: static int parse_reedit_arg(const struct option *opt, const char *arg, int unset\n    - \treturn parse_reuse_arg(opt, arg, unset);\n    - }\n    - \n    -+static int parse_separator_arg(const struct option *opt, const char *arg,\n    -+\t\t\t       int unset)\n    -+{\n    -+\tif (unset)\n    -+\t\t*(const char **)opt->value = NULL;\n    -+\telse\n    -+\t\t*(const char **)opt->value = arg ? arg : \"\\n\";\n    -+\treturn 0;\n    -+}\n    -+\n    - static int notes_copy_from_stdin(int force, const char *rewrite_cmd)\n    - {\n    - \tstruct strbuf buf = STRBUF_INIT;\n     @@ builtin/notes.c: static int add(int argc, const char **argv, const char *prefix)\n      \tstruct object_id object, new_note;\n      \tconst struct object_id *note;\n    @@ builtin/notes.c: static int add(int argc, const char **argv, const char *prefix)\n      \t\tOPT_BOOL(0, \"allow-empty\", &allow_empty,\n      \t\t\tN_(\"allow storing empty note\")),\n      \t\tOPT__FORCE(&force, N_(\"replace existing notes\"), PARSE_OPT_NOCOMPLETE),\n    -+\t\tOPT_CALLBACK_F(0, \"separator\", &separator,\n    -+\t\t\tN_(\"<paragraph-break>\"),\n    -+\t\t\tN_(\"insert <paragraph-break> between paragraphs\"),\n    -+\t\t\tPARSE_OPT_OPTARG, parse_separator_arg),\n    ++\t\tOPT_STRING(0, \"separator\", &separator, N_(\"separator\"),\n    ++\t\t\tN_(\"insert <paragraph-break> between paragraphs\")),\n      \t\tOPT_END()\n      \t};\n      \n    @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char\n      \t\t\tparse_reuse_arg),\n      \t\tOPT_BOOL(0, \"allow-empty\", &allow_empty,\n      \t\t\tN_(\"allow storing empty note\")),\n    -+\t\tOPT_CALLBACK_F(0, \"separator\", &separator,\n    -+\t\t\tN_(\"<paragraph-break>\"),\n    -+\t\t\tN_(\"insert <paragraph-break> between paragraphs\"),\n    -+\t\t\tPARSE_OPT_OPTARG, parse_separator_arg),\n    ++\t\tOPT_STRING(0, \"separator\", &separator, N_(\"separator\"),\n    ++\t\t\tN_(\"insert <paragraph-break> between paragraphs\")),\n      \t\tOPT_END()\n      \t};\n      \tint edit = !strcmp(argv[0], \"edit\");\n    @@ t/t3301-notes.sh: test_expect_success 'create note with combination of -m and -F\n      '\n      \n     +test_expect_success 'create note with combination of -m and -F and --separator' '\n    -+\ttest_when_finished git notes remove HEAD &&\n     +\tcat >expect-combine_m_and_F <<-\\EOF &&\n     +\tfoo\n     +\t-------\n    @@ t/t3301-notes.sh: test_expect_success 'create note with combination of -m and -F\n     +\tEOF\n     +\techo \"xyzzy\" >note_a &&\n     +\techo \"zyxxy\" >note_b &&\n    -+\tgit notes add -m \"foo\" -F note_a -m \"bar\" -F note_b -m \"baz\" --separator=\"-------\" &&\n    -+\tgit notes show >actual &&\n    -+\ttest_cmp expect-combine_m_and_F actual\n    -+'\n    -+\n    -+test_expect_success 'create note with combination of -m and -F and --no-separator' '\n    -+\tcat >expect-combine_m_and_F <<-\\EOF &&\n    -+\tfoo\n    -+\txyzzy\n    -+\tbar\n    -+\tzyxxy\n    -+\tbaz\n    -+\tEOF\n    -+\techo \"xyzzy\" >note_a &&\n    -+\techo \"zyxxy\" >note_b &&\n    -+\tgit notes add -m \"foo\" -F note_a -m \"bar\" -F note_b -m \"baz\" --no-separator &&\n    ++\tgit notes add -m \"foo\" -F note_a -m \"bar\" -F note_b -m \"baz\" --separator \"-------\" &&\n     +\tgit notes show >actual &&\n     +\ttest_cmp expect-combine_m_and_F actual\n     +'\n    @@ t/t3301-notes.sh: test_expect_success 'listing non-existing notes fails' '\n      \ttest_must_be_empty actual\n      '\n      \n    -+test_expect_success 'append: specify a separator with an empty arg' '\n    ++test_expect_success 'append: specify an empty separator' '\n     +\ttest_when_finished git notes remove HEAD &&\n     +\tcat >expect <<-\\EOF &&\n     +\tnotes-1\n    @@ t/t3301-notes.sh: test_expect_success 'listing non-existing notes fails' '\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success 'append: specify a separator without arg' '\n    -+\ttest_when_finished git notes remove HEAD &&\n    -+\tcat >expect <<-\\EOF &&\n    -+\tnotes-1\n    -+\n    -+\tnotes-2\n    -+\tEOF\n    -+\n    -+\tgit notes add -m \"notes-1\" &&\n    -+\tgit notes append --separator -m \"notes-2\" &&\n    -+\tgit notes show >actual &&\n    -+\ttest_cmp expect actual\n    -+'\n    -+\n    -+test_expect_success 'append: specify as --no-separator' '\n    -+\ttest_when_finished git notes remove HEAD &&\n    -+\tcat >expect <<-\\EOF &&\n    -+\tnotes-1\n    -+\tnotes-2\n    -+\tEOF\n    -+\n    -+\tgit notes add -m \"notes-1\" &&\n    -+\tgit notes append --no-separator -m \"notes-2\" &&\n    -+\tgit notes show >actual &&\n    -+\ttest_cmp expect actual\n    -+'\n    -+\n     +test_expect_success 'append: specify separator with line break' '\n     +\ttest_when_finished git notes remove HEAD &&\n     +\tcat >expect <<-\\EOF &&\n    @@ t/t3301-notes.sh: test_expect_success 'listing non-existing notes fails' '\n     +\n     +\techo \"f-notes-1\" >note_a &&\n     +\techo \"f-notes-2\" >note_b &&\n    -+\tgit notes append -m \"m-notes-1\" -F note_a -m \"m-notes-2\" -F note_b -m \"m-notes-3\" --separator=\"-------\" &&\n    ++\tgit notes append -m \"m-notes-1\" -F note_a -m \"m-notes-2\" -F note_b -m \"m-notes-3\" --separator \"-------\" &&\n     +\tgit notes show >actual &&\n     +\ttest_cmp expect-combine_m_and_F actual\n     +'\n5:  76c93f19 ! 5:  4ba62a48 notes.c: append separator instead of insert by pos\n    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_\n     -static void insert_separator(struct strbuf *message, size_t pos)\n     +static void append_separator(struct strbuf *message)\n      {\n    - \tif (!separator)\n    - \t\treturn;\n    - \telse if (separator[strlen(separator) - 1] == '\\n')\n    + \tsize_t sep_len = strlen(separator);\n    + \tif (sep_len && separator[sep_len - 1] == '\\n')\n     -\t\tstrbuf_insertstr(message, pos, separator);\n     +\t\tstrbuf_addstr(message, separator);\n      \telse\n    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_\n      \n      static void concat_messages(struct note_data *d)\n     @@ builtin/notes.c: static void concat_messages(struct note_data *d)\n    - \n    + \tsize_t i;\n      \tfor (i = 0; i < d->msg_nr ; i++) {\n      \t\tif (d->buf.len)\n     -\t\t\tinsert_separator(&d->buf, d->buf.len);\n6:  d65f067c ! 6:  19865941 notes.c: introduce \"--[no-]stripspace\" option\n    @@ Documentation/git-notes.txt: SYNOPSIS\n      --------\n      [verse]\n      'git notes' [list [<object>]]\n    --'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n    -+'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n    +-'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n    ++'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n      'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )\n    --'git notes' append [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n    +-'git notes' append [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n     -'git notes' edit [--allow-empty] [<object>]\n    -+'git notes' append [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n    ++'git notes' append [--allow-empty] [--separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n     +'git notes' edit [--allow-empty] [<object>] [--[no-]stripspace]\n      'git notes' show [<object>]\n      'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>\n    @@ Documentation/git-notes.txt: OPTIONS\n      -c <object>::\n      --reedit-message=<object>::\n     @@ Documentation/git-notes.txt: OPTIONS\n    - \tseparators will be added between paragraphs.  Defaults to a blank\n    - \tline.\n    + \t(a newline is added at the end as needed).  Defaults to a\n    + \tblank line.\n      \n     +--[no-]stripspace::\n     +\tStrip leading and trailing whitespace from the note message.\n    @@ Documentation/git-notes.txt: OPTIONS\n      \t`GIT_NOTES_REF` and the \"core.notesRef\" configuration.  The ref\n     \n      ## builtin/notes.c ##\n    -@@\n    - static const char *separator = \"\\n\";\n    - static const char * const git_notes_usage[] = {\n    - \tN_(\"git notes [--ref <notes-ref>] [list [<object>]]\"),\n    --\tN_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n    -+\tN_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [--[no-]stripspace] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n    - \tN_(\"git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>\"),\n    --\tN_(\"git notes [--ref <notes-ref>] append [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n    -+\tN_(\"git notes [--ref <notes-ref>] append [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [--[no-]stripspace] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n    - \tN_(\"git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]\"),\n    - \tN_(\"git notes [--ref <notes-ref>] show [<object>]\"),\n    - \tN_(\"git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>\"),\n     @@ builtin/notes.c: static const char * const git_notes_get_ref_usage[] = {\n      static const char note_template[] =\n      \tN_(\"Write/edit the notes for the following object:\");\n    @@ builtin/notes.c: static int add(int argc, const char **argv, const char *prefix)\n      \tstruct option options[] = {\n      \t\tOPT_CALLBACK_F('m', \"message\", &d, N_(\"message\"),\n     @@ builtin/notes.c: static int add(int argc, const char **argv, const char *prefix)\n    - \t\t\tN_(\"<paragraph-break>\"),\n    - \t\t\tN_(\"insert <paragraph-break> between paragraphs\"),\n    - \t\t\tPARSE_OPT_OPTARG, parse_separator_arg),\n    + \t\tOPT__FORCE(&force, N_(\"replace existing notes\"), PARSE_OPT_NOCOMPLETE),\n    + \t\tOPT_STRING(0, \"separator\", &separator, N_(\"separator\"),\n    + \t\t\tN_(\"insert <paragraph-break> between paragraphs\")),\n     +\t\tOPT_BOOL(0, \"stripspace\", &d.stripspace,\n     +\t\t\tN_(\"remove unnecessary whitespace\")),\n      \t\tOPT_END()\n    @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char\n      \t\tOPT_CALLBACK_F('m', \"message\", &d, N_(\"message\"),\n      \t\t\tN_(\"note contents as a string\"), PARSE_OPT_NONEG,\n     @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char *prefix)\n    - \t\t\tN_(\"<paragraph-break>\"),\n    - \t\t\tN_(\"insert <paragraph-break> between paragraphs\"),\n    - \t\t\tPARSE_OPT_OPTARG, parse_separator_arg),\n    + \t\t\tN_(\"allow storing empty note\")),\n    + \t\tOPT_STRING(0, \"separator\", &separator, N_(\"separator\"),\n    + \t\t\tN_(\"insert <paragraph-break> between paragraphs\")),\n     +\t\tOPT_BOOL(0, \"stripspace\", &d.stripspace,\n     +\t\t\tN_(\"remove unnecessary whitespace\")),\n      \t\tOPT_END()\n-:  -------- > 7:  7105778c notes: introduce \"--no-separator\" option\n-- \n2.40.0.356.g367cb1d4\n\n"},{"id":"477748","messageId":"0634434e2a3e40ef4af5013349202491d55fe1d7.1685174012.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1685174011.git.dyroneteng@gmail.com","subject":"[PATCH v11 1/7] notes.c: cleanup 'strbuf_grow' call in 'append_edit'","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-27T07:57:48Z","receivedAt":"2023-05-27T07:58:10Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\nLet's cleanup the unnecessary 'strbuf_grow' call in 'append_edit'. This\n\"strbuf_grow(&d.buf, size + 1);\" is prepared for insert a blank line if\nneeded, but actually when inserting, \"strbuf_insertstr(&d.buf, 0,\n\"\\n\");\" will do the \"grow\" for us.\n\n348f199b (builtin-notes: Refactor handling of -F option to allow\ncombining -m and -F, 2010-02-13) added these to mimic the code\nintroduced by 2347fae5 (builtin-notes: Add \"append\" subcommand for\nappending to note objects, 2010-02-13) that reads in previous note\nbefore the message.  And the resulting code with explicit sizing is\ncarried to this day.\n\nIn the context of reading an existing note in, exact sizing may have\nmade sense, but because the resulting note needs cleansing with\nstripspace() when appending with this option, such an exact sizing\ndoes not buy us all that much in practice.\n\nIt may help avoiding overallocation due to ALLOC_GROW() slop, but\nnobody can feed so many long messages for it to matter from the\ncommand line.\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nHelped-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/notes.c | 2 --\n 1 file changed, 2 deletions(-)\n\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 4ff44f1e..c501c6ee 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -219,7 +219,6 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)\n \n \tBUG_ON_OPT_NEG(unset);\n \n-\tstrbuf_grow(&d->buf, strlen(arg) + 2);\n \tif (d->buf.len)\n \t\tstrbuf_addch(&d->buf, '\\n');\n \tstrbuf_addstr(&d->buf, arg);\n@@ -623,7 +622,6 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \t\tchar *prev_buf = repo_read_object_file(the_repository, note,\n \t\t\t\t\t\t       &type, &size);\n \n-\t\tstrbuf_grow(&d.buf, size + 1);\n \t\tif (d.buf.len && prev_buf && size)\n \t\t\tstrbuf_insertstr(&d.buf, 0, \"\\n\");\n \t\tif (prev_buf && size)\n-- \n2.40.0.356.g367cb1d4\n\n"},{"id":"477749","messageId":"4ad7840584191d6bbb3fcba72887aa569c797a15.1685174012.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1685174011.git.dyroneteng@gmail.com","subject":"[PATCH v11 2/7] notes.c: use designated initializers for clarity","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-27T07:57:49Z","receivedAt":"2023-05-27T07:58:12Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\nThe \"struct note_data d = { 0, 0, NULL, STRBUF_INIT };\" style could be\nreplaced with designated initializer for clarity.\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/notes.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex c501c6ee..9d8ca795 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -405,7 +405,7 @@ static int add(int argc, const char **argv, const char *prefix)\n \tstruct notes_tree *t;\n \tstruct object_id object, new_note;\n \tconst struct object_id *note;\n-\tstruct note_data d = { 0, 0, NULL, STRBUF_INIT };\n+\tstruct note_data d = { .buf = STRBUF_INIT };\n \tstruct option options[] = {\n \t\tOPT_CALLBACK_F('m', \"message\", &d, N_(\"message\"),\n \t\t\tN_(\"note contents as a string\"), PARSE_OPT_NONEG,\n@@ -571,7 +571,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \tconst struct object_id *note;\n \tchar *logmsg;\n \tconst char * const *usage;\n-\tstruct note_data d = { 0, 0, NULL, STRBUF_INIT };\n+\tstruct note_data d = { .buf = STRBUF_INIT };\n \tstruct option options[] = {\n \t\tOPT_CALLBACK_F('m', \"message\", &d, N_(\"message\"),\n \t\t\tN_(\"note contents as a string\"), PARSE_OPT_NONEG,\n-- \n2.40.0.356.g367cb1d4\n\n"},{"id":"477750","messageId":"c2fc2091d2afbb5911220bb55574fae11cf1c1dc.1685174012.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1685174011.git.dyroneteng@gmail.com","subject":"[PATCH v11 3/7] t3321: add test cases about the notes stripspace behavior","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-27T07:57:50Z","receivedAt":"2023-05-27T07:58:16Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n t/t3321-notes-stripspace.sh | 291 ++++++++++++++++++++++++++++++++++++\n 1 file changed, 291 insertions(+)\n create mode 100755 t/t3321-notes-stripspace.sh\n\ndiff --git a/t/t3321-notes-stripspace.sh b/t/t3321-notes-stripspace.sh\nnew file mode 100755\nindex 00000000..89977873\n--- /dev/null\n+++ b/t/t3321-notes-stripspace.sh\n@@ -0,0 +1,291 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2023 Teng Long\n+#\n+\n+test_description='Test commit notes with stripspace behavior'\n+\n+. ./test-lib.sh\n+\n+MULTI_LF=\"$LF$LF$LF\"\n+write_script fake_editor <<\\EOF\n+echo \"$MSG\" >\"$1\"\n+echo \"$MSG\" >&2\n+EOF\n+GIT_EDITOR=./fake_editor\n+export GIT_EDITOR\n+\n+test_expect_success 'setup the commit' '\n+\ttest_commit 1st\n+'\n+\n+test_expect_success 'add note by editor' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes add  &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying single \"-m\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tgit notes add -m \"${LF}first-line${MULTI_LF}second-line${LF}\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying multiple \"-m\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tgit notes add -m \"${LF}\" \\\n+\t\t      -m \"first-line\" \\\n+\t\t      -m \"${MULTI_LF}\" \\\n+\t\t      -m \"second-line\" \\\n+\t\t      -m \"${LF}\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+\n+test_expect_success 'append note by editor' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tgit notes add -m \"first-line\" &&\n+\tMSG=\"${MULTI_LF}second-line${LF}\" git notes append  &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append note by specifying single \"-m\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tgit notes add -m \"${LF}first-line\" &&\n+\tgit notes append -m \"${MULTI_LF}second-line${LF}\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append note by specifying multiple \"-m\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tgit notes add -m \"${LF}first-line\" &&\n+\tgit notes append -m \"${MULTI_LF}\" \\\n+\t\t      -m \"second-line\" \\\n+\t\t      -m \"${LF}\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying single \"-F\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tcat >note-file <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add -F note-file &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add notes by specifying multiple \"-F\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfile-1-first-line\n+\n+\tfile-1-second-line\n+\n+\tfile-2-first-line\n+\n+\tfile-2-second-line\n+\tEOF\n+\n+\tcat >note-file-1 <<-EOF &&\n+\t${LF}\n+\tfile-1-first-line\n+\t${MULTI_LF}\n+\tfile-1-second-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file-2 <<-EOF &&\n+\t${LF}\n+\tfile-2-first-line\n+\t${MULTI_LF}\n+\tfile-2-second-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add -F note-file-1 -F note-file-2 &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append note by specifying single \"-F\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tinitial-line\n+\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tcat >note-file <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add -m \"initial-line\" &&\n+\tgit notes append -F note-file &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append notes by specifying multiple \"-F\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tinitial-line\n+\n+\tfile-1-first-line\n+\n+\tfile-1-second-line\n+\n+\tfile-2-first-line\n+\n+\tfile-2-second-line\n+\tEOF\n+\n+\tcat >note-file-1 <<-EOF &&\n+\t${LF}\n+\tfile-1-first-line\n+\t${MULTI_LF}\n+\tfile-1-second-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file-2 <<-EOF &&\n+\t${LF}\n+\tfile-2-first-line\n+\t${MULTI_LF}\n+\tfile-2-second-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add -m \"initial-line\" &&\n+\tgit notes append -F note-file-1 -F note-file-2 &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add notes with empty messages' '\n+\trev=$(git rev-parse HEAD) &&\n+\tgit notes add -m \"${LF}\" \\\n+\t\t      -m \"${MULTI_LF}\" \\\n+\t\t      -m \"${LF}\" >actual 2>&1 &&\n+\ttest_i18ngrep \"Removing note for object\" actual\n+'\n+\n+test_expect_success 'add note by specifying \"-C\" , do not stripspace is the default behavior' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tcat expect | git hash-object -w --stdin >blob &&\n+\tgit notes add -C $(cat blob) &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add notes with \"-C\" and \"-m\", \"-m\" will stripspace all together' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >data <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\n+\tthird-line\n+\tEOF\n+\n+\tcat data | git hash-object -w --stdin >blob &&\n+\tgit notes add -C $(cat blob) -m \"third-line\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add notes with \"-m\" and \"-C\", \"-C\" will not stripspace all together' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >data <<-EOF &&\n+\n+\tsecond-line\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\t${LF}\n+\tsecond-line\n+\tEOF\n+\n+\tcat data | git hash-object -w --stdin >blob &&\n+\tgit notes add -m \"first-line\" -C $(cat blob)  &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \n2.40.0.356.g367cb1d4\n\n"},{"id":"477751","messageId":"367cb1d465755b401f71b8ac752992689879a699.1685174012.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1685174011.git.dyroneteng@gmail.com","subject":"[PATCH v11 4/7] notes.c: introduce '--separator=<paragraph-break>' option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-27T07:57:51Z","receivedAt":"2023-05-27T07:58:25Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\nWhen adding new notes or appending to an existing notes, we will\ninsert a blank line between the paragraphs, like:\n\n     $ git notes add -m foo -m bar\n     $ git notes show HEAD\n     foo\n\n     bar\n\nThe default behavour sometimes is not enough, the user may want\nto use a custom delimiter between paragraphs, like when\nspecifying '-m', '-F', '-C', '-c' options. So this commit\nintroduce a new '--separator' option for 'git notes add' and\n'git notes append', for example when executing:\n\n    $ git notes add -m foo -m bar --separator=\"-\"\n    $ git notes show HEAD\n    foo\n    -\n    bar\n\na newline is added to the value given to --separator if it\ndoes not end with one already. So when executing:\n\n      $ git notes add -m foo -m bar --separator=\"-\"\nand\n      $ export LF=\"\n      \"\n      $ git notes add -m foo -m bar --separator=\"-$LF\"\n\nBoth the two exections produce the same result.\n\nThe reason we use a \"strbuf\" array to concat but not \"string_list\", is\nthat the binary file content may contain '\\0' in the middle, this will\ncause the corrupt result if using a string to save.\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n Documentation/git-notes.txt |  21 ++++--\n builtin/notes.c             | 109 ++++++++++++++++++++++++-------\n t/t3301-notes.sh            | 126 ++++++++++++++++++++++++++++++++++++\n 3 files changed, 227 insertions(+), 29 deletions(-)\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex efbc10f0..59980b21 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -9,9 +9,9 @@ SYNOPSIS\n --------\n [verse]\n 'git notes' [list [<object>]]\n-'git notes' add [-f] [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n+'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n 'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )\n-'git notes' append [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n+'git notes' append [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n 'git notes' edit [--allow-empty] [<object>]\n 'git notes' show [<object>]\n 'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>\n@@ -65,7 +65,9 @@ add::\n \tHowever, if you're using `add` interactively (using an editor\n \tto supply the notes contents), then - instead of aborting -\n \tthe existing notes will be opened in the editor (like the `edit`\n-\tsubcommand).\n+\tsubcommand). If you specify multiple `-m` and `-F`, a blank\n+\tline will be inserted between the messages. Use the `--separator`\n+\toption to insert other delimiters.\n \n copy::\n \tCopy the notes for the first object onto the second object (defaults to\n@@ -85,8 +87,12 @@ corresponding <to-object>.  (The optional `<rest>` is ignored so that\n the command can read the input given to the `post-rewrite` hook.)\n \n append::\n-\tAppend to the notes of an existing object (defaults to HEAD).\n-\tCreates a new notes object if needed.\n+\tAppend new message(s) given by `-m` or `-F` options to an\n+\texisting note, or add them as a new note if one does not\n+\texist, for the object (defaults to HEAD).  When appending to\n+\tan existing note, a blank line is added before each new\n+\tmessage as an inter-paragraph separator.  The separator can\n+\tbe customized with the `--separator` option.\n \n edit::\n \tEdit the notes for a given object (defaults to HEAD).\n@@ -159,6 +165,11 @@ OPTIONS\n \tAllow an empty note object to be stored. The default behavior is\n \tto automatically remove empty notes.\n \n+--separator <paragraph-break>::\n+\tSpecify a string used as a custom inter-paragraph separator\n+\t(a newline is added at the end as needed).  Defaults to a\n+\tblank line.\n+\n --ref <ref>::\n \tManipulate the notes tree in <ref>.  This overrides\n \t`GIT_NOTES_REF` and the \"core.notesRef\" configuration.  The ref\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 9d8ca795..d8a39fe2 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -8,6 +8,7 @@\n  */\n \n #include \"cache.h\"\n+#include \"alloc.h\"\n #include \"config.h\"\n #include \"builtin.h\"\n #include \"gettext.h\"\n@@ -27,11 +28,12 @@\n #include \"worktree.h\"\n #include \"write-or-die.h\"\n \n+static char *separator = \"\\n\";\n static const char * const git_notes_usage[] = {\n \tN_(\"git notes [--ref <notes-ref>] [list [<object>]]\"),\n-\tN_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n+\tN_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n \tN_(\"git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>\"),\n-\tN_(\"git notes [--ref <notes-ref>] append [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n+\tN_(\"git notes [--ref <notes-ref>] append [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n \tN_(\"git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]\"),\n \tN_(\"git notes [--ref <notes-ref>] show [<object>]\"),\n \tN_(\"git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>\"),\n@@ -99,11 +101,19 @@ static const char * const git_notes_get_ref_usage[] = {\n static const char note_template[] =\n \tN_(\"Write/edit the notes for the following object:\");\n \n+struct note_msg {\n+\tint stripspace;\n+\tstruct strbuf buf;\n+};\n+\n struct note_data {\n \tint given;\n \tint use_editor;\n \tchar *edit_path;\n \tstruct strbuf buf;\n+\tstruct note_msg **messages;\n+\tsize_t msg_nr;\n+\tsize_t msg_alloc;\n };\n \n static void free_note_data(struct note_data *d)\n@@ -113,6 +123,12 @@ static void free_note_data(struct note_data *d)\n \t\tfree(d->edit_path);\n \t}\n \tstrbuf_release(&d->buf);\n+\n+\twhile (d->msg_nr--) {\n+\t\tstrbuf_release(&d->messages[d->msg_nr]->buf);\n+\t\tfree(d->messages[d->msg_nr]);\n+\t}\n+\tfree(d->messages);\n }\n \n static int list_each_note(const struct object_id *object_oid,\n@@ -213,65 +229,97 @@ static void write_note_data(struct note_data *d, struct object_id *oid)\n \t}\n }\n \n+static void insert_separator(struct strbuf *message, size_t pos)\n+{\n+\tsize_t sep_len = strlen(separator);\n+\tif (sep_len && separator[sep_len - 1] == '\\n')\n+\t\tstrbuf_insertstr(message, pos, separator);\n+\telse\n+\t\tstrbuf_insertf(message, pos, \"%s%s\", separator, \"\\n\");\n+}\n+\n+static void concat_messages(struct note_data *d)\n+{\n+\tstruct strbuf msg = STRBUF_INIT;\n+\n+\tsize_t i;\n+\tfor (i = 0; i < d->msg_nr ; i++) {\n+\t\tif (d->buf.len)\n+\t\t\tinsert_separator(&d->buf, d->buf.len);\n+\t\tstrbuf_add(&msg, d->messages[i]->buf.buf, d->messages[i]->buf.len);\n+\t\tstrbuf_addbuf(&d->buf, &msg);\n+\t\tif (d->messages[i]->stripspace)\n+\t\t\tstrbuf_stripspace(&d->buf, 0);\n+\t\tstrbuf_reset(&msg);\n+\t}\n+\tstrbuf_release(&msg);\n+}\n+\n static int parse_msg_arg(const struct option *opt, const char *arg, int unset)\n {\n \tstruct note_data *d = opt->value;\n+\tstruct note_msg *msg = xmalloc(sizeof(*msg));\n \n \tBUG_ON_OPT_NEG(unset);\n \n-\tif (d->buf.len)\n-\t\tstrbuf_addch(&d->buf, '\\n');\n-\tstrbuf_addstr(&d->buf, arg);\n-\tstrbuf_stripspace(&d->buf, 0);\n-\n-\td->given = 1;\n+\tstrbuf_init(&msg->buf, strlen(arg));\n+\tstrbuf_addstr(&msg->buf, arg);\n+\tALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);\n+\td->messages[d->msg_nr - 1] = msg;\n+\tmsg->stripspace = 1;\n \treturn 0;\n }\n \n static int parse_file_arg(const struct option *opt, const char *arg, int unset)\n {\n \tstruct note_data *d = opt->value;\n+\tstruct note_msg *msg = xmalloc(sizeof(*msg));\n \n \tBUG_ON_OPT_NEG(unset);\n \n-\tif (d->buf.len)\n-\t\tstrbuf_addch(&d->buf, '\\n');\n+\tstrbuf_init(&msg->buf , 0);\n \tif (!strcmp(arg, \"-\")) {\n-\t\tif (strbuf_read(&d->buf, 0, 1024) < 0)\n+\t\tif (strbuf_read(&msg->buf, 0, 1024) < 0)\n \t\t\tdie_errno(_(\"cannot read '%s'\"), arg);\n-\t} else if (strbuf_read_file(&d->buf, arg, 1024) < 0)\n+\t} else if (strbuf_read_file(&msg->buf, arg, 1024) < 0)\n \t\tdie_errno(_(\"could not open or read '%s'\"), arg);\n-\tstrbuf_stripspace(&d->buf, 0);\n \n-\td->given = 1;\n+\tALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);\n+\td->messages[d->msg_nr - 1] = msg;\n+\tmsg->stripspace = 1;\n \treturn 0;\n }\n \n static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)\n {\n \tstruct note_data *d = opt->value;\n-\tchar *buf;\n+\tstruct note_msg *msg = xmalloc(sizeof(*msg));\n+\tchar *value;\n \tstruct object_id object;\n \tenum object_type type;\n \tunsigned long len;\n \n \tBUG_ON_OPT_NEG(unset);\n \n-\tif (d->buf.len)\n-\t\tstrbuf_addch(&d->buf, '\\n');\n-\n+\tstrbuf_init(&msg->buf, 0);\n \tif (repo_get_oid(the_repository, arg, &object))\n \t\tdie(_(\"failed to resolve '%s' as a valid ref.\"), arg);\n-\tif (!(buf = repo_read_object_file(the_repository, &object, &type, &len)))\n+\tif (!(value = repo_read_object_file(the_repository, &object, &type, &len)))\n \t\tdie(_(\"failed to read object '%s'.\"), arg);\n \tif (type != OBJ_BLOB) {\n-\t\tfree(buf);\n+\t\tstrbuf_release(&msg->buf);\n+\t\tfree(value);\n+\t\tfree(msg);\n \t\tdie(_(\"cannot read note data from non-blob object '%s'.\"), arg);\n \t}\n-\tstrbuf_add(&d->buf, buf, len);\n-\tfree(buf);\n \n-\td->given = 1;\n+\tstrbuf_add(&msg->buf, value, len);\n+\tfree(value);\n+\n+\tmsg->buf.len = len;\n+\tALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);\n+\td->messages[d->msg_nr - 1] = msg;\n+\tmsg->stripspace = 0;\n \treturn 0;\n }\n \n@@ -406,6 +454,7 @@ static int add(int argc, const char **argv, const char *prefix)\n \tstruct object_id object, new_note;\n \tconst struct object_id *note;\n \tstruct note_data d = { .buf = STRBUF_INIT };\n+\n \tstruct option options[] = {\n \t\tOPT_CALLBACK_F('m', \"message\", &d, N_(\"message\"),\n \t\t\tN_(\"note contents as a string\"), PARSE_OPT_NONEG,\n@@ -422,6 +471,8 @@ static int add(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"allow-empty\", &allow_empty,\n \t\t\tN_(\"allow storing empty note\")),\n \t\tOPT__FORCE(&force, N_(\"replace existing notes\"), PARSE_OPT_NOCOMPLETE),\n+\t\tOPT_STRING(0, \"separator\", &separator, N_(\"separator\"),\n+\t\t\tN_(\"insert <paragraph-break> between paragraphs\")),\n \t\tOPT_END()\n \t};\n \n@@ -433,6 +484,10 @@ static int add(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(git_notes_add_usage, options);\n \t}\n \n+\tif (d.msg_nr)\n+\t\tconcat_messages(&d);\n+\td.given = !!d.buf.len;\n+\n \tobject_ref = argc > 1 ? argv[1] : \"HEAD\";\n \n \tif (repo_get_oid(the_repository, object_ref, &object))\n@@ -587,6 +642,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \t\t\tparse_reuse_arg),\n \t\tOPT_BOOL(0, \"allow-empty\", &allow_empty,\n \t\t\tN_(\"allow storing empty note\")),\n+\t\tOPT_STRING(0, \"separator\", &separator, N_(\"separator\"),\n+\t\t\tN_(\"insert <paragraph-break> between paragraphs\")),\n \t\tOPT_END()\n \t};\n \tint edit = !strcmp(argv[0], \"edit\");\n@@ -600,6 +657,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(usage, options);\n \t}\n \n+\tif (d.msg_nr)\n+\t\tconcat_messages(&d);\n+\td.given = !!d.buf.len;\n+\n \tif (d.given && edit)\n \t\tfprintf(stderr, _(\"The -m/-F/-c/-C options have been deprecated \"\n \t\t\t\"for the 'edit' subcommand.\\n\"\n@@ -623,7 +684,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \t\t\t\t\t\t       &type, &size);\n \n \t\tif (d.buf.len && prev_buf && size)\n-\t\t\tstrbuf_insertstr(&d.buf, 0, \"\\n\");\n+\t\t\tinsert_separator(&d.buf, 0);\n \t\tif (prev_buf && size)\n \t\t\tstrbuf_insert(&d.buf, 0, prev_buf, size);\n \t\tfree(prev_buf);\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex 3288aaec..dbadcf13 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -362,6 +362,7 @@ test_expect_success 'do not create empty note with -m \"\"' '\n '\n \n test_expect_success 'create note with combination of -m and -F' '\n+\ttest_when_finished git notes remove HEAD &&\n \tcat >expect-combine_m_and_F <<-EOF &&\n \t\tfoo\n \n@@ -380,6 +381,25 @@ test_expect_success 'create note with combination of -m and -F' '\n \ttest_cmp expect-combine_m_and_F actual\n '\n \n+test_expect_success 'create note with combination of -m and -F and --separator' '\n+\tcat >expect-combine_m_and_F <<-\\EOF &&\n+\tfoo\n+\t-------\n+\txyzzy\n+\t-------\n+\tbar\n+\t-------\n+\tzyxxy\n+\t-------\n+\tbaz\n+\tEOF\n+\techo \"xyzzy\" >note_a &&\n+\techo \"zyxxy\" >note_b &&\n+\tgit notes add -m \"foo\" -F note_a -m \"bar\" -F note_b -m \"baz\" --separator \"-------\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect-combine_m_and_F actual\n+'\n+\n test_expect_success 'remove note with \"git notes remove\"' '\n \tgit notes remove HEAD^ &&\n \tgit notes remove &&\n@@ -521,6 +541,85 @@ test_expect_success 'listing non-existing notes fails' '\n \ttest_must_be_empty actual\n '\n \n+test_expect_success 'append: specify an empty separator' '\n+\ttest_when_finished git notes remove HEAD &&\n+\tcat >expect <<-\\EOF &&\n+\tnotes-1\n+\n+\tnotes-2\n+\tEOF\n+\n+\tgit notes add -m \"notes-1\" &&\n+\tgit notes append --separator=\"\" -m \"notes-2\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append: specify separator with line break' '\n+\ttest_when_finished git notes remove HEAD &&\n+\tcat >expect <<-\\EOF &&\n+\tnotes-1\n+\t-------\n+\tnotes-2\n+\tEOF\n+\n+\tgit notes add -m \"notes-1\" &&\n+\tgit notes append --separator=\"-------$LF\" -m \"notes-2\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append: specify separator without line break' '\n+\ttest_when_finished git notes remove HEAD &&\n+\tcat >expect <<-\\EOF &&\n+\tnotes-1\n+\t-------\n+\tnotes-2\n+\tEOF\n+\n+\tgit notes add -m \"notes-1\" &&\n+\tgit notes append --separator=\"-------\" -m \"notes-2\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append: specify separator with multiple messages' '\n+\ttest_when_finished git notes remove HEAD &&\n+\tcat >expect <<-\\EOF &&\n+\tnotes-1\n+\t-------\n+\tnotes-2\n+\t-------\n+\tnotes-3\n+\tEOF\n+\n+\tgit notes add -m \"notes-1\" &&\n+\tgit notes append --separator=\"-------\" -m \"notes-2\" -m \"notes-3\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append note with combination of -m and -F and --separator' '\n+\ttest_when_finished git notes remove HEAD &&\n+\tcat >expect-combine_m_and_F <<-\\EOF &&\n+\tm-notes-1\n+\t-------\n+\tf-notes-1\n+\t-------\n+\tm-notes-2\n+\t-------\n+\tf-notes-2\n+\t-------\n+\tm-notes-3\n+\tEOF\n+\n+\techo \"f-notes-1\" >note_a &&\n+\techo \"f-notes-2\" >note_b &&\n+\tgit notes append -m \"m-notes-1\" -F note_a -m \"m-notes-2\" -F note_b -m \"m-notes-3\" --separator \"-------\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect-combine_m_and_F actual\n+'\n+\n test_expect_success 'append to existing note with \"git notes append\"' '\n \tcat >expect <<-EOF &&\n \t\tInitial set of notes\n@@ -818,6 +917,33 @@ test_expect_success 'create note from blob with \"git notes add -C\" reuses blob i\n \ttest_cmp blob actual\n '\n \n+test_expect_success 'create note from blob with \"-C\", also specify \"-m\", \"-F\" and \"--separator\"' '\n+\t# 8th will be reuseed in following tests, so rollback when the test is done\n+\ttest_when_finished \"git notes remove && git notes add -C $(cat blob)\" &&\n+\tcommit=$(git rev-parse HEAD) &&\n+\tcat >expect <<-EOF &&\n+\t\tcommit $commit\n+\t\tAuthor: A U Thor <author@example.com>\n+\t\tDate:   Thu Apr 7 15:20:13 2005 -0700\n+\n+\t\t${indent}8th\n+\n+\t\tNotes:\n+\t\t${indent}This is a blob object\n+\t\t${indent}-------\n+\t\t${indent}This is created by -m\n+\t\t${indent}-------\n+\t\t${indent}This is created by -F\n+\tEOF\n+\n+\tgit notes remove &&\n+\techo \"This is a blob object\" | git hash-object -w --stdin >blob &&\n+\techo \"This is created by -F\" >note_a &&\n+\tgit notes add -C $(cat blob) -m \"This is created by -m\" -F note_a --separator=\"-------\" &&\n+\tgit log -1 >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'create note from other note with \"git notes add -c\"' '\n \ttest_commit 9th &&\n \tcommit=$(git rev-parse HEAD) &&\n-- \n2.40.0.356.g367cb1d4\n\n"},{"id":"477752","messageId":"4ba62a48ec56b43f859fce0eb0d88528f6873edb.1685174012.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1685174011.git.dyroneteng@gmail.com","subject":"[PATCH v11 5/7] notes.c: append separator instead of insert by pos","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-27T07:57:52Z","receivedAt":"2023-05-27T07:58:26Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\nRename \"insert_separator\" to \"append_separator\" and also remove the\n\"postion\" argument, this serves two purpose:\n\nThe first is that when specifying more than one \"-m\" ( like \"-F\", etc)\nto \"git notes add\" or \"git notes append\", the order of them matters,\nwhich means we need to append the each separator and message in turn,\nso we don't have to make the caller specify the position, the \"append\"\noperation is enough and clear.\n\nThe second is that when we execute the \"git notes append\" subcommand,\nwe need to combine the \"prev_note\" and \"current_note\" to get the\nfinal result. Before, we inserted a newline character at the beginning\nof \"current_note\". Now, we will append a newline to the end of\n\"prev_note\" instead, this will give the consisitent results.\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n builtin/notes.c | 21 ++++++++++++---------\n 1 file changed, 12 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex d8a39fe2..b2804479 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -229,13 +229,13 @@ static void write_note_data(struct note_data *d, struct object_id *oid)\n \t}\n }\n \n-static void insert_separator(struct strbuf *message, size_t pos)\n+static void append_separator(struct strbuf *message)\n {\n \tsize_t sep_len = strlen(separator);\n \tif (sep_len && separator[sep_len - 1] == '\\n')\n-\t\tstrbuf_insertstr(message, pos, separator);\n+\t\tstrbuf_addstr(message, separator);\n \telse\n-\t\tstrbuf_insertf(message, pos, \"%s%s\", separator, \"\\n\");\n+\t\tstrbuf_addf(message, \"%s%s\", separator, \"\\n\");\n }\n \n static void concat_messages(struct note_data *d)\n@@ -245,7 +245,7 @@ static void concat_messages(struct note_data *d)\n \tsize_t i;\n \tfor (i = 0; i < d->msg_nr ; i++) {\n \t\tif (d->buf.len)\n-\t\t\tinsert_separator(&d->buf, d->buf.len);\n+\t\t\tappend_separator(&d->buf);\n \t\tstrbuf_add(&msg, d->messages[i]->buf.buf, d->messages[i]->buf.len);\n \t\tstrbuf_addbuf(&d->buf, &msg);\n \t\tif (d->messages[i]->stripspace)\n@@ -680,14 +680,17 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \t\t/* Append buf to previous note contents */\n \t\tunsigned long size;\n \t\tenum object_type type;\n-\t\tchar *prev_buf = repo_read_object_file(the_repository, note,\n-\t\t\t\t\t\t       &type, &size);\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tchar *prev_buf = repo_read_object_file(the_repository, note, &type, &size);\n \n-\t\tif (d.buf.len && prev_buf && size)\n-\t\t\tinsert_separator(&d.buf, 0);\n \t\tif (prev_buf && size)\n-\t\t\tstrbuf_insert(&d.buf, 0, prev_buf, size);\n+\t\t\tstrbuf_add(&buf, prev_buf, size);\n+\t\tif (d.buf.len && prev_buf && size)\n+\t\t\tappend_separator(&buf);\n+\t\tstrbuf_insert(&d.buf, 0, buf.buf, buf.len);\n+\n \t\tfree(prev_buf);\n+\t\tstrbuf_release(&buf);\n \t}\n \n \tif (d.buf.len || allow_empty) {\n-- \n2.40.0.356.g367cb1d4\n\n"},{"id":"477753","messageId":"19865941bb29c28a690ed3e28fc4f6870e352923.1685174012.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1685174011.git.dyroneteng@gmail.com","subject":"[PATCH v11 6/7] notes.c: introduce \"--[no-]stripspace\" option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-27T07:57:53Z","receivedAt":"2023-05-27T07:58:34Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\nThis commit introduces a new option \"--[no-]stripspace\" to git notes\nappend, git notes edit, and git notes add. This option allows users to\ncontrol whether the note message need to stripped out.\n\nFor the consideration of backward compatibility, let's look at the\nbehavior about \"stripspace\" in \"git notes\" command:\n\n1. \"Edit Message\" case: using the default editor to edit the note\nmessage.\n\n    In \"edit\" case, the edited message will always be stripped out, the\n    implementation which can be found in the \"prepare_note_data()\". In\n    addition, the \"-c\" option supports to reuse an existing blob as a\n    note message, then open the editor to make a further edition on it,\n    the edited message will be stripped.\n\n    This commit doesn't change the default behavior of \"edit\" case by\n    using an enum \"notes_stripspace\", only when \"--no-stripspace\" option\n    is specified, the note message will not be stripped out. If you do\n    not specify the option or you specify \"--stripspace\", clearly, the\n    note message will be stripped out.\n\n2. \"Assign Message\" case: using the \"-m\"/\"-F\"/\"-C\" option to specify the\nnote message.\n\n    In \"assign\" case, when specify message by \"-m\" or \"-F\", the message\n    will be stripped out by default, but when specify message by \"-C\",\n    the message will be copied verbatim, in other word, the message will\n    not be stripped out. One more thing need to note is \"the order of\n    the options matter\", that is, if you specify \"-C\" before \"-m\" or\n    \"-F\", the reused message by \"-C\" will be stripped out together,\n    because everytime concat \"-m\" or \"-F\" message, the concated message\n    will be stripped together. Oppositely, if you specify \"-m\" or \"-F\"\n    before \"-C\", the reused message by \"-C\" will not be stripped out.\n\n    This commit doesn't change the default behavior of \"assign\" case by\n    extending the \"stripspace\" field in \"struct note_msg\", so we can\n    distinguish the different behavior of \"-m\"/\"-F\" and \"-C\" options\n    when we need to parse and concat the message.\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n Documentation/git-notes.txt |  25 ++-\n builtin/notes.c             |  30 +++-\n t/t3321-notes-stripspace.sh | 296 +++++++++++++++++++++++++++++++++++-\n 3 files changed, 332 insertions(+), 19 deletions(-)\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex 59980b21..5f3a9479 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -9,10 +9,10 @@ SYNOPSIS\n --------\n [verse]\n 'git notes' [list [<object>]]\n-'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n+'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n 'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )\n-'git notes' append [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n-'git notes' edit [--allow-empty] [<object>]\n+'git notes' append [--allow-empty] [--separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n+'git notes' edit [--allow-empty] [<object>] [--[no-]stripspace]\n 'git notes' show [<object>]\n 'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>\n 'git notes' merge --commit [-v | -q]\n@@ -141,20 +141,26 @@ OPTIONS\n \tIf multiple `-m` options are given, their values\n \tare concatenated as separate paragraphs.\n \tLines starting with `#` and empty lines other than a\n-\tsingle line between paragraphs will be stripped out.\n+\tsingle line between paragraphs will be stripped out,\n+\tif you wish to keep them verbatim, use `--no-stripspace`.\n \n -F <file>::\n --file=<file>::\n \tTake the note message from the given file.  Use '-' to\n \tread the note message from the standard input.\n \tLines starting with `#` and empty lines other than a\n-\tsingle line between paragraphs will be stripped out.\n+\tsingle line between paragraphs will be stripped out,\n+\tif you wish to keep them verbatim, use with\n+\t`--no-stripspace` option.\n \n -C <object>::\n --reuse-message=<object>::\n \tTake the given blob object (for example, another note) as the\n \tnote message. (Use `git notes copy <object>` instead to\n-\tcopy notes between objects.)\n+\tcopy notes between objects.).  By default, message will be\n+\tcopied verbatim, but if you wish to strip out the lines\n+\tstarting with `#` and empty lines other than a single line\n+\tbetween paragraphs, use with`--stripspace` option.\n \n -c <object>::\n --reedit-message=<object>::\n@@ -170,6 +176,13 @@ OPTIONS\n \t(a newline is added at the end as needed).  Defaults to a\n \tblank line.\n \n+--[no-]stripspace::\n+\tStrip leading and trailing whitespace from the note message.\n+\tAlso strip out empty lines other than a single line between\n+\tparagraphs. For lines starting with `#` will be stripped out\n+\tin non-editor cases like \"-m\", \"-F\" and \"-C\", but not in\n+\teditor case like \"git notes edit\", \"-c\", etc.\n+\n --ref <ref>::\n \tManipulate the notes tree in <ref>.  This overrides\n \t`GIT_NOTES_REF` and the \"core.notesRef\" configuration.  The ref\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex b2804479..2140a697 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -101,14 +101,21 @@ static const char * const git_notes_get_ref_usage[] = {\n static const char note_template[] =\n \tN_(\"Write/edit the notes for the following object:\");\n \n+enum notes_stripspace {\n+\tUNSPECIFIED = -1,\n+\tNO_STRIPSPACE = 0,\n+\tSTRIPSPACE = 1,\n+};\n+\n struct note_msg {\n-\tint stripspace;\n+\tenum notes_stripspace stripspace;\n \tstruct strbuf buf;\n };\n \n struct note_data {\n \tint given;\n \tint use_editor;\n+\tint stripspace;\n \tchar *edit_path;\n \tstruct strbuf buf;\n \tstruct note_msg **messages;\n@@ -213,7 +220,8 @@ static void prepare_note_data(const struct object_id *object, struct note_data *\n \t\tif (launch_editor(d->edit_path, &d->buf, NULL)) {\n \t\t\tdie(_(\"please supply the note contents using either -m or -F option\"));\n \t\t}\n-\t\tstrbuf_stripspace(&d->buf, 1);\n+\t\tif (d->stripspace)\n+\t\t\tstrbuf_stripspace(&d->buf, 1);\n \t}\n }\n \n@@ -248,7 +256,9 @@ static void concat_messages(struct note_data *d)\n \t\t\tappend_separator(&d->buf);\n \t\tstrbuf_add(&msg, d->messages[i]->buf.buf, d->messages[i]->buf.len);\n \t\tstrbuf_addbuf(&d->buf, &msg);\n-\t\tif (d->messages[i]->stripspace)\n+\t\tif ((d->stripspace == UNSPECIFIED &&\n+\t\t     d->messages[i]->stripspace == STRIPSPACE) ||\n+\t\t    d->stripspace == STRIPSPACE)\n \t\t\tstrbuf_stripspace(&d->buf, 0);\n \t\tstrbuf_reset(&msg);\n \t}\n@@ -266,7 +276,7 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)\n \tstrbuf_addstr(&msg->buf, arg);\n \tALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);\n \td->messages[d->msg_nr - 1] = msg;\n-\tmsg->stripspace = 1;\n+\tmsg->stripspace = STRIPSPACE;\n \treturn 0;\n }\n \n@@ -286,7 +296,7 @@ static int parse_file_arg(const struct option *opt, const char *arg, int unset)\n \n \tALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);\n \td->messages[d->msg_nr - 1] = msg;\n-\tmsg->stripspace = 1;\n+\tmsg->stripspace = STRIPSPACE;\n \treturn 0;\n }\n \n@@ -319,7 +329,7 @@ static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)\n \tmsg->buf.len = len;\n \tALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);\n \td->messages[d->msg_nr - 1] = msg;\n-\tmsg->stripspace = 0;\n+\tmsg->stripspace = NO_STRIPSPACE;\n \treturn 0;\n }\n \n@@ -453,7 +463,7 @@ static int add(int argc, const char **argv, const char *prefix)\n \tstruct notes_tree *t;\n \tstruct object_id object, new_note;\n \tconst struct object_id *note;\n-\tstruct note_data d = { .buf = STRBUF_INIT };\n+\tstruct note_data d = { .buf = STRBUF_INIT, .stripspace = UNSPECIFIED };\n \n \tstruct option options[] = {\n \t\tOPT_CALLBACK_F('m', \"message\", &d, N_(\"message\"),\n@@ -473,6 +483,8 @@ static int add(int argc, const char **argv, const char *prefix)\n \t\tOPT__FORCE(&force, N_(\"replace existing notes\"), PARSE_OPT_NOCOMPLETE),\n \t\tOPT_STRING(0, \"separator\", &separator, N_(\"separator\"),\n \t\t\tN_(\"insert <paragraph-break> between paragraphs\")),\n+\t\tOPT_BOOL(0, \"stripspace\", &d.stripspace,\n+\t\t\tN_(\"remove unnecessary whitespace\")),\n \t\tOPT_END()\n \t};\n \n@@ -626,7 +638,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \tconst struct object_id *note;\n \tchar *logmsg;\n \tconst char * const *usage;\n-\tstruct note_data d = { .buf = STRBUF_INIT };\n+\tstruct note_data d = { .buf = STRBUF_INIT, .stripspace = UNSPECIFIED };\n \tstruct option options[] = {\n \t\tOPT_CALLBACK_F('m', \"message\", &d, N_(\"message\"),\n \t\t\tN_(\"note contents as a string\"), PARSE_OPT_NONEG,\n@@ -644,6 +656,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"allow storing empty note\")),\n \t\tOPT_STRING(0, \"separator\", &separator, N_(\"separator\"),\n \t\t\tN_(\"insert <paragraph-break> between paragraphs\")),\n+\t\tOPT_BOOL(0, \"stripspace\", &d.stripspace,\n+\t\t\tN_(\"remove unnecessary whitespace\")),\n \t\tOPT_END()\n \t};\n \tint edit = !strcmp(argv[0], \"edit\");\ndiff --git a/t/t3321-notes-stripspace.sh b/t/t3321-notes-stripspace.sh\nindex 89977873..028d825e 100755\n--- a/t/t3321-notes-stripspace.sh\n+++ b/t/t3321-notes-stripspace.sh\n@@ -32,7 +32,7 @@ test_expect_success 'add note by editor' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'add note by specifying single \"-m\"' '\n+test_expect_success 'add note by specifying single \"-m\", \"--stripspace\" is the default behavior' '\n \ttest_when_finished \"git notes remove\" &&\n \tcat >expect <<-EOF &&\n \tfirst-line\n@@ -42,10 +42,26 @@ test_expect_success 'add note by specifying single \"-m\"' '\n \n \tgit notes add -m \"${LF}first-line${MULTI_LF}second-line${LF}\" &&\n \tgit notes show >actual &&\n+\ttest_cmp expect actual &&\n+\tgit notes remove &&\n+\tgit notes add --stripspace -m \"${LF}first-line${MULTI_LF}second-line${LF}\" &&\n+\tgit notes show >actual &&\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'add note by specifying multiple \"-m\"' '\n+test_expect_success 'add note by specifying single \"-m\" and \"--no-stripspace\" ' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\t${LF}first-line${MULTI_LF}second-line\n+\tEOF\n+\n+\tgit notes add --no-stripspace \\\n+\t\t      -m \"${LF}first-line${MULTI_LF}second-line${LF}\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying multiple \"-m\", \"--stripspace\" is the default behavior' '\n \ttest_when_finished \"git notes remove\" &&\n \tcat >expect <<-EOF &&\n \tfirst-line\n@@ -59,9 +75,156 @@ test_expect_success 'add note by specifying multiple \"-m\"' '\n \t\t      -m \"second-line\" \\\n \t\t      -m \"${LF}\" &&\n \tgit notes show >actual &&\n+\ttest_cmp expect actual &&\n+\tgit notes remove &&\n+\tgit notes add --stripspace -m \"${LF}\" \\\n+\t\t      -m \"first-line\" \\\n+\t\t      -m \"${MULTI_LF}\" \\\n+\t\t      -m \"second-line\" \\\n+\t\t      -m \"${LF}\" &&\n+\tgit notes show >actual &&\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'add notes by specifying multiple \"-m\" and \"--no-stripspace\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line${LF}\n+\tEOF\n+\n+\tgit notes add --no-stripspace \\\n+\t\t      -m \"${LF}\" \\\n+\t\t      -m \"first-line\" \\\n+\t\t      -m \"${MULTI_LF}\" \\\n+\t\t      -m \"second-line\" \\\n+\t\t      -m \"${LF}\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying single \"-F\", \"--stripspace\" is the default behavior' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tcat >note-file <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add -F note-file &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual &&\n+\tgit notes remove &&\n+\tgit notes add --stripspace -F note-file &&\n+\tgit notes show >actual\n+'\n+\n+test_expect_success 'add note by specifying single \"-F\" and \"--no-stripspace\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add --no-stripspace -F note-file &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying multiple \"-F\", \"--stripspace\" is the default behavior' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfile-1-first-line\n+\n+\tfile-1-second-line\n+\n+\tfile-2-first-line\n+\n+\tfile-2-second-line\n+\tEOF\n+\n+\tcat >note-file-1 <<-EOF &&\n+\t${LF}\n+\tfile-1-first-line\n+\t${MULTI_LF}\n+\tfile-1-second-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file-2 <<-EOF &&\n+\t${LF}\n+\tfile-2-first-line\n+\t${MULTI_LF}\n+\tfile-2-second-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add -F note-file-1 -F note-file-2 &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual &&\n+\tgit notes remove &&\n+\tgit notes add --stripspace -F note-file-1 -F note-file-2 &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying multiple \"-F\" with \"--no-stripspace\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\t${LF}\n+\tfile-1-first-line\n+\t${MULTI_LF}\n+\tfile-1-second-line\n+\t${LF}\n+\n+\t${LF}\n+\tfile-2-first-line\n+\t${MULTI_LF}\n+\tfile-2-second-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file-1 <<-EOF &&\n+\t${LF}\n+\tfile-1-first-line\n+\t${MULTI_LF}\n+\tfile-1-second-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file-2 <<-EOF &&\n+\t${LF}\n+\tfile-2-first-line\n+\t${MULTI_LF}\n+\tfile-2-second-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add --no-stripspace -F note-file-1 -F note-file-2 &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n \n test_expect_success 'append note by editor' '\n \ttest_when_finished \"git notes remove\" &&\n@@ -221,6 +384,45 @@ test_expect_success 'append notes by specifying multiple \"-F\"' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'append note by specifying multiple \"-F\" with \"--no-stripspace\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tinitial-line\n+\t${LF}${LF}\n+\tfile-1-first-line\n+\t${MULTI_LF}\n+\tfile-1-second-line\n+\t${LF}\n+\n+\t${LF}\n+\tfile-2-first-line\n+\t${MULTI_LF}\n+\tfile-2-second-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file-1 <<-EOF &&\n+\t${LF}\n+\tfile-1-first-line\n+\t${MULTI_LF}\n+\tfile-1-second-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >note-file-2 <<-EOF &&\n+\t${LF}\n+\tfile-2-first-line\n+\t${MULTI_LF}\n+\tfile-2-second-line\n+\t${LF}\n+\tEOF\n+\n+\tgit notes add -m \"initial-line\" &&\n+\tgit notes append --no-stripspace -F note-file-1 -F note-file-2 &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'add notes with empty messages' '\n \trev=$(git rev-parse HEAD) &&\n \tgit notes add -m \"${LF}\" \\\n@@ -229,7 +431,7 @@ test_expect_success 'add notes with empty messages' '\n \ttest_i18ngrep \"Removing note for object\" actual\n '\n \n-test_expect_success 'add note by specifying \"-C\" , do not stripspace is the default behavior' '\n+test_expect_success 'add note by specifying \"-C\", \"--no-stripspace\" is the default behavior' '\n \ttest_when_finished \"git notes remove\" &&\n \tcat >expect <<-EOF &&\n \t${LF}\n@@ -242,10 +444,36 @@ test_expect_success 'add note by specifying \"-C\" , do not stripspace is the defa\n \tcat expect | git hash-object -w --stdin >blob &&\n \tgit notes add -C $(cat blob) &&\n \tgit notes show >actual &&\n+\ttest_cmp expect actual &&\n+\tgit notes remove &&\n+\tgit notes add --no-stripspace -C $(cat blob) &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'reuse note by specifying \"-C\" and \"--stripspace\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >data <<-EOF &&\n+\t${LF}\n+\tfirst-line\n+\t${MULTI_LF}\n+\tsecond-line\n+\t${LF}\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tcat data | git hash-object -w --stdin >blob &&\n+\tgit notes add --stripspace -C $(cat blob) &&\n+\tgit notes show >actual &&\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'add notes with \"-C\" and \"-m\", \"-m\" will stripspace all together' '\n+test_expect_success 'reuse with \"-C\" and add note with \"-m\", \"-m\" will stripspace all together' '\n \ttest_when_finished \"git notes remove\" &&\n \tcat >data <<-EOF &&\n \t${LF}\n@@ -269,7 +497,7 @@ test_expect_success 'add notes with \"-C\" and \"-m\", \"-m\" will stripspace all toge\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'add notes with \"-m\" and \"-C\", \"-C\" will not stripspace all together' '\n+test_expect_success 'add note with \"-m\" and reuse note with \"-C\", \"-C\" will not stripspace all together' '\n \ttest_when_finished \"git notes remove\" &&\n \tcat >data <<-EOF &&\n \n@@ -288,4 +516,62 @@ test_expect_success 'add notes with \"-m\" and \"-C\", \"-C\" will not stripspace all\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'add note by specifying \"-c\", \"--stripspace\" is the default behavior' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\techo \"initial-line\" | git hash-object -w --stdin >blob &&\n+\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes add -c $(cat blob) &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual &&\n+\tgit notes remove &&\n+\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes add --stripspace -c $(cat blob) &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'add note by specifying \"-c\" with \"--no-stripspace\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\t${LF}first-line${MULTI_LF}second-line${LF}\n+\tEOF\n+\n+\techo \"initial-line\" | git hash-object -w --stdin >blob &&\n+\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes add --no-stripspace -c $(cat blob) &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'edit note by specifying \"-c\", \"--stripspace\" is the default behavior' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\tfirst-line\n+\n+\tsecond-line\n+\tEOF\n+\n+\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes edit &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual &&\n+\tgit notes remove &&\n+\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes edit --stripspace &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'edit note by specifying \"-c\" with \"--no-stripspace\"' '\n+\ttest_when_finished \"git notes remove\" &&\n+\tcat >expect <<-EOF &&\n+\t${LF}first-line${MULTI_LF}second-line${LF}\n+\tEOF\n+\n+\tMSG=\"${LF}first-line${MULTI_LF}second-line${LF}\" git notes add --no-stripspace &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.40.0.356.g367cb1d4\n\n"},{"id":"477754","messageId":"7105778c30102069ffadcdbb7086a62083b4c766.1685174012.git.dyroneteng@gmail.com","threadId":"59667","inReplyTo":"cover.1685174011.git.dyroneteng@gmail.com","subject":"[PATCH v11 7/7] notes: introduce \"--no-separator\" option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-27T07:57:54Z","receivedAt":"2023-05-27T07:58:39Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Teng Long <dyroneteng@gmail.com>\n\nSometimes, the user may want to add or append multiple notes\nwithout any separator to be added between them.\n\nDisscussion:\n\n  https://public-inbox.org/git/3f86a553-246a-4626-b1bd-bacd8148318a@app.fastmail.com/\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n Documentation/git-notes.txt | 11 +++++----\n builtin/notes.c             | 37 ++++++++++++++++++++--------\n t/t3301-notes.sh            | 49 ++++++++++++++++++++++++++++++++++---\n 3 files changed, 79 insertions(+), 18 deletions(-)\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex 5f3a9479..bc1bfa37 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -9,9 +9,9 @@ SYNOPSIS\n --------\n [verse]\n 'git notes' [list [<object>]]\n-'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n+'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n 'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )\n-'git notes' append [--allow-empty] [--separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n+'git notes' append [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n 'git notes' edit [--allow-empty] [<object>] [--[no-]stripspace]\n 'git notes' show [<object>]\n 'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>\n@@ -171,10 +171,11 @@ OPTIONS\n \tAllow an empty note object to be stored. The default behavior is\n \tto automatically remove empty notes.\n \n---separator <paragraph-break>::\n+--[no-]separator, --separator=<paragraph-break>::\n \tSpecify a string used as a custom inter-paragraph separator\n-\t(a newline is added at the end as needed).  Defaults to a\n-\tblank line.\n+\t(a newline is added at the end as needed). If `--no-separator`, no\n+\tseparators will be added between paragraphs.  Defaults to a blank\n+\tline.\n \n --[no-]stripspace::\n \tStrip leading and trailing whitespace from the note message.\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 2140a697..00268e94 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -28,12 +28,12 @@\n #include \"worktree.h\"\n #include \"write-or-die.h\"\n \n-static char *separator = \"\\n\";\n+static const char *separator = \"\\n\";\n static const char * const git_notes_usage[] = {\n \tN_(\"git notes [--ref <notes-ref>] [list [<object>]]\"),\n-\tN_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n+\tN_(\"git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [--[no-]stripspace] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n \tN_(\"git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>\"),\n-\tN_(\"git notes [--ref <notes-ref>] append [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n+\tN_(\"git notes [--ref <notes-ref>] append [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [--[no-]stripspace] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\"),\n \tN_(\"git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]\"),\n \tN_(\"git notes [--ref <notes-ref>] show [<object>]\"),\n \tN_(\"git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>\"),\n@@ -239,8 +239,11 @@ static void write_note_data(struct note_data *d, struct object_id *oid)\n \n static void append_separator(struct strbuf *message)\n {\n-\tsize_t sep_len = strlen(separator);\n-\tif (sep_len && separator[sep_len - 1] == '\\n')\n+\tsize_t sep_len = 0;\n+\n+\tif (!separator)\n+\t\treturn;\n+\telse if ((sep_len = strlen(separator)) && separator[sep_len - 1] == '\\n')\n \t\tstrbuf_addstr(message, separator);\n \telse\n \t\tstrbuf_addf(message, \"%s%s\", separator, \"\\n\");\n@@ -249,8 +252,8 @@ static void append_separator(struct strbuf *message)\n static void concat_messages(struct note_data *d)\n {\n \tstruct strbuf msg = STRBUF_INIT;\n-\n \tsize_t i;\n+\n \tfor (i = 0; i < d->msg_nr ; i++) {\n \t\tif (d->buf.len)\n \t\t\tappend_separator(&d->buf);\n@@ -341,6 +344,16 @@ static int parse_reedit_arg(const struct option *opt, const char *arg, int unset\n \treturn parse_reuse_arg(opt, arg, unset);\n }\n \n+static int parse_separator_arg(const struct option *opt, const char *arg,\n+\t\t\t       int unset)\n+{\n+\tif (unset)\n+\t\t*(const char **)opt->value = NULL;\n+\telse\n+\t\t*(const char **)opt->value = arg ? arg : \"\\n\";\n+\treturn 0;\n+}\n+\n static int notes_copy_from_stdin(int force, const char *rewrite_cmd)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -481,8 +494,10 @@ static int add(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"allow-empty\", &allow_empty,\n \t\t\tN_(\"allow storing empty note\")),\n \t\tOPT__FORCE(&force, N_(\"replace existing notes\"), PARSE_OPT_NOCOMPLETE),\n-\t\tOPT_STRING(0, \"separator\", &separator, N_(\"separator\"),\n-\t\t\tN_(\"insert <paragraph-break> between paragraphs\")),\n+\t\tOPT_CALLBACK_F(0, \"separator\", &separator,\n+\t\t\tN_(\"<paragraph-break>\"),\n+\t\t\tN_(\"insert <paragraph-break> between paragraphs\"),\n+\t\t\tPARSE_OPT_OPTARG, parse_separator_arg),\n \t\tOPT_BOOL(0, \"stripspace\", &d.stripspace,\n \t\t\tN_(\"remove unnecessary whitespace\")),\n \t\tOPT_END()\n@@ -654,8 +669,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)\n \t\t\tparse_reuse_arg),\n \t\tOPT_BOOL(0, \"allow-empty\", &allow_empty,\n \t\t\tN_(\"allow storing empty note\")),\n-\t\tOPT_STRING(0, \"separator\", &separator, N_(\"separator\"),\n-\t\t\tN_(\"insert <paragraph-break> between paragraphs\")),\n+\t\tOPT_CALLBACK_F(0, \"separator\", &separator,\n+\t\t\tN_(\"<paragraph-break>\"),\n+\t\t\tN_(\"insert <paragraph-break> between paragraphs\"),\n+\t\t\tPARSE_OPT_OPTARG, parse_separator_arg),\n \t\tOPT_BOOL(0, \"stripspace\", &d.stripspace,\n \t\t\tN_(\"remove unnecessary whitespace\")),\n \t\tOPT_END()\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex dbadcf13..d734000d 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -382,6 +382,7 @@ test_expect_success 'create note with combination of -m and -F' '\n '\n \n test_expect_success 'create note with combination of -m and -F and --separator' '\n+\ttest_when_finished git notes remove HEAD &&\n \tcat >expect-combine_m_and_F <<-\\EOF &&\n \tfoo\n \t-------\n@@ -395,7 +396,22 @@ test_expect_success 'create note with combination of -m and -F and --separator'\n \tEOF\n \techo \"xyzzy\" >note_a &&\n \techo \"zyxxy\" >note_b &&\n-\tgit notes add -m \"foo\" -F note_a -m \"bar\" -F note_b -m \"baz\" --separator \"-------\" &&\n+\tgit notes add -m \"foo\" -F note_a -m \"bar\" -F note_b -m \"baz\" --separator=\"-------\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect-combine_m_and_F actual\n+'\n+\n+test_expect_success 'create note with combination of -m and -F and --no-separator' '\n+\tcat >expect-combine_m_and_F <<-\\EOF &&\n+\tfoo\n+\txyzzy\n+\tbar\n+\tzyxxy\n+\tbaz\n+\tEOF\n+\techo \"xyzzy\" >note_a &&\n+\techo \"zyxxy\" >note_b &&\n+\tgit notes add -m \"foo\" -F note_a -m \"bar\" -F note_b -m \"baz\" --no-separator &&\n \tgit notes show >actual &&\n \ttest_cmp expect-combine_m_and_F actual\n '\n@@ -541,7 +557,7 @@ test_expect_success 'listing non-existing notes fails' '\n \ttest_must_be_empty actual\n '\n \n-test_expect_success 'append: specify an empty separator' '\n+test_expect_success 'append: specify a separator with an empty arg' '\n \ttest_when_finished git notes remove HEAD &&\n \tcat >expect <<-\\EOF &&\n \tnotes-1\n@@ -555,6 +571,33 @@ test_expect_success 'append: specify an empty separator' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'append: specify a separator without arg' '\n+\ttest_when_finished git notes remove HEAD &&\n+\tcat >expect <<-\\EOF &&\n+\tnotes-1\n+\n+\tnotes-2\n+\tEOF\n+\n+\tgit notes add -m \"notes-1\" &&\n+\tgit notes append --separator -m \"notes-2\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'append: specify as --no-separator' '\n+\ttest_when_finished git notes remove HEAD &&\n+\tcat >expect <<-\\EOF &&\n+\tnotes-1\n+\tnotes-2\n+\tEOF\n+\n+\tgit notes add -m \"notes-1\" &&\n+\tgit notes append --no-separator -m \"notes-2\" &&\n+\tgit notes show >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'append: specify separator with line break' '\n \ttest_when_finished git notes remove HEAD &&\n \tcat >expect <<-\\EOF &&\n@@ -615,7 +658,7 @@ test_expect_success 'append note with combination of -m and -F and --separator'\n \n \techo \"f-notes-1\" >note_a &&\n \techo \"f-notes-2\" >note_b &&\n-\tgit notes append -m \"m-notes-1\" -F note_a -m \"m-notes-2\" -F note_b -m \"m-notes-3\" --separator \"-------\" &&\n+\tgit notes append -m \"m-notes-1\" -F note_a -m \"m-notes-2\" -F note_b -m \"m-notes-3\" --separator=\"-------\" &&\n \tgit notes show >actual &&\n \ttest_cmp expect-combine_m_and_F actual\n '\n-- \n2.40.0.356.g367cb1d4\n\n"},{"id":"477759","messageId":"20230527171936.GA1460206@coredump.intra.peff.net","threadId":"59667","inReplyTo":"20230527071721.24285-1-tenglong.tl@alibaba-inc.com","subject":"Re: [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2023-05-27T17:19:36Z","receivedAt":"2023-05-27T17:19:48Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, May 27, 2023 at 03:17:21PM +0800, Teng Long wrote:\n\n> > [1] To reproduce, I did:\n> > \n> >       git checkout 3993a53a13\n> >       make SANITIZE=address,undefined CC=clang\n> >       cd t && ./t3301-notes.sh -v -i\n> > \n> >     I'm using clang-14 on a Debian machine.\n> \n> Do you always do the 'make' with 'SANITIZE=address,undefined', should I\n> follow that approach, may I ask you to give some advices about it?\n\nNo, not usually. I usually run it when I am working on a patch that does\nsomething tricky with memory handling, and I want extra confidence that\nI haven't screwed anything up. :)\n\nWe do run CI jobs for both ASan and UBSan, as of 1c0962c0c4 (ci: add\naddress and undefined sanitizer tasks, 2022-10-20). But they run with\ngcc, which doesn't seem to catch this particular case.\n\nIMHO it might be worth switching to clang for those jobs. My vague\nrecollection was that I've seen it catch more things, but now we have a\nconcrete example. I think it might also run faster (for some reason\ndoing both sanitizers at once is slower for gcc, but faster for clang).\n\n-Peff\n"},{"id":"477776","messageId":"20230529114804.5129-1-tenglong.tl@alibaba-inc.com","threadId":"59667","inReplyTo":"20230527171936.GA1460206@coredump.intra.peff.net","subject":"Re: [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-05-29T11:48:04Z","receivedAt":"2023-05-29T11:48:14Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> No, not usually. I usually run it when I am working on a patch that does\n> something tricky with memory handling, and I want extra confidence that\n> I haven't screwed anything up. :)\n> \n> We do run CI jobs for both ASan and UBSan, as of 1c0962c0c4 (ci: add\n> address and undefined sanitizer tasks, 2022-10-20). But they run with\n> gcc, which doesn't seem to catch this particular case.\n> \n> IMHO it might be worth switching to clang for those jobs. My vague\n> recollection was that I've seen it catch more things, but now we have a\n> concrete example. I think it might also run faster (for some reason\n> doing both sanitizers at once is slower for gcc, but faster for clang).\n\nThanks for explaining this.\n"},{"id":"477853","messageId":"xmqqjzwnd93w.fsf@gitster.g","threadId":"59667","inReplyTo":"cover.1685174011.git.dyroneteng@gmail.com","subject":"Re: [PATCH v11 0/7] notes.c: introduce \"--separator\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-06-01T05:50:27Z","receivedAt":"2023-06-01T05:53:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Teng Long <dyroneteng@gmail.com> writes:\n\n> From: Teng Long <dyroneteng@gmail.com>\n>\n> 1.  \"--[no-]separator\" to an independent patch which suggested by Junio[1].\n\nI did suggest but not quite.\n\nThe 4th step you are fixing is already in 'next'.  You do not\nreplace a commit in a series that has been cooking there for a long\ntime.\n\nIn a more perfect world, you would have sent the \"--separator\" thing\nthat already supported \"--no-separator\" from the get-go.  You would\nalso have sent the 4th step that does not need ubsan fix.  You do\nnot have the luxury to do so when your series hit 'next'---you need\nto live with your earlier mistakes.\n\nUntil we cross the release boundary, that is.  \n\nI'll revert the whole thing out of 'next'.  Once the next cycle\nopens after the dust from the release settles (i.e. around 10th of\nJune or so), please resubmit the series that does not need \"oops, we\nprobably should have added '--[no-]separator' from the get-go\" or\n\"oops, we had undefined behaviour here, and here is a fix\", as if\nyou are a more perfect developer than you were when the version that\nhas been queued in 'next' was written (and in fact, you are, after\ndiscussing the design and UBSan issues and stuff during the review).\n\nThanks.\n"},{"id":"478016","messageId":"20230603100117.2276-1-tenglong.tl@alibaba-inc.com","threadId":"59667","inReplyTo":"xmqqjzwnd93w.fsf@gitster.g","subject":"Re: [PATCH v11 0/7] notes.c: introduce \"--separator\"","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-06-03T10:01:17Z","receivedAt":"2023-06-03T10:01:31Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> In a more perfect world, you would have sent the \"--separator\" thing\n> that already supported \"--no-separator\" from the get-go.  You would\n> also have sent the 4th step that does not need ubsan fix.  You do\n> not have the luxury to do so when your series hit 'next'---you need\n> to live with your earlier mistakes.\n> \n> Until we cross the release boundary, that is.\n\nOK, I hope to make the commit more cohesive before they release, but\nI ignored that the 'next' branch is already in the release period\n(because I think it might be possible to replace it in the 'next', but\nactually it shouldn't be done), sorry for the trouble it may cause you.\n\n> I'll revert the whole thing out of 'next'.  Once the next cycle\n> opens after the dust from the release settles (i.e. around 10th of\n> June or so), ...\n\nOK.\n\n> ... please resubmit the series that does not need \"oops, we\n> probably should have added '--[no-]separator' from the get-go\" or\n> \"oops, we had undefined behaviour here, and here is a fix\", as if\n> you are a more perfect developer than you were when the version that\n> has been queued in 'next' was written (and in fact, you are, after\n> discussing the design and UBSan issues and stuff during the review).\n\nI think the v11 should be a version which is acceptable if there is\nno incoming review conversitions (The current v11 that separates\n'--no-separator' as a individual commit OR make 7/7 into 4/7, both\napproaches seem fine)?\n\nThanks.\n"},{"id":"478353","messageId":"xmqqh6ra26vq.fsf@gitster.g","threadId":"59667","inReplyTo":"ed930ef4f795f30792bc14d9c1939484e4976db8.1682671758.git.dyroneteng@gmail.com","subject":"Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-06-14T01:02:33Z","receivedAt":"2023-06-14T01:02:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This step seems to break CI asan/ubsan job\n\nhttps://github.com/git/git/actions/runs/5260417146/jobs/9507299904#step:4:1826\n\nPerhaps something like this is in order?\n\n builtin/notes.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git c/builtin/notes.c w/builtin/notes.c\nindex 6eede30597..785b1922ad 100644\n--- c/builtin/notes.c\n+++ w/builtin/notes.c\n@@ -239,7 +239,7 @@ static void write_note_data(struct note_data *d, struct object_id *oid)\n \n static void append_separator(struct strbuf *message)\n {\n-\tif (separator[strlen(separator) - 1] == '\\n')\n+\tif (*separator && separator[strlen(separator) - 1] == '\\n')\n \t\tstrbuf_addstr(message, separator);\n \telse\n \t\tstrbuf_addf(message, \"%s%s\", separator, \"\\n\");\n"},{"id":"478354","messageId":"xmqqbkhi26hy.fsf_-_@gitster.g","threadId":"59667","inReplyTo":"xmqqh6ra26vq.fsf@gitster.g","subject":"[PATCH] notes: do not access before the beginning of an array","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-06-14T01:10:49Z","receivedAt":"2023-06-14T01:10:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The check to see if the last byte of the separator string is a LF\nmakes sense *only* when the separator string is known to be non\nempty.  This has been breaking the asan/ubsan job at CI.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n * This time with a proposed log message with sign-off.\n\n builtin/notes.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 6eede30597..785b1922ad 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -239,7 +239,7 @@ static void write_note_data(struct note_data *d, struct object_id *oid)\n \n static void append_separator(struct strbuf *message)\n {\n-\tif (separator[strlen(separator) - 1] == '\\n')\n+\tif (*separator && separator[strlen(separator) - 1] == '\\n')\n \t\tstrbuf_addstr(message, separator);\n \telse\n \t\tstrbuf_addf(message, \"%s%s\", separator, \"\\n\");\n-- \n2.41.0-28-gd7d8841f67\n\n"},{"id":"478355","messageId":"CAPig+cRzx2r34tqqi527sBcDXm3h_sLR1Y7_gX9R4uQ4KUKzFA@mail.gmail.com","threadId":"59667","inReplyTo":"xmqqh6ra26vq.fsf@gitster.g","subject":"Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2023-06-14T01:41:08Z","receivedAt":"2023-06-14T01:41:23Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Jun 13, 2023 at 9:02 PM Junio C Hamano <gitster@pobox.com> wrote:\n> This step seems to break CI asan/ubsan job\n> https://github.com/git/git/actions/runs/5260417146/jobs/9507299904#step:4:1826\n> Perhaps something like this is in order?\n>\n> diff --git c/builtin/notes.c w/builtin/notes.c\n> @@ -239,7 +239,7 @@ static void write_note_data(struct note_data *d, struct object_id *oid)\n>  static void append_separator(struct strbuf *message)\n>  {\n> -       if (separator[strlen(separator) - 1] == '\\n')\n> +       if (*separator && separator[strlen(separator) - 1] == '\\n')\n\nIs this the same issue Peff reported[1]? His proposed solution was a\nbit different.\n\n[1]: https://lore.kernel.org/git/20230519005447.GA2955320@coredump.intra.peff.net/\n"},{"id":"478357","messageId":"xmqq7cs623v9.fsf@gitster.g","threadId":"59667","inReplyTo":"CAPig+cRzx2r34tqqi527sBcDXm3h_sLR1Y7_gX9R4uQ4KUKzFA@mail.gmail.com","subject":"Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-06-14T02:07:38Z","receivedAt":"2023-06-14T02:07:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> Is this the same issue Peff reported[1]? His proposed solution was a\n> bit different.\n>\n> [1]: https://lore.kernel.org/git/20230519005447.GA2955320@coredump.intra.peff.net/\n\nAh, I should have checked before merging the topic in 'next'.\n"},{"id":"478431","messageId":"20230615071314.GC1460739@coredump.intra.peff.net","threadId":"59667","inReplyTo":"xmqq7cs623v9.fsf@gitster.g","subject":"Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2023-06-15T07:13:14Z","receivedAt":"2023-06-15T07:16:54Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jun 13, 2023 at 07:07:38PM -0700, Junio C Hamano wrote:\n\n> Eric Sunshine <sunshine@sunshineco.com> writes:\n> \n> > Is this the same issue Peff reported[1]? His proposed solution was a\n> > bit different.\n> >\n> > [1]: https://lore.kernel.org/git/20230519005447.GA2955320@coredump.intra.peff.net/\n> \n> Ah, I should have checked before merging the topic in 'next'.\n\nI only found it after it was in 'next'. :)\n\nIt originally did not cause problems, but the switch to clang started\ntriggering it in CI (it was this case that motivated me to send those\npatches CI patches).\n\nI was not following the topic closely, but I think there is a v10 of\nthis series anyway, so you may want to eject it from 'next' in the\nmeantime (I had assumed you were going to do so as part of the\npost-release rewind, but it looks like that happened already and it got\nre-merged).\n\n-Peff\n"},{"id":"478448","messageId":"xmqqmt10v8nl.fsf@gitster.g","threadId":"59667","inReplyTo":"20230615071314.GC1460739@coredump.intra.peff.net","subject":"Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-06-15T19:15:58Z","receivedAt":"2023-06-15T19:16:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> I was not following the topic closely, but I think there is a v10 of\n> this series anyway, so you may want to eject it from 'next' in the\n> meantime (I had assumed you were going to do so as part of the\n> post-release rewind, but it looks like that happened already and it got\n> re-merged).\n\nI can do the post-release rewind again ;-)\n\nI somehow had an impression that the topic was more or less done,\nbut if it deserves another chance, let's give it one.\n\nThanks.\n\n"},{"id":"478524","messageId":"20230619060815.2291-1-tenglong.tl@alibaba-inc.com","threadId":"59667","inReplyTo":"xmqqmt10v8nl.fsf@gitster.g","subject":"Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-06-19T06:08:15Z","receivedAt":"2023-06-19T06:08:24Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Jeff King <peff@peff.net> writes:\n> \n> > I was not following the topic closely, but I think there is a v10 of\n> > this series anyway, so you may want to eject it from 'next' in the\n> > meantime (I had assumed you were going to do so as part of the\n> > post-release rewind, but it looks like that happened already and it got\n> > re-merged).\n> \n> I can do the post-release rewind again ;-)\n> \n> I somehow had an impression that the topic was more or less done,\n> but if it deserves another chance, let's give it one.\n> \n> Thanks.\n\nI have been on vacation for the last two weeks, sorry for not\nreplying in time.\n\nI think that is :\n\n  time-1: v9 was in next, actually v9 [4/6] will break the CI, but\n          [5/6] fix it so the CI passed on the whole patchset.\n  time-2: then v10 was tought a new `--no-separator ` mainly\n          On the release point, v10 implicitly fixed the problem\n          in v9 [4/6]\n  time-3: 2.41 release work begin\n  time-4: Peff found that the v9 [4/6] merged in next would break\n          CI and a UBSan problem.\n  time-5: I wanted to make a full reroll to fix the UBSan problem,\n          by the way, separate the \"--no-separator\" to a single\n          commit, because I thought it cannot catch the last bus\n          on 2.41 maybe.\n\nI think we are now in a new cycle, maybe we could rewind and use\nv11 directlly? Sorry that it may have brought on extra work.\n\nThanks.\n"},{"id":"478593","messageId":"xmqq1qi5na64.fsf@gitster.g","threadId":"59667","inReplyTo":"20230619060815.2291-1-tenglong.tl@alibaba-inc.com","subject":"Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-06-20T20:36:19Z","receivedAt":"2023-06-20T20:36:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Teng Long <dyroneteng@gmail.com> writes:\n\n> I think we are now in a new cycle, maybe we could rewind and use\n> v11 directlly? Sorry that it may have brought on extra work.\n\nI think the tl/notes-separator topic is no longer in 'next' so we\ncan do whatever you like to it ;-).  It would be easy to just\nreplace the whole thing with newly reviewed version.\n\nThanks.\n"},{"id":"478608","messageId":"20230621025027.1996-1-tenglong.tl@alibaba-inc.com","threadId":"59667","inReplyTo":"xmqq1qi5na64.fsf@gitster.g","subject":"Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-06-21T02:50:27Z","receivedAt":"2023-06-21T02:50:37Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"> Teng Long <dyroneteng@gmail.com> writes:\n> \n> > I think we are now in a new cycle, maybe we could rewind and use\n> > v11 directlly? Sorry that it may have brought on extra work.\n> \n> I think the tl/notes-separator topic is no longer in 'next' so we\n> can do whatever you like to it ;-).  It would be easy to just\n> replace the whole thing with newly reviewed version.\n\nYes, v11 fixed the UBSan problem which found by Peff and also the broken CI\nunder run a bisection (both are found in v9 which already be rewinded in\n'next'). If there are no new comments on v11, I think maybe it's what we want at\npresent.\n\nThanks.\n"}]}