threads / patch / 59667

v9, 6 partsnotes.c: cleanup 'strbuf_grow' call in 'append_edit'

Subject: [PATCH v9 1/6] notes.c: cleanup 'strbuf_grow' call in 'append_edit'

## tl;dr

55 messages between Apr 28, 2023 and Jun 21, 2023. Diffs are folded; open one to read it.

replies: 54people: 5as markdown or json

Teng Long· Apr 28, 2023, 09:23 UTC · lore

[PATCH v9 0/6] notes.c: introduce "--separator" option

From: Teng Long <dyroneteng@gmail.com>
Diff since v8:
1. test case: Uniform indent format, as recommended by Junio C Hamano.
2. [4/6] make the static var "separator" initialized as "\n", simplify
   the code in "insert_separator(...)".
3. [4/6] I don't change the other parts about the "struct note_msg", the
   stripspace way (Junio suggest to consider about the stripspace each messge
   individually, but I found it will break the compatibility about "-C",
   which can be found the case of 'reuse with "-C" and add note with "-m",
   "-m" will stripspace all together').
4. [5/6] Optimized the commit message and replace "strbuf_insert*(...)" with
   "strbuf_add*(...)".
5. [6/6] As Junio replied, I'm not sure whether the "-C" problem (When the
   "-C" argument is used with "-m/-F", the order of "-C" in the options will
   affect the result of stripspace differently,) is need to be fixed or keep
   as is, I choose to do not break the old behaviour (In fact, I hope to fix
   this issue in another patch, if at all, and let this long-tailed patchset
   to mature faster, maybe).
Thanks.
Teng Long (6):
  notes.c: cleanup 'strbuf_grow' call in 'append_edit'
  notes.c: use designated initializers for clarity
  t3321: add test cases about the notes stripspace behavior
  notes.c: introduce '--separator=<paragraph-break>' option
  notes.c: append separator instead of insert by pos
  notes.c: introduce "--[no-]stripspace" option
 Documentation/git-notes.txt |  42 ++-
 builtin/notes.c             | 141 ++++++---
 t/t3301-notes.sh            | 126 ++++++++
 t/t3321-notes-stripspace.sh | 577 ++++++++++++++++++++++++++++++++++++
 4 files changed, 844 insertions(+), 42 deletions(-)
 create mode 100755 t/t3321-notes-stripspace.sh
Range-diff against v8:
1:  0634434e = 1:  0634434e notes.c: cleanup 'strbuf_grow' call in 'append_edit'
2:  4ad78405 = 2:  4ad78405 notes.c: use designated initializers for clarity
3:  6dfb5bf2 ! 3:  c2fc2091 t3321: add test cases about the notes stripspace behavior
    @@ t/t3321-notes-stripspace.sh (new)
     @@
     +#!/bin/sh
     +#
    -+# Copyright (c) 2007 Teng Long
    ++# Copyright (c) 2023 Teng Long
     +#
     +
     +test_description='Test commit notes with stripspace behavior'
    @@ t/t3321-notes-stripspace.sh (new)
     +test_expect_success 'add note by editor' '
     +	test_when_finished "git notes remove" &&
     +	cat >expect <<-EOF &&
    -+		first-line
    ++	first-line
     +
    -+		second-line
    ++	second-line
     +	EOF
     +
     +	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes add  &&
    @@ t/t3321-notes-stripspace.sh (new)
     +test_expect_success 'add note by specifying single "-m"' '
     +	test_when_finished "git notes remove" &&
     +	cat >expect <<-EOF &&
    -+		first-line
    ++	first-line
     +
    -+		second-line
    ++	second-line
     +	EOF
     +
     +	git notes add -m "${LF}first-line${MULTI_LF}second-line${LF}" &&
    @@ t/t3321-notes-stripspace.sh (new)
     +test_expect_success 'append note by editor' '
     +	test_when_finished "git notes remove" &&
     +	cat >expect <<-EOF &&
    -+		first-line
    ++	first-line
     +
    -+		second-line
    ++	second-line
     +	EOF
     +
     +	git notes add -m "first-line" &&
    @@ t/t3321-notes-stripspace.sh (new)
     +test_expect_success 'append note by specifying single "-m"' '
     +	test_when_finished "git notes remove" &&
     +	cat >expect <<-EOF &&
    -+		first-line
    ++	first-line
     +
    -+		second-line
    ++	second-line
     +	EOF
     +
     +	git notes add -m "${LF}first-line" &&
    @@ t/t3321-notes-stripspace.sh (new)
     +test_expect_success 'add note by specifying single "-F"' '
     +	test_when_finished "git notes remove" &&
     +	cat >expect <<-EOF &&
    -+		first-line
    ++	first-line
     +
    -+		second-line
    ++	second-line
     +	EOF
     +
     +	cat >note-file <<-EOF &&
    -+		${LF}
    -+		first-line
    -+		${MULTI_LF}
    -+		second-line
    -+		${LF}
    ++	${LF}
    ++	first-line
    ++	${MULTI_LF}
    ++	second-line
    ++	${LF}
     +	EOF
     +
     +	git notes add -F note-file &&
    @@ t/t3321-notes-stripspace.sh (new)
     +test_expect_success 'add notes by specifying multiple "-F"' '
     +	test_when_finished "git notes remove" &&
     +	cat >expect <<-EOF &&
    -+		file-1-first-line
    ++	file-1-first-line
     +
    -+		file-1-second-line
    ++	file-1-second-line
     +
    -+		file-2-first-line
    ++	file-2-first-line
     +
    -+		file-2-second-line
    ++	file-2-second-line
     +	EOF
     +
     +	cat >note-file-1 <<-EOF &&
    -+		${LF}
    -+		file-1-first-line
    -+		${MULTI_LF}
    -+		file-1-second-line
    -+		${LF}
    ++	${LF}
    ++	file-1-first-line
    ++	${MULTI_LF}
    ++	file-1-second-line
    ++	${LF}
     +	EOF
     +
     +	cat >note-file-2 <<-EOF &&
    -+		${LF}
    -+		file-2-first-line
    -+		${MULTI_LF}
    -+		file-2-second-line
    -+		${LF}
    ++	${LF}
    ++	file-2-first-line
    ++	${MULTI_LF}
    ++	file-2-second-line
    ++	${LF}
     +	EOF
     +
     +	git notes add -F note-file-1 -F note-file-2 &&
    @@ t/t3321-notes-stripspace.sh (new)
     +test_expect_success 'append note by specifying single "-F"' '
     +	test_when_finished "git notes remove" &&
     +	cat >expect <<-EOF &&
    -+		initial-line
    ++	initial-line
     +
    -+		first-line
    ++	first-line
     +
    -+		second-line
    ++	second-line
     +	EOF
     +
     +	cat >note-file <<-EOF &&
    -+		${LF}
    -+		first-line
    -+		${MULTI_LF}
    -+		second-line
    -+		${LF}
    ++	${LF}
    ++	first-line
    ++	${MULTI_LF}
    ++	second-line
    ++	${LF}
     +	EOF
     +
     +	git notes add -m "initial-line" &&
    @@ t/t3321-notes-stripspace.sh (new)
     +test_expect_success 'append notes by specifying multiple "-F"' '
     +	test_when_finished "git notes remove" &&
     +	cat >expect <<-EOF &&
    -+		initial-line
    ++	initial-line
     +
    -+		file-1-first-line
    ++	file-1-first-line
     +
    -+		file-1-second-line
    ++	file-1-second-line
     +
    -+		file-2-first-line
    ++	file-2-first-line
     +
    -+		file-2-second-line
    ++	file-2-second-line
     +	EOF
     +
     +	cat >note-file-1 <<-EOF &&
    -+		${LF}
    -+		file-1-first-line
    -+		${MULTI_LF}
    -+		file-1-second-line
    -+		${LF}
    ++	${LF}
    ++	file-1-first-line
    ++	${MULTI_LF}
    ++	file-1-second-line
    ++	${LF}
     +	EOF
     +
     +	cat >note-file-2 <<-EOF &&
    -+		${LF}
    -+		file-2-first-line
    -+		${MULTI_LF}
    -+		file-2-second-line
    -+		${LF}
    ++	${LF}
    ++	file-2-first-line
    ++	${MULTI_LF}
    ++	file-2-second-line
    ++	${LF}
     +	EOF
     +
     +	git notes add -m "initial-line" &&
    @@ t/t3321-notes-stripspace.sh (new)
     +test_expect_success 'add note by specifying "-C" , do not stripspace is the default behavior' '
     +	test_when_finished "git notes remove" &&
     +	cat >expect <<-EOF &&
    -+		${LF}
    -+		first-line
    -+		${MULTI_LF}
    -+		second-line
    -+		${LF}
    ++	${LF}
    ++	first-line
    ++	${MULTI_LF}
    ++	second-line
    ++	${LF}
     +	EOF
     +
     +	cat expect | git hash-object -w --stdin >blob &&
    @@ t/t3321-notes-stripspace.sh (new)
     +test_expect_success 'add notes with "-C" and "-m", "-m" will stripspace all together' '
     +	test_when_finished "git notes remove" &&
     +	cat >data <<-EOF &&
    -+		${LF}
    -+		first-line
    -+		${MULTI_LF}
    -+		second-line
    -+		${LF}
    ++	${LF}
    ++	first-line
    ++	${MULTI_LF}
    ++	second-line
    ++	${LF}
     +	EOF
     +
     +	cat >expect <<-EOF &&
    -+		first-line
    ++	first-line
     +
    -+		second-line
    ++	second-line
     +
    -+		third-line
    ++	third-line
     +	EOF
     +
     +	cat data | git hash-object -w --stdin >blob &&
    @@ t/t3321-notes-stripspace.sh (new)
     +	test_when_finished "git notes remove" &&
     +	cat >data <<-EOF &&
     +
    -+		second-line
    ++	second-line
     +	EOF
     +
     +	cat >expect <<-EOF &&
    -+		first-line
    -+		${LF}
    -+		second-line
    ++	first-line
    ++	${LF}
    ++	second-line
     +	EOF
     +
     +	cat data | git hash-object -w --stdin >blob &&
4:  be86f9ca ! 4:  ed930ef4 notes.c: introduce '--separator=<paragraph-break>' option
    @@ Commit message
         insert a blank line between the paragraphs, like:
     
              $ git notes add -m foo -m bar
    -         $ git notes show HEAD | cat
    +         $ git notes show HEAD
              foo
     
              bar
    @@ Commit message
         'git notes append', for example when executing:
     
             $ git notes add -m foo -m bar --separator="-"
    -        $ git notes show HEAD | cat
    +        $ git notes show HEAD
             foo
             -
             bar
    @@ builtin/notes.c
      #include "worktree.h"
      #include "write-or-die.h"
      
    -+static char *separator = NULL;
    ++static char *separator = "\n";
      static const char * const git_notes_usage[] = {
      	N_("git notes [--ref <notes-ref>] [list [<object>]]"),
     -	N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
    @@ builtin/notes.c: static void free_note_data(struct note_data *d)
      	}
      	strbuf_release(&d->buf);
     +
    -+	while (d->msg_nr) {
    -+		--d->msg_nr;
    ++	while (d->msg_nr--) {
     +		strbuf_release(&d->messages[d->msg_nr]->buf);
     +		free(d->messages[d->msg_nr]);
     +	}
    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_
      
     +static void insert_separator(struct strbuf *message, size_t pos)
     +{
    -+	if (!separator)
    -+		strbuf_insertstr(message, pos, "\n");
    -+	else if (separator[strlen(separator) - 1] == '\n')
    -+		strbuf_insertstr(message, pos, separator);
    ++	if (separator[strlen(separator) - 1] == '\n')
    ++		strbuf_addstr(message, separator);
     +	else
     +		strbuf_insertf(message, pos, "%s%s", separator, "\n");
     +}
5:  ef40e0ef ! 5:  eea2246f notes.c: append separator instead of insert by pos
    @@ Metadata
      ## Commit message ##
         notes.c: append separator instead of insert by pos
     
    -    This commit rename "insert_separator" to "append_separator" and also
    -    remove the "postion" argument, this serves two purpose:
    +    Rename "insert_separator" to "append_separator" and also remove the
    +    "postion" argument, this serves two purpose:
     
         The first is that when specifying more than one "-m" ( like "-F", etc)
         to "git notes add" or "git notes append", the order of them matters,
    @@ Commit message
         so we don't have to make the caller specify the position, the "append"
         operation is enough and clear.
     
    -    The second is that when we execute the "git notes append" subcommand
    -    , we need to combine the "prev_note" and "current_note" to get the
    +    The second is that when we execute the "git notes append" subcommand,
    +    we need to combine the "prev_note" and "current_note" to get the
         final result. Before, we inserted a newline character at the beginning
         of "current_note". Now, we will append a newline to the end of
         "prev_note" instead, this will give the consisitent results.
    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_
     -static void insert_separator(struct strbuf *message, size_t pos)
     +static void append_separator(struct strbuf *message)
      {
    - 	if (!separator)
    --		strbuf_insertstr(message, pos, "\n");
    -+		strbuf_insertstr(message, message->len, "\n");
    - 	else if (separator[strlen(separator) - 1] == '\n')
    --		strbuf_insertstr(message, pos, separator);
    -+		strbuf_insertstr(message, message->len, separator);
    + 	if (separator[strlen(separator) - 1] == '\n')
    + 		strbuf_addstr(message, separator);
      	else
     -		strbuf_insertf(message, pos, "%s%s", separator, "\n");
    -+		strbuf_insertf(message, message->len, "%s%s", separator, "\n");
    ++		strbuf_addf(message, "%s%s", separator, "\n");
      }
      
      static void concat_messages(struct note_data *d)
6:  f60f7432 ! 6:  20063bea notes.c: introduce "--[no-]stripspace" option
    @@ t/t3321-notes-stripspace.sh: test_expect_success 'add note by editor' '
     +test_expect_success 'add note by specifying single "-m", "--stripspace" is the default behavior' '
      	test_when_finished "git notes remove" &&
      	cat >expect <<-EOF &&
    - 		first-line
    + 	first-line
     @@ t/t3321-notes-stripspace.sh: test_expect_success 'add note by specifying single "-m"' '
      
      	git notes add -m "${LF}first-line${MULTI_LF}second-line${LF}" &&
    @@ t/t3321-notes-stripspace.sh: test_expect_success 'add note by specifying single
     +test_expect_success 'add note by specifying single "-m" and "--no-stripspace" ' '
     +	test_when_finished "git notes remove" &&
     +	cat >expect <<-EOF &&
    -+		${LF}first-line${MULTI_LF}second-line
    ++	${LF}first-line${MULTI_LF}second-line
     +	EOF
     +
     +	git notes add --no-stripspace \
    @@ t/t3321-notes-stripspace.sh: test_expect_success 'add note by specifying multipl
      	test_cmp expect actual
      '
      
    --
    --test_expect_success 'append note by editor' '
     +test_expect_success 'add notes by specifying multiple "-m" and "--no-stripspace"' '
    - 	test_when_finished "git notes remove" &&
    - 	cat >expect <<-EOF &&
    -+		${LF}
    - 		first-line
    --
    --		second-line
    -+		${MULTI_LF}
    -+		second-line${LF}
    - 	EOF
    - 
    --	git notes add -m "first-line" &&
    --	MSG="${MULTI_LF}second-line${LF}" git notes append  &&
    ++	test_when_finished "git notes remove" &&
    ++	cat >expect <<-EOF &&
    ++	${LF}
    ++	first-line
    ++	${MULTI_LF}
    ++	second-line${LF}
    ++	EOF
    ++
     +	git notes add --no-stripspace \
     +		      -m "${LF}" \
     +		      -m "first-line" \
     +		      -m "${MULTI_LF}" \
     +		      -m "second-line" \
     +		      -m "${LF}" &&
    - 	git notes show >actual &&
    - 	test_cmp expect actual
    - '
    - 
    --test_expect_success 'append note by specifying single "-m"' '
    -+test_expect_success 'add note by specifying single "-F", "--stripspace" is the default behavior' '
    - 	test_when_finished "git notes remove" &&
    - 	cat >expect <<-EOF &&
    - 		first-line
    -@@ t/t3321-notes-stripspace.sh: test_expect_success 'append note by specifying single "-m"' '
    - 		second-line
    - 	EOF
    - 
    --	git notes add -m "${LF}first-line" &&
    --	git notes append -m "${MULTI_LF}second-line${LF}" &&
    --	git notes show >actual &&
    --	test_cmp expect actual
    --'
    --
    --test_expect_success 'append note by specifying multiple "-m"' '
    --	test_when_finished "git notes remove" &&
    --	cat >expect <<-EOF &&
    --	first-line
    --
    --	second-line
    -+	cat >note-file <<-EOF &&
    -+		${LF}
    -+		first-line
    -+		${MULTI_LF}
    -+		second-line
    -+		${LF}
    - 	EOF
    - 
    --	git notes add -m "${LF}first-line" &&
    --	git notes append -m "${MULTI_LF}" \
    --		      -m "second-line" \
    --		      -m "${LF}" &&
    -+	git notes add -F note-file &&
    - 	git notes show >actual &&
    --	test_cmp expect actual
    -+	test_cmp expect actual &&
    -+	git notes remove &&
    -+	git notes add --stripspace -F note-file &&
    -+	git notes show >actual
    - '
    - 
    --test_expect_success 'add note by specifying single "-F"' '
    -+test_expect_success 'add note by specifying single "-F" and "--no-stripspace"' '
    - 	test_when_finished "git notes remove" &&
    - 	cat >expect <<-EOF &&
    -+		${LF}
    - 		first-line
    --
    -+		${MULTI_LF}
    - 		second-line
    -+		${LF}
    - 	EOF
    - 
    - 	cat >note-file <<-EOF &&
    -@@ t/t3321-notes-stripspace.sh: test_expect_success 'add note by specifying single "-F"' '
    - 		${LF}
    - 	EOF
    - 
    --	git notes add -F note-file &&
    -+	git notes add --no-stripspace -F note-file &&
    - 	git notes show >actual &&
    - 	test_cmp expect actual
    - '
    - 
    --test_expect_success 'add notes by specifying multiple "-F"' '
    -+test_expect_success 'add note by specifying multiple "-F", "--stripspace" is the default behavior' '
    - 	test_when_finished "git notes remove" &&
    - 	cat >expect <<-EOF &&
    - 		file-1-first-line
    -@@ t/t3321-notes-stripspace.sh: test_expect_success 'add notes by specifying multiple "-F"' '
    - 
    - 	git notes add -F note-file-1 -F note-file-2 &&
    - 	git notes show >actual &&
    -+	test_cmp expect actual &&
    -+	git notes remove &&
    -+	git notes add --stripspace -F note-file-1 -F note-file-2 &&
     +	git notes show >actual &&
     +	test_cmp expect actual
     +'
     +
    -+test_expect_success 'add note by specifying multiple "-F" with "--no-stripspace"' '
    ++test_expect_success 'add note by specifying single "-F", "--stripspace" is the default behavior' '
     +	test_when_finished "git notes remove" &&
     +	cat >expect <<-EOF &&
    -+		${LF}
    -+		file-1-first-line
    -+		${MULTI_LF}
    -+		file-1-second-line
    -+		${LF}
    -+
    -+		${LF}
    -+		file-2-first-line
    -+		${MULTI_LF}
    -+		file-2-second-line
    -+		${LF}
    -+	EOF
    ++	first-line
     +
    -+	cat >note-file-1 <<-EOF &&
    -+		${LF}
    -+		file-1-first-line
    -+		${MULTI_LF}
    -+		file-1-second-line
    -+		${LF}
    ++	second-line
     +	EOF
     +
    -+	cat >note-file-2 <<-EOF &&
    -+		${LF}
    -+		file-2-first-line
    -+		${MULTI_LF}
    -+		file-2-second-line
    -+		${LF}
    ++	cat >note-file <<-EOF &&
    ++	${LF}
    ++	first-line
    ++	${MULTI_LF}
    ++	second-line
    ++	${LF}
     +	EOF
     +
    -+	git notes add --no-stripspace -F note-file-1 -F note-file-2 &&
    ++	git notes add -F note-file &&
     +	git notes show >actual &&
    -+	test_cmp expect actual
    ++	test_cmp expect actual &&
    ++	git notes remove &&
    ++	git notes add --stripspace -F note-file &&
    ++	git notes show >actual
     +'
     +
    -+test_expect_success 'append note by editor' '
    ++test_expect_success 'add note by specifying single "-F" and "--no-stripspace"' '
     +	test_when_finished "git notes remove" &&
     +	cat >expect <<-EOF &&
    -+		first-line
    ++	${LF}
    ++	first-line
    ++	${MULTI_LF}
    ++	second-line
    ++	${LF}
    ++	EOF
     +
    -+		second-line
    ++	cat >note-file <<-EOF &&
    ++	${LF}
    ++	first-line
    ++	${MULTI_LF}
    ++	second-line
    ++	${LF}
     +	EOF
     +
    -+	git notes add -m "first-line" &&
    -+	MSG="${MULTI_LF}second-line${LF}" git notes append  &&
    ++	git notes add --no-stripspace -F note-file &&
     +	git notes show >actual &&
     +	test_cmp expect actual
     +'
     +
    -+test_expect_success 'append note by specifying single "-m"' '
    ++test_expect_success 'add note by specifying multiple "-F", "--stripspace" is the default behavior' '
     +	test_when_finished "git notes remove" &&
     +	cat >expect <<-EOF &&
    -+		first-line
    ++	file-1-first-line
    ++
    ++	file-1-second-line
    ++
    ++	file-2-first-line
     +
    -+		second-line
    ++	file-2-second-line
    ++	EOF
    ++
    ++	cat >note-file-1 <<-EOF &&
    ++	${LF}
    ++	file-1-first-line
    ++	${MULTI_LF}
    ++	file-1-second-line
    ++	${LF}
    ++	EOF
    ++
    ++	cat >note-file-2 <<-EOF &&
    ++	${LF}
    ++	file-2-first-line
    ++	${MULTI_LF}
    ++	file-2-second-line
    ++	${LF}
     +	EOF
     +
    -+	git notes add -m "${LF}first-line" &&
    -+	git notes append -m "${MULTI_LF}second-line${LF}" &&
    ++	git notes add -F note-file-1 -F note-file-2 &&
    ++	git notes show >actual &&
    ++	test_cmp expect actual &&
    ++	git notes remove &&
    ++	git notes add --stripspace -F note-file-1 -F note-file-2 &&
     +	git notes show >actual &&
     +	test_cmp expect actual
     +'
     +
    -+test_expect_success 'append note by specifying multiple "-m"' '
    ++test_expect_success 'add note by specifying multiple "-F" with "--no-stripspace"' '
     +	test_when_finished "git notes remove" &&
     +	cat >expect <<-EOF &&
    -+	first-line
    ++	${LF}
    ++	file-1-first-line
    ++	${MULTI_LF}
    ++	file-1-second-line
    ++	${LF}
    ++
    ++	${LF}
    ++	file-2-first-line
    ++	${MULTI_LF}
    ++	file-2-second-line
    ++	${LF}
    ++	EOF
     +
    -+	second-line
    ++	cat >note-file-1 <<-EOF &&
    ++	${LF}
    ++	file-1-first-line
    ++	${MULTI_LF}
    ++	file-1-second-line
    ++	${LF}
    ++	EOF
    ++
    ++	cat >note-file-2 <<-EOF &&
    ++	${LF}
    ++	file-2-first-line
    ++	${MULTI_LF}
    ++	file-2-second-line
    ++	${LF}
     +	EOF
     +
    -+	git notes add -m "${LF}first-line" &&
    -+	git notes append -m "${MULTI_LF}" -m "second-line" -m "${LF}" &&
    ++	git notes add --no-stripspace -F note-file-1 -F note-file-2 &&
     +	git notes show >actual &&
    - 	test_cmp expect actual
    - '
    ++	test_cmp expect actual
    ++'
      
    + test_expect_success 'append note by editor' '
    + 	test_when_finished "git notes remove" &&
     @@ t/t3321-notes-stripspace.sh: test_expect_success 'append notes by specifying multiple "-F"' '
      	test_cmp expect actual
      '
    @@ t/t3321-notes-stripspace.sh: test_expect_success 'append notes by specifying mul
     +test_expect_success 'append note by specifying multiple "-F" with "--no-stripspace"' '
     +	test_when_finished "git notes remove" &&
     +	cat >expect <<-EOF &&
    -+		initial-line
    -+		${LF}${LF}
    -+		file-1-first-line
    -+		${MULTI_LF}
    -+		file-1-second-line
    -+		${LF}
    -+
    -+		${LF}
    -+		file-2-first-line
    -+		${MULTI_LF}
    -+		file-2-second-line
    -+		${LF}
    ++	initial-line
    ++	${LF}${LF}
    ++	file-1-first-line
    ++	${MULTI_LF}
    ++	file-1-second-line
    ++	${LF}
    ++
    ++	${LF}
    ++	file-2-first-line
    ++	${MULTI_LF}
    ++	file-2-second-line
    ++	${LF}
     +	EOF
     +
     +	cat >note-file-1 <<-EOF &&
    -+		${LF}
    -+		file-1-first-line
    -+		${MULTI_LF}
    -+		file-1-second-line
    -+		${LF}
    ++	${LF}
    ++	file-1-first-line
    ++	${MULTI_LF}
    ++	file-1-second-line
    ++	${LF}
     +	EOF
     +
     +	cat >note-file-2 <<-EOF &&
    -+		${LF}
    -+		file-2-first-line
    -+		${MULTI_LF}
    -+		file-2-second-line
    -+		${LF}
    ++	${LF}
    ++	file-2-first-line
    ++	${MULTI_LF}
    ++	file-2-second-line
    ++	${LF}
     +	EOF
     +
     +	git notes add -m "initial-line" &&
    @@ t/t3321-notes-stripspace.sh: test_expect_success 'add notes with empty messages'
     +test_expect_success 'add note by specifying "-C", "--no-stripspace" is the default behavior' '
      	test_when_finished "git notes remove" &&
      	cat >expect <<-EOF &&
    - 		${LF}
    + 	${LF}
     @@ t/t3321-notes-stripspace.sh: test_expect_success 'add note by specifying "-C" , do not stripspace is the defa
      	cat expect | git hash-object -w --stdin >blob &&
      	git notes add -C $(cat blob) &&
    @@ t/t3321-notes-stripspace.sh: test_expect_success 'add note by specifying "-C" ,
     +test_expect_success 'reuse note by specifying "-C" and "--stripspace"' '
     +	test_when_finished "git notes remove" &&
     +	cat >data <<-EOF &&
    -+		${LF}
    -+		first-line
    -+		${MULTI_LF}
    -+		second-line
    -+		${LF}
    ++	${LF}
    ++	first-line
    ++	${MULTI_LF}
    ++	second-line
    ++	${LF}
     +	EOF
     +
     +	cat >expect <<-EOF &&
    -+		first-line
    ++	first-line
     +
    -+		second-line
    ++	second-line
     +	EOF
     +
     +	cat data | git hash-object -w --stdin >blob &&
    @@ t/t3321-notes-stripspace.sh: test_expect_success 'add note by specifying "-C" ,
     +test_expect_success 'reuse with "-C" and add note with "-m", "-m" will stripspace all together' '
      	test_when_finished "git notes remove" &&
      	cat >data <<-EOF &&
    - 		${LF}
    + 	${LF}
     @@ t/t3321-notes-stripspace.sh: test_expect_success 'add notes with "-C" and "-m", "-m" will stripspace all toge
      	test_cmp expect actual
      '
    @@ t/t3321-notes-stripspace.sh: test_expect_success 'add notes with "-m" and "-C",
     +test_expect_success 'add note by specifying "-c", "--stripspace" is the default behavior' '
     +	test_when_finished "git notes remove" &&
     +	cat >expect <<-EOF &&
    -+		first-line
    ++	first-line
     +
    -+		second-line
    ++	second-line
     +	EOF
     +
     +	echo "initial-line" | git hash-object -w --stdin >blob &&
    @@ t/t3321-notes-stripspace.sh: test_expect_success 'add notes with "-m" and "-C",
     +test_expect_success 'add note by specifying "-c" with "--no-stripspace"' '
     +	test_when_finished "git notes remove" &&
     +	cat >expect <<-EOF &&
    -+		${LF}first-line${MULTI_LF}second-line${LF}
    ++	${LF}first-line${MULTI_LF}second-line${LF}
     +	EOF
     +
     +	echo "initial-line" | git hash-object -w --stdin >blob &&
    @@ t/t3321-notes-stripspace.sh: test_expect_success 'add notes with "-m" and "-C",
     +test_expect_success 'edit note by specifying "-c", "--stripspace" is the default behavior' '
     +	test_when_finished "git notes remove" &&
     +	cat >expect <<-EOF &&
    -+		first-line
    ++	first-line
     +
    -+		second-line
    ++	second-line
     +	EOF
     +
     +	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes edit &&
    @@ t/t3321-notes-stripspace.sh: test_expect_success 'add notes with "-m" and "-C",
     +test_expect_success 'edit note by specifying "-c" with "--no-stripspace"' '
     +	test_when_finished "git notes remove" &&
     +	cat >expect <<-EOF &&
    -+		${LF}first-line${MULTI_LF}second-line${LF}
    ++	${LF}first-line${MULTI_LF}second-line${LF}
     +	EOF
     +
     +	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes add --no-stripspace &&
-- 
2.40.0.358.g2947072e.dirty
Teng Long· Apr 28, 2023, 09:23 UTC · re: Teng Long · lore
From: Teng Long <dyroneteng@gmail.com>

Let's cleanup the unnecessary 'strbuf_grow' call in 'append_edit'. This "strbuf_grow(&d.buf, size + 1);" is prepared for insert a blank line if needed, but actually when inserting, "strbuf_insertstr(&d.buf, 0, "\n");" will do the "grow" for us.

348f199b (builtin-notes: Refactor handling of -F option to allow combining -m and -F, 2010-02-13) added these to mimic the code introduced by 2347fae5 (builtin-notes: Add "append" subcommand for appending to note objects, 2010-02-13) that reads in previous note before the message. And the resulting code with explicit sizing is carried to this day.

In the context of reading an existing note in, exact sizing may have made sense, but because the resulting note needs cleansing with stripspace() when appending with this option, such an exact sizing does not buy us all that much in practice.

It may help avoiding overallocation due to ALLOC_GROW() slop, but nobody can feed so many long messages for it to matter from the command line.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Helped-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/notes.c | 2 --
 1 file changed, 2 deletions(-)
Show changes to builtin/notes.c +0 −2
diff --git a/builtin/notes.c b/builtin/notes.c
index 4ff44f1e..c501c6ee 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -219,7 +219,6 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
 
 	BUG_ON_OPT_NEG(unset);
 
-	strbuf_grow(&d->buf, strlen(arg) + 2);
 	if (d->buf.len)
 		strbuf_addch(&d->buf, '\n');
 	strbuf_addstr(&d->buf, arg);
@@ -623,7 +622,6 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		char *prev_buf = repo_read_object_file(the_repository, note,
 						       &type, &size);
 
-		strbuf_grow(&d.buf, size + 1);
 		if (d.buf.len && prev_buf && size)
 			strbuf_insertstr(&d.buf, 0, "\n");
 		if (prev_buf && size)
-- 
2.40.0.358.g2947072e.dirty
Teng Long· Apr 28, 2023, 09:23 UTC · re: Teng Long · lore

[PATCH v9 2/6] notes.c: use designated initializers for clarity

From: Teng Long <dyroneteng@gmail.com>

The "struct note_data d = { 0, 0, NULL, STRBUF_INIT };" style could be replaced with designated initializer for clarity.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/notes.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
Show changes to builtin/notes.c +2 −2
diff --git a/builtin/notes.c b/builtin/notes.c
index c501c6ee..9d8ca795 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -405,7 +405,7 @@ static int add(int argc, const char **argv, const char *prefix)
 	struct notes_tree *t;
 	struct object_id object, new_note;
 	const struct object_id *note;
-	struct note_data d = { 0, 0, NULL, STRBUF_INIT };
+	struct note_data d = { .buf = STRBUF_INIT };
 	struct option options[] = {
 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
 			N_("note contents as a string"), PARSE_OPT_NONEG,
@@ -571,7 +571,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 	const struct object_id *note;
 	char *logmsg;
 	const char * const *usage;
-	struct note_data d = { 0, 0, NULL, STRBUF_INIT };
+	struct note_data d = { .buf = STRBUF_INIT };
 	struct option options[] = {
 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
 			N_("note contents as a string"), PARSE_OPT_NONEG,
-- 
2.40.0.358.g2947072e.dirty
Teng Long· Apr 28, 2023, 09:23 UTC · re: Teng Long · lore

[PATCH v9 3/6] t3321: add test cases about the notes stripspace behavior

From: Teng Long <dyroneteng@gmail.com>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 t/t3321-notes-stripspace.sh | 291 ++++++++++++++++++++++++++++++++++++
 1 file changed, 291 insertions(+)
 create mode 100755 t/t3321-notes-stripspace.sh
Show changes to t/t3321-notes-stripspace.sh +291 −0
diff --git a/t/t3321-notes-stripspace.sh b/t/t3321-notes-stripspace.sh
new file mode 100755
index 00000000..89977873
--- /dev/null
+++ b/t/t3321-notes-stripspace.sh
@@ -0,0 +1,291 @@
+#!/bin/sh
+#
+# Copyright (c) 2023 Teng Long
+#
+
+test_description='Test commit notes with stripspace behavior'
+
+. ./test-lib.sh
+
+MULTI_LF="$LF$LF$LF"
+write_script fake_editor <<\EOF
+echo "$MSG" >"$1"
+echo "$MSG" >&2
+EOF
+GIT_EDITOR=./fake_editor
+export GIT_EDITOR
+
+test_expect_success 'setup the commit' '
+	test_commit 1st
+'
+
+test_expect_success 'add note by editor' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes add  &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying single "-m"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	git notes add -m "${LF}first-line${MULTI_LF}second-line${LF}" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying multiple "-m"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	git notes add -m "${LF}" \
+		      -m "first-line" \
+		      -m "${MULTI_LF}" \
+		      -m "second-line" \
+		      -m "${LF}" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+
+test_expect_success 'append note by editor' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	git notes add -m "first-line" &&
+	MSG="${MULTI_LF}second-line${LF}" git notes append  &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append note by specifying single "-m"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	git notes add -m "${LF}first-line" &&
+	git notes append -m "${MULTI_LF}second-line${LF}" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append note by specifying multiple "-m"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	git notes add -m "${LF}first-line" &&
+	git notes append -m "${MULTI_LF}" \
+		      -m "second-line" \
+		      -m "${LF}" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying single "-F"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	cat >note-file <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	git notes add -F note-file &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add notes by specifying multiple "-F"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	file-1-first-line
+
+	file-1-second-line
+
+	file-2-first-line
+
+	file-2-second-line
+	EOF
+
+	cat >note-file-1 <<-EOF &&
+	${LF}
+	file-1-first-line
+	${MULTI_LF}
+	file-1-second-line
+	${LF}
+	EOF
+
+	cat >note-file-2 <<-EOF &&
+	${LF}
+	file-2-first-line
+	${MULTI_LF}
+	file-2-second-line
+	${LF}
+	EOF
+
+	git notes add -F note-file-1 -F note-file-2 &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append note by specifying single "-F"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	initial-line
+
+	first-line
+
+	second-line
+	EOF
+
+	cat >note-file <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	git notes add -m "initial-line" &&
+	git notes append -F note-file &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append notes by specifying multiple "-F"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	initial-line
+
+	file-1-first-line
+
+	file-1-second-line
+
+	file-2-first-line
+
+	file-2-second-line
+	EOF
+
+	cat >note-file-1 <<-EOF &&
+	${LF}
+	file-1-first-line
+	${MULTI_LF}
+	file-1-second-line
+	${LF}
+	EOF
+
+	cat >note-file-2 <<-EOF &&
+	${LF}
+	file-2-first-line
+	${MULTI_LF}
+	file-2-second-line
+	${LF}
+	EOF
+
+	git notes add -m "initial-line" &&
+	git notes append -F note-file-1 -F note-file-2 &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add notes with empty messages' '
+	rev=$(git rev-parse HEAD) &&
+	git notes add -m "${LF}" \
+		      -m "${MULTI_LF}" \
+		      -m "${LF}" >actual 2>&1 &&
+	test_i18ngrep "Removing note for object" actual
+'
+
+test_expect_success 'add note by specifying "-C" , do not stripspace is the default behavior' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	cat expect | git hash-object -w --stdin >blob &&
+	git notes add -C $(cat blob) &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add notes with "-C" and "-m", "-m" will stripspace all together' '
+	test_when_finished "git notes remove" &&
+	cat >data <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+
+	third-line
+	EOF
+
+	cat data | git hash-object -w --stdin >blob &&
+	git notes add -C $(cat blob) -m "third-line" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add notes with "-m" and "-C", "-C" will not stripspace all together' '
+	test_when_finished "git notes remove" &&
+	cat >data <<-EOF &&
+
+	second-line
+	EOF
+
+	cat >expect <<-EOF &&
+	first-line
+	${LF}
+	second-line
+	EOF
+
+	cat data | git hash-object -w --stdin >blob &&
+	git notes add -m "first-line" -C $(cat blob)  &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_done
-- 
2.40.0.358.g2947072e.dirty
Teng Long· Apr 28, 2023, 09:23 UTC · re: Teng Long · lore

[PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option

From: Teng Long <dyroneteng@gmail.com>

When adding new notes or appending to an existing notes, we will insert a blank line between the paragraphs, like:

     $ git notes add -m foo -m bar
     $ git notes show HEAD
     foo
     bar

The default behavour sometimes is not enough, the user may want to use a custom delimiter between paragraphs, like when specifying '-m', '-F', '-C', '-c' options. So this commit introduce a new '--separator' option for 'git notes add' and 'git notes append', for example when executing:

    $ git notes add -m foo -m bar --separator="-"
    $ git notes show HEAD
    foo
    -
    bar

a newline is added to the value given to --separator if it does not end with one already. So when executing:

      $ git notes add -m foo -m bar --separator="-"
and
      $ export LF="
      "
      $ git notes add -m foo -m bar --separator="-$LF"
Both the two exections produce the same result.

The reason we use a "strbuf" array to concat but not "string_list", is that the binary file content may contain '\0' in the middle, this will cause the corrupt result if using a string to save.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-notes.txt |  21 ++++--
 builtin/notes.c             | 108 ++++++++++++++++++++++++-------
 t/t3301-notes.sh            | 126 ++++++++++++++++++++++++++++++++++++
 3 files changed, 226 insertions(+), 29 deletions(-)
Show changes to 3 files +226 −29

Documentation/git-notes.txt, builtin/notes.c, t/t3301-notes.sh

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index efbc10f0..59980b21 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -9,9 +9,9 @@ SYNOPSIS
 --------
 [verse]
 'git notes' [list [<object>]]
-'git notes' add [-f] [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
+'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
 'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )
-'git notes' append [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
+'git notes' append [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
 'git notes' edit [--allow-empty] [<object>]
 'git notes' show [<object>]
 'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>
@@ -65,7 +65,9 @@ add::
 	However, if you're using `add` interactively (using an editor
 	to supply the notes contents), then - instead of aborting -
 	the existing notes will be opened in the editor (like the `edit`
-	subcommand).
+	subcommand). If you specify multiple `-m` and `-F`, a blank
+	line will be inserted between the messages. Use the `--separator`
+	option to insert other delimiters.
 
 copy::
 	Copy the notes for the first object onto the second object (defaults to
@@ -85,8 +87,12 @@ corresponding <to-object>.  (The optional `<rest>` is ignored so that
 the command can read the input given to the `post-rewrite` hook.)
 
 append::
-	Append to the notes of an existing object (defaults to HEAD).
-	Creates a new notes object if needed.
+	Append new message(s) given by `-m` or `-F` options to an
+	existing note, or add them as a new note if one does not
+	exist, for the object (defaults to HEAD).  When appending to
+	an existing note, a blank line is added before each new
+	message as an inter-paragraph separator.  The separator can
+	be customized with the `--separator` option.
 
 edit::
 	Edit the notes for a given object (defaults to HEAD).
@@ -159,6 +165,11 @@ OPTIONS
 	Allow an empty note object to be stored. The default behavior is
 	to automatically remove empty notes.
 
+--separator <paragraph-break>::
+	Specify a string used as a custom inter-paragraph separator
+	(a newline is added at the end as needed).  Defaults to a
+	blank line.
+
 --ref <ref>::
 	Manipulate the notes tree in <ref>.  This overrides
 	`GIT_NOTES_REF` and the "core.notesRef" configuration.  The ref
diff --git a/builtin/notes.c b/builtin/notes.c
index 9d8ca795..3215bce1 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -8,6 +8,7 @@
  */
 
 #include "cache.h"
+#include "alloc.h"
 #include "config.h"
 #include "builtin.h"
 #include "gettext.h"
@@ -27,11 +28,12 @@
 #include "worktree.h"
 #include "write-or-die.h"
 
+static char *separator = "\n";
 static const char * const git_notes_usage[] = {
 	N_("git notes [--ref <notes-ref>] [list [<object>]]"),
-	N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
+	N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
 	N_("git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>"),
-	N_("git notes [--ref <notes-ref>] append [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
+	N_("git notes [--ref <notes-ref>] append [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
 	N_("git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]"),
 	N_("git notes [--ref <notes-ref>] show [<object>]"),
 	N_("git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>"),
@@ -99,11 +101,19 @@ static const char * const git_notes_get_ref_usage[] = {
 static const char note_template[] =
 	N_("Write/edit the notes for the following object:");
 
+struct note_msg {
+	int stripspace;
+	struct strbuf buf;
+};
+
 struct note_data {
 	int given;
 	int use_editor;
 	char *edit_path;
 	struct strbuf buf;
+	struct note_msg **messages;
+	size_t msg_nr;
+	size_t msg_alloc;
 };
 
 static void free_note_data(struct note_data *d)
@@ -113,6 +123,12 @@ static void free_note_data(struct note_data *d)
 		free(d->edit_path);
 	}
 	strbuf_release(&d->buf);
+
+	while (d->msg_nr--) {
+		strbuf_release(&d->messages[d->msg_nr]->buf);
+		free(d->messages[d->msg_nr]);
+	}
+	free(d->messages);
 }
 
 static int list_each_note(const struct object_id *object_oid,
@@ -213,65 +229,96 @@ static void write_note_data(struct note_data *d, struct object_id *oid)
 	}
 }
 
+static void insert_separator(struct strbuf *message, size_t pos)
+{
+	if (separator[strlen(separator) - 1] == '\n')
+		strbuf_addstr(message, separator);
+	else
+		strbuf_insertf(message, pos, "%s%s", separator, "\n");
+}
+
+static void concat_messages(struct note_data *d)
+{
+	struct strbuf msg = STRBUF_INIT;
+
+	size_t i;
+	for (i = 0; i < d->msg_nr ; i++) {
+		if (d->buf.len)
+			insert_separator(&d->buf, d->buf.len);
+		strbuf_add(&msg, d->messages[i]->buf.buf, d->messages[i]->buf.len);
+		strbuf_addbuf(&d->buf, &msg);
+		if (d->messages[i]->stripspace)
+			strbuf_stripspace(&d->buf, 0);
+		strbuf_reset(&msg);
+	}
+	strbuf_release(&msg);
+}
+
 static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
 {
 	struct note_data *d = opt->value;
+	struct note_msg *msg = xmalloc(sizeof(*msg));
 
 	BUG_ON_OPT_NEG(unset);
 
-	if (d->buf.len)
-		strbuf_addch(&d->buf, '\n');
-	strbuf_addstr(&d->buf, arg);
-	strbuf_stripspace(&d->buf, 0);
-
-	d->given = 1;
+	strbuf_init(&msg->buf, strlen(arg));
+	strbuf_addstr(&msg->buf, arg);
+	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
+	d->messages[d->msg_nr - 1] = msg;
+	msg->stripspace = 1;
 	return 0;
 }
 
 static int parse_file_arg(const struct option *opt, const char *arg, int unset)
 {
 	struct note_data *d = opt->value;
+	struct note_msg *msg = xmalloc(sizeof(*msg));
 
 	BUG_ON_OPT_NEG(unset);
 
-	if (d->buf.len)
-		strbuf_addch(&d->buf, '\n');
+	strbuf_init(&msg->buf , 0);
 	if (!strcmp(arg, "-")) {
-		if (strbuf_read(&d->buf, 0, 1024) < 0)
+		if (strbuf_read(&msg->buf, 0, 1024) < 0)
 			die_errno(_("cannot read '%s'"), arg);
-	} else if (strbuf_read_file(&d->buf, arg, 1024) < 0)
+	} else if (strbuf_read_file(&msg->buf, arg, 1024) < 0)
 		die_errno(_("could not open or read '%s'"), arg);
-	strbuf_stripspace(&d->buf, 0);
 
-	d->given = 1;
+	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
+	d->messages[d->msg_nr - 1] = msg;
+	msg->stripspace = 1;
 	return 0;
 }
 
 static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)
 {
 	struct note_data *d = opt->value;
-	char *buf;
+	struct note_msg *msg = xmalloc(sizeof(*msg));
+	char *value;
 	struct object_id object;
 	enum object_type type;
 	unsigned long len;
 
 	BUG_ON_OPT_NEG(unset);
 
-	if (d->buf.len)
-		strbuf_addch(&d->buf, '\n');
-
+	strbuf_init(&msg->buf, 0);
 	if (repo_get_oid(the_repository, arg, &object))
 		die(_("failed to resolve '%s' as a valid ref."), arg);
-	if (!(buf = repo_read_object_file(the_repository, &object, &type, &len)))
+	if (!(value = repo_read_object_file(the_repository, &object, &type, &len)))
 		die(_("failed to read object '%s'."), arg);
 	if (type != OBJ_BLOB) {
-		free(buf);
+		strbuf_release(&msg->buf);
+		free(value);
+		free(msg);
 		die(_("cannot read note data from non-blob object '%s'."), arg);
 	}
-	strbuf_add(&d->buf, buf, len);
-	free(buf);
 
-	d->given = 1;
+	strbuf_add(&msg->buf, value, len);
+	free(value);
+
+	msg->buf.len = len;
+	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
+	d->messages[d->msg_nr - 1] = msg;
+	msg->stripspace = 0;
 	return 0;
 }
 
@@ -406,6 +453,7 @@ static int add(int argc, const char **argv, const char *prefix)
 	struct object_id object, new_note;
 	const struct object_id *note;
 	struct note_data d = { .buf = STRBUF_INIT };
+
 	struct option options[] = {
 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
 			N_("note contents as a string"), PARSE_OPT_NONEG,
@@ -422,6 +470,8 @@ static int add(int argc, const char **argv, const char *prefix)
 		OPT_BOOL(0, "allow-empty", &allow_empty,
 			N_("allow storing empty note")),
 		OPT__FORCE(&force, N_("replace existing notes"), PARSE_OPT_NOCOMPLETE),
+		OPT_STRING(0, "separator", &separator, N_("separator"),
+			N_("insert <paragraph-break> between paragraphs")),
 		OPT_END()
 	};
 
@@ -433,6 +483,10 @@ static int add(int argc, const char **argv, const char *prefix)
 		usage_with_options(git_notes_add_usage, options);
 	}
 
+	if (d.msg_nr)
+		concat_messages(&d);
+	d.given = !!d.buf.len;
+
 	object_ref = argc > 1 ? argv[1] : "HEAD";
 
 	if (repo_get_oid(the_repository, object_ref, &object))
@@ -587,6 +641,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 			parse_reuse_arg),
 		OPT_BOOL(0, "allow-empty", &allow_empty,
 			N_("allow storing empty note")),
+		OPT_STRING(0, "separator", &separator, N_("separator"),
+			N_("insert <paragraph-break> between paragraphs")),
 		OPT_END()
 	};
 	int edit = !strcmp(argv[0], "edit");
@@ -600,6 +656,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		usage_with_options(usage, options);
 	}
 
+	if (d.msg_nr)
+		concat_messages(&d);
+	d.given = !!d.buf.len;
+
 	if (d.given && edit)
 		fprintf(stderr, _("The -m/-F/-c/-C options have been deprecated "
 			"for the 'edit' subcommand.\n"
@@ -623,7 +683,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 						       &type, &size);
 
 		if (d.buf.len && prev_buf && size)
-			strbuf_insertstr(&d.buf, 0, "\n");
+			insert_separator(&d.buf, 0);
 		if (prev_buf && size)
 			strbuf_insert(&d.buf, 0, prev_buf, size);
 		free(prev_buf);
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 3288aaec..dbadcf13 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -362,6 +362,7 @@ test_expect_success 'do not create empty note with -m ""' '
 '
 
 test_expect_success 'create note with combination of -m and -F' '
+	test_when_finished git notes remove HEAD &&
 	cat >expect-combine_m_and_F <<-EOF &&
 		foo
 
@@ -380,6 +381,25 @@ test_expect_success 'create note with combination of -m and -F' '
 	test_cmp expect-combine_m_and_F actual
 '
 
+test_expect_success 'create note with combination of -m and -F and --separator' '
+	cat >expect-combine_m_and_F <<-\EOF &&
+	foo
+	-------
+	xyzzy
+	-------
+	bar
+	-------
+	zyxxy
+	-------
+	baz
+	EOF
+	echo "xyzzy" >note_a &&
+	echo "zyxxy" >note_b &&
+	git notes add -m "foo" -F note_a -m "bar" -F note_b -m "baz" --separator "-------" &&
+	git notes show >actual &&
+	test_cmp expect-combine_m_and_F actual
+'
+
 test_expect_success 'remove note with "git notes remove"' '
 	git notes remove HEAD^ &&
 	git notes remove &&
@@ -521,6 +541,85 @@ test_expect_success 'listing non-existing notes fails' '
 	test_must_be_empty actual
 '
 
+test_expect_success 'append: specify an empty separator' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect <<-\EOF &&
+	notes-1
+
+	notes-2
+	EOF
+
+	git notes add -m "notes-1" &&
+	git notes append --separator="" -m "notes-2" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append: specify separator with line break' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect <<-\EOF &&
+	notes-1
+	-------
+	notes-2
+	EOF
+
+	git notes add -m "notes-1" &&
+	git notes append --separator="-------$LF" -m "notes-2" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append: specify separator without line break' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect <<-\EOF &&
+	notes-1
+	-------
+	notes-2
+	EOF
+
+	git notes add -m "notes-1" &&
+	git notes append --separator="-------" -m "notes-2" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append: specify separator with multiple messages' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect <<-\EOF &&
+	notes-1
+	-------
+	notes-2
+	-------
+	notes-3
+	EOF
+
+	git notes add -m "notes-1" &&
+	git notes append --separator="-------" -m "notes-2" -m "notes-3" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append note with combination of -m and -F and --separator' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect-combine_m_and_F <<-\EOF &&
+	m-notes-1
+	-------
+	f-notes-1
+	-------
+	m-notes-2
+	-------
+	f-notes-2
+	-------
+	m-notes-3
+	EOF
+
+	echo "f-notes-1" >note_a &&
+	echo "f-notes-2" >note_b &&
+	git notes append -m "m-notes-1" -F note_a -m "m-notes-2" -F note_b -m "m-notes-3" --separator "-------" &&
+	git notes show >actual &&
+	test_cmp expect-combine_m_and_F actual
+'
+
 test_expect_success 'append to existing note with "git notes append"' '
 	cat >expect <<-EOF &&
 		Initial set of notes
@@ -818,6 +917,33 @@ test_expect_success 'create note from blob with "git notes add -C" reuses blob i
 	test_cmp blob actual
 '
 
+test_expect_success 'create note from blob with "-C", also specify "-m", "-F" and "--separator"' '
+	# 8th will be reuseed in following tests, so rollback when the test is done
+	test_when_finished "git notes remove && git notes add -C $(cat blob)" &&
+	commit=$(git rev-parse HEAD) &&
+	cat >expect <<-EOF &&
+		commit $commit
+		Author: A U Thor <author@example.com>
+		Date:   Thu Apr 7 15:20:13 2005 -0700
+
+		${indent}8th
+
+		Notes:
+		${indent}This is a blob object
+		${indent}-------
+		${indent}This is created by -m
+		${indent}-------
+		${indent}This is created by -F
+	EOF
+
+	git notes remove &&
+	echo "This is a blob object" | git hash-object -w --stdin >blob &&
+	echo "This is created by -F" >note_a &&
+	git notes add -C $(cat blob) -m "This is created by -m" -F note_a --separator="-------" &&
+	git log -1 >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'create note from other note with "git notes add -c"' '
 	test_commit 9th &&
 	commit=$(git rev-parse HEAD) &&
-- 
2.40.0.358.g2947072e.dirty
Junio C Hamano· Apr 28, 2023, 20:44 UTC · re: Teng Long · lore

Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option

Teng Long <dyroneteng@gmail.com> writes:
> +static char *separator = "\n";

The only two ways this pointer gains a non-NULL value are with this initialization and parsing the command line "--separator=<value>" option with OPT_STRING(). Neither of them allocate new storage but points an existing string that we do not "own" (and cannot free) with the pointer. So it probably is safer to make it a pointer to a const string, i.e.

	static const char *separator = "\n";
Show 18 quoted lines
> @@ -213,65 +229,96 @@ static void write_note_data(struct note_data *d, struct object_id *oid)
>  	}
>  }
>  
> +static void insert_separator(struct strbuf *message, size_t pos)
> +{
> +	if (separator[strlen(separator) - 1] == '\n')
> +		strbuf_addstr(message, separator);
> +	else
> +		strbuf_insertf(message, pos, "%s%s", separator, "\n");
> +}
> +
> +static void concat_messages(struct note_data *d)
> +{
> +	struct strbuf msg = STRBUF_INIT;
> +
> +	size_t i;
> +	for (i = 0; i < d->msg_nr ; i++) {

Wrong placement of the blank line that separates the declaration and the first statement.

Teng Long· May 6, 2023, 09:12 UTC · re: Junio C Hamano · lore

Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option

Junio C Hamano <gitster@pobox.com> writes:
Show 32 quoted lines
>> +static char *separator = "\n";
>
>The only two ways this pointer gains a non-NULL value are with this
>initialization and parsing the command line "--separator=<value>"
>option with OPT_STRING().  Neither of them allocate new storage but
>points an existing string that we do not "own" (and cannot free)
>with the pointer.  So it probably is safer to make it a pointer to a
>const string, i.e.
>
>	static const char *separator = "\n";
>
>> @@ -213,65 +229,96 @@ static void write_note_data(struct note_data *d, struct object_id *oid)
>>  	}
>>  }
>>  
>> +static void insert_separator(struct strbuf *message, size_t pos)
>> +{
>> +	if (separator[strlen(separator) - 1] == '\n')
>> +		strbuf_addstr(message, separator);
>> +	else
>> +		strbuf_insertf(message, pos, "%s%s", separator, "\n");
>> +}
>> +
>> +static void concat_messages(struct note_data *d)
>> +{
>> +	struct strbuf msg = STRBUF_INIT;
>> +
>> +	size_t i;
>> +	for (i = 0; i < d->msg_nr ; i++) {
>
>Wrong placement of the blank line that separates the declaration and
>the first statement.

Thanks for your advice and detailed explanation. I have noticed in "what's cooking in git" that this patchset is being planed to merge into 'next', so I display the diff here for the convenience of applying, or please let me know if a new patch is required.

Show changes to builtin/notes.c +2 −2
diff --git a/builtin/notes.c b/builtin/notes.c
index 8905298b..6eede305 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -28,7 +28,7 @@
 #include "worktree.h"
 #include "write-or-die.h"
 
-static const char *separator = "\n";
+static char *separator = "\n";
 static const char * const git_notes_usage[] = {
        N_("git notes [--ref <notes-ref>] [list [<object>]]"),
        N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
@@ -248,8 +248,8 @@ static void append_separator(struct strbuf *message)
 static void concat_messages(struct note_data *d)
 {
        struct strbuf msg = STRBUF_INIT;
-       size_t i;
 
+       size_t i;
        for (i = 0; i < d->msg_nr ; i++) {
                if (d->buf.len)
                        append_separator(&d->buf);
Teng Long· May 6, 2023, 09:22 UTC · re: Teng Long · lore

Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option

Show 23 quoted lines
>diff --git a/builtin/notes.c b/builtin/notes.c
>index 8905298b..6eede305 100644
>--- a/builtin/notes.c
>+++ b/builtin/notes.c
>@@ -28,7 +28,7 @@
> #include "worktree.h"
> #include "write-or-die.h"
> 
>-static const char *separator = "\n";
>+static char *separator = "\n";
> static const char * const git_notes_usage[] = {
>        N_("git notes [--ref <notes-ref>] [list [<object>]]"),
>        N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
>@@ -248,8 +248,8 @@ static void append_separator(struct strbuf *message)
> static void concat_messages(struct note_data *d)
> {
>        struct strbuf msg = STRBUF_INIT;
>-       size_t i;
> 
>+       size_t i;
>        for (i = 0; i < d->msg_nr ; i++) {
>                if (d->buf.len)
>                        append_separator(&d->buf);
Sorry, the direction is reversed wrongly.
Show changes to builtin/notes.c +2 −2
diff --git a/builtin/notes.c b/builtin/notes.c
index 6eede305..8905298b 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -28,7 +28,7 @@
 #include "worktree.h"
 #include "write-or-die.h"
 
-static char *separator = "\n";
+static const char *separator = "\n";
 static const char * const git_notes_usage[] = {
        N_("git notes [--ref <notes-ref>] [list [<object>]]"),
        N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
@@ -248,8 +248,8 @@ static void append_separator(struct strbuf *message)
 static void concat_messages(struct note_data *d)
 {
        struct strbuf msg = STRBUF_INIT;
-
        size_t i;
+
        for (i = 0; i < d->msg_nr ; i++) {
                if (d->buf.len)
                        append_separator(&d->buf);
Kristoffer Haugsbakk· May 10, 2023, 19:19 UTC · re: Teng Long · lore

Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option

I realize that this series is going to be merged to `master`.[1] I was trying out this new change since I might have some use for it when the next version is released.

On Fri, Apr 28, 2023, at 11:23, Teng Long wrote:
> From: Teng Long <dyroneteng@gmail.com>
>
> When adding new notes or appending to an existing notes, we will
> insert a blank line between the paragraphs, like:

The test case[2] `append: specify an empty separator` demonstrates that `--separator=""` is the same as the default behavior, namely to add a blank line. It has (according to the commit messages) been like this since v5 of this patch.[3]

v4 of this patch special-cased `--separator=""` to mean “no separator”. And this was the same behavior as the original `--no-blankline`,[4] which eventually mutated into `--separator`.

Why was this changed to act the same as the default behavior (add a blank line)? I can’t seem to find a note for it on the cover letter of v5 or in the relevant replies.

It seemed that v4 of this patch (with special-cased empty argument) was perhaps based on Eric Sunshine’s suggestion:[5]

Show 5 quoted lines
> Taking a step back, perhaps think of this in terms of "separator". The
> default behavior is to insert "\n" as a separator between notes. If
> you add a --separator option, then users could supply their own
> separator, such as "----\n" or, in your case, "" to suppress the blank
> line.
(And then reiterated in a v4 email [6])

Was the idea perhaps to eventually (separately) add a separate option which functions like `--each-message-is-line-not-paragraph`, like what was mentioned in [7]?

Maybe I’ve missed something. (I probably have.)

[1] https://lore.kernel.org/git/xmqqpm785erg.fsf@gitster.g/T/#md9b20801457c3eb24dc0e793f5dfbeae2f2707fd [2] On `next`, 74a8c73209 (Sync with 'master', 2023-05-09) [3] https://lore.kernel.org/git/a74c96d6dd23f2f1df6d3492093f3fd27451e24c.1676551077.git.dyroneteng@gmail.com/

   Commit message on v4:
   >  * --separator='': we specify an empty separator which means will
   >  append the message directly without inserting any separator at
   >  first.
   Commit message on v5:
   > * --separator='': we specify an empty separator which has the same
   > behavour with --separator='\n' and or not specified the option.

[4] https://lore.kernel.org/git/20221013055654.39628-1-tenglong.tl@alibaba-inc.com/ [5] https://lore.kernel.org/git/CAPig+cRcezSp4Rqt1Y9bD-FT6+7b0g9qHfbGRx65AOnw2FQXKg@mail.gmail.com/ [6] https://lore.kernel.org/git/CAPig+cSF7Fp3oM4TRU1QbiSzTeKNd1qGtqU7goPc1r-p4g8mkg@mail.gmail.com/ [7] https://lore.kernel.org/git/xmqqh6yh3nk4.fsf@gitster.g/

-- 
Kristoffer Haugsbakk
Teng Long· May 12, 2023, 04:07 UTC · re: Kristoffer Haugsbakk · lore

Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option

"Kristoffer Haugsbakk" <code@khaugsbakk.name> writes:
>I realize that this series is going to be merged to `master`.[1] I was
>trying out this new change since I might have some use for it when the
>next version is released.
I'm glad there are use cases for the topic.
Show 23 quoted lines
>The test case[2] `append: specify an empty separator` demonstrates that
>`--separator=""` is the same as the default behavior, namely to add a
>blank line. It has (according to the commit messages) been like this
>since v5 of this patch.[3]
>
>v4 of this patch special-cased `--separator=""` to mean “no
>separator”. And this was the same behavior as the original
>`--no-blankline`,[4] which eventually mutated into `--separator`.
>
>Why was this changed to act the same as the default behavior (add a
>blank line)? I can’t seem to find a note for it on the cover letter of
>v5 or in the relevant replies.
>
>It seemed that v4 of this patch (with special-cased empty argument) was
>perhaps based on Eric Sunshine’s suggestion:[5]
>
>> Taking a step back, perhaps think of this in terms of "separator". The
>> default behavior is to insert "\n" as a separator between notes. If
>> you add a --separator option, then users could supply their own
>> separator, such as "----\n" or, in your case, "" to suppress the blank
>> line.
>
>(And then reiterated in a v4 email [6])

There is indeed this change in the process, I did not express it clearly in the patch, sorry, allow me to explain it:

Initially, I wanted to support separator insertion with --[no-]blankline. It was intuitive and clear in logic. If it is specified as --blankline, newline would be append, if it is --no-blankline, newline would not be append, and --blankline would be the default behavior.

The problem with this is that "blankline" might not be a good name because it doesn't seem to express the meaning of the separator or paragraph-break, and since we're going to support separators, it might be more thorough to support a custom separator, which is a better solution I think.

Returning to the issue of -separator = "", my thought is that I want --separator="" to behave the same way as --separator="<any-string-without-a-trailing-\n'>" when deals a string which does not contains a trailing newline. This will eliminate one more implicit logic and make behavior more consistent.

>Was the idea perhaps to eventually (separately) add a separate option
>which functions like `--each-message-is-line-not-paragraph`, like what
>was mentioned in [7]?

I think --no-separator maybe a better name, means that not any separator will be append between two paragraphs even a newline.

Thanks.
Kristoffer Haugsbakk· May 12, 2023, 07:29 UTC · re: Teng Long · lore

Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option

Hi Teng Long and thanks for the explanation.
On Fri, May 12, 2023, at 06:07, Teng Long wrote:
> I think --no-separator maybe a better name, means that not any separator will be
> append between two paragraphs even a newline.

That makes sense. Using `--no-separator` seems very consistent with other commands.

-- 
Kristoffer Haugsbakk
Junio C Hamano· May 16, 2023, 17:00 UTC · re: Teng Long · lore

Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option

Teng Long <dyroneteng@gmail.com> writes:
Show 5 quoted lines
> Returning to the issue of -separator = "", my thought is that I want
> --separator="" to behave the same way as
> --separator="<any-string-without-a-trailing-\n'>" when deals a string which does
> not contains a trailing newline. This will eliminate one more implicit logic and
> make behavior more consistent.

An obvious alternative would be to use the string given to the "--separator" option literally, I guess, and you can add your own newline if you want to. But most of the time you would not want to have an incomplete line, so appending the newline at the end by default does make sense.

Special casing an empty value to "--separator" as "nothing" does make sort-of sense. Using a blank line as the inter-paragraph separator is the default, and there is not much use in the "--separator=''" that becomes "--separator=$'\012'" automatically.

> I think --no-separator maybe a better name, means that not any separator will be
> append between two paragraphs even a newline.

It would work as well. Is it something we can safely add before merging the topic to upcoming -rc1?

Thanks.
Teng Long· May 17, 2023, 03:58 UTC · re: Junio C Hamano · lore

Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option

Junio C Hamano <gitster@pobox.com> writes:
Show 5 quoted lines
>> I think --no-separator maybe a better name, means that not any separator will be
>> append between two paragraphs even a newline.
>
>It would work as well.  Is it something we can safely add before
>merging the topic to upcoming -rc1?

I noticed rc-1 will be released at this Friday, I will post a new patch about "--no-separator" today(UTC/GMT+08:00).

Thanks.
Junio C Hamano· May 17, 2023, 15:32 UTC · re: Teng Long · lore

Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option

Teng Long <dyroneteng@gmail.com> writes:
Show 10 quoted lines
> Junio C Hamano <gitster@pobox.com> writes:
>
>>> I think --no-separator maybe a better name, means that not any separator will be
>>> append between two paragraphs even a newline.
>>
>>It would work as well.  Is it something we can safely add before
>>merging the topic to upcoming -rc1?
>
> I noticed rc-1 will be released at this Friday, I will post a new patch
> about "--no-separator" today(UTC/GMT+08:00).
Thanks.
Junio C Hamano· Jun 14, 2023, 01:02 UTC · re: Teng Long · lore

Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option

This step seems to break CI asan/ubsan job
https://github.com/git/git/actions/runs/5260417146/jobs/9507299904#step:4:1826
Perhaps something like this is in order?
 builtin/notes.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to diff +1 −1
diff --git c/builtin/notes.c w/builtin/notes.c
index 6eede30597..785b1922ad 100644
--- c/builtin/notes.c
+++ w/builtin/notes.c
@@ -239,7 +239,7 @@ static void write_note_data(struct note_data *d, struct object_id *oid)
 
 static void append_separator(struct strbuf *message)
 {
-	if (separator[strlen(separator) - 1] == '\n')
+	if (*separator && separator[strlen(separator) - 1] == '\n')
 		strbuf_addstr(message, separator);
 	else
 		strbuf_addf(message, "%s%s", separator, "\n");
Junio C Hamano· Jun 14, 2023, 01:10 UTC · re: Junio C Hamano · lore

[PATCH] notes: do not access before the beginning of an array

The check to see if the last byte of the separator string is a LF makes sense *only* when the separator string is known to be non empty. This has been breaking the asan/ubsan job at CI.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 * This time with a proposed log message with sign-off.
 builtin/notes.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/notes.c +1 −1
diff --git a/builtin/notes.c b/builtin/notes.c
index 6eede30597..785b1922ad 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -239,7 +239,7 @@ static void write_note_data(struct note_data *d, struct object_id *oid)
 
 static void append_separator(struct strbuf *message)
 {
-	if (separator[strlen(separator) - 1] == '\n')
+	if (*separator && separator[strlen(separator) - 1] == '\n')
 		strbuf_addstr(message, separator);
 	else
 		strbuf_addf(message, "%s%s", separator, "\n");
-- 
2.41.0-28-gd7d8841f67
Eric Sunshine· Jun 14, 2023, 01:41 UTC · re: Junio C Hamano · lore

Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option

On Tue, Jun 13, 2023 at 9:02 PM Junio C Hamano <gitster@pobox.com> wrote:
Show 10 quoted lines
> This step seems to break CI asan/ubsan job
> https://github.com/git/git/actions/runs/5260417146/jobs/9507299904#step:4:1826
> Perhaps something like this is in order?
>
> diff --git c/builtin/notes.c w/builtin/notes.c
> @@ -239,7 +239,7 @@ static void write_note_data(struct note_data *d, struct object_id *oid)
>  static void append_separator(struct strbuf *message)
>  {
> -       if (separator[strlen(separator) - 1] == '\n')
> +       if (*separator && separator[strlen(separator) - 1] == '\n')

Is this the same issue Peff reported[1]? His proposed solution was a bit different.

[1]: https://lore.kernel.org/git/20230519005447.GA2955320@coredump.intra.peff.net/
Junio C Hamano· Jun 14, 2023, 02:07 UTC · re: Eric Sunshine · lore

Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option

Eric Sunshine <sunshine@sunshineco.com> writes:
> Is this the same issue Peff reported[1]? His proposed solution was a
> bit different.
>
> [1]: https://lore.kernel.org/git/20230519005447.GA2955320@coredump.intra.peff.net/
Ah, I should have checked before merging the topic in 'next'.
Jeff King· Jun 15, 2023, 07:13 UTC · re: Junio C Hamano · lore

Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option

On Tue, Jun 13, 2023 at 07:07:38PM -0700, Junio C Hamano wrote:
Show 8 quoted lines
> Eric Sunshine <sunshine@sunshineco.com> writes:
> 
> > Is this the same issue Peff reported[1]? His proposed solution was a
> > bit different.
> >
> > [1]: https://lore.kernel.org/git/20230519005447.GA2955320@coredump.intra.peff.net/
> 
> Ah, I should have checked before merging the topic in 'next'.
I only found it after it was in 'next'. :)

It originally did not cause problems, but the switch to clang started triggering it in CI (it was this case that motivated me to send those patches CI patches).

I was not following the topic closely, but I think there is a v10 of this series anyway, so you may want to eject it from 'next' in the meantime (I had assumed you were going to do so as part of the post-release rewind, but it looks like that happened already and it got re-merged).

-Peff
Junio C Hamano· Jun 15, 2023, 19:15 UTC · re: Jeff King · lore

Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option

Jeff King <peff@peff.net> writes:
Show 5 quoted lines
> I was not following the topic closely, but I think there is a v10 of
> this series anyway, so you may want to eject it from 'next' in the
> meantime (I had assumed you were going to do so as part of the
> post-release rewind, but it looks like that happened already and it got
> re-merged).
I can do the post-release rewind again ;-)

I somehow had an impression that the topic was more or less done, but if it deserves another chance, let's give it one.

Thanks.
Teng Long· Jun 19, 2023, 06:08 UTC · re: Junio C Hamano · lore

Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option

Junio C Hamano <gitster@pobox.com> writes:
Show 14 quoted lines
> Jeff King <peff@peff.net> writes:
> 
> > I was not following the topic closely, but I think there is a v10 of
> > this series anyway, so you may want to eject it from 'next' in the
> > meantime (I had assumed you were going to do so as part of the
> > post-release rewind, but it looks like that happened already and it got
> > re-merged).
> 
> I can do the post-release rewind again ;-)
> 
> I somehow had an impression that the topic was more or less done,
> but if it deserves another chance, let's give it one.
> 
> Thanks.

I have been on vacation for the last two weeks, sorry for not replying in time.

I think that is :
  time-1: v9 was in next, actually v9 [4/6] will break the CI, but
          [5/6] fix it so the CI passed on the whole patchset.
  time-2: then v10 was tought a new `--no-separator ` mainly
          On the release point, v10 implicitly fixed the problem
          in v9 [4/6]
  time-3: 2.41 release work begin
  time-4: Peff found that the v9 [4/6] merged in next would break
          CI and a UBSan problem.
  time-5: I wanted to make a full reroll to fix the UBSan problem,
          by the way, separate the "--no-separator" to a single
          commit, because I thought it cannot catch the last bus
          on 2.41 maybe.

I think we are now in a new cycle, maybe we could rewind and use v11 directlly? Sorry that it may have brought on extra work.

Thanks.
Junio C Hamano· Jun 20, 2023, 20:36 UTC · re: Teng Long · lore

Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option

Teng Long <dyroneteng@gmail.com> writes:
> I think we are now in a new cycle, maybe we could rewind and use
> v11 directlly? Sorry that it may have brought on extra work.

I think the tl/notes-separator topic is no longer in 'next' so we can do whatever you like to it ;-). It would be easy to just replace the whole thing with newly reviewed version.

Thanks.
Teng Long· Jun 21, 2023, 02:50 UTC · re: Junio C Hamano · lore

Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option

Show 8 quoted lines
> Teng Long <dyroneteng@gmail.com> writes:
> 
> > I think we are now in a new cycle, maybe we could rewind and use
> > v11 directlly? Sorry that it may have brought on extra work.
> 
> I think the tl/notes-separator topic is no longer in 'next' so we
> can do whatever you like to it ;-).  It would be easy to just
> replace the whole thing with newly reviewed version.

Yes, v11 fixed the UBSan problem which found by Peff and also the broken CI under run a bisection (both are found in v9 which already be rewinded in 'next'). If there are no new comments on v11, I think maybe it's what we want at present.

Thanks.
Teng Long· Apr 28, 2023, 09:23 UTC · re: Teng Long · lore

[PATCH v9 5/6] notes.c: append separator instead of insert by pos

From: Teng Long <dyroneteng@gmail.com>

Rename "insert_separator" to "append_separator" and also remove the "postion" argument, this serves two purpose:

The first is that when specifying more than one "-m" ( like "-F", etc) to "git notes add" or "git notes append", the order of them matters, which means we need to append the each separator and message in turn, so we don't have to make the caller specify the position, the "append" operation is enough and clear.

The second is that when we execute the "git notes append" subcommand, we need to combine the "prev_note" and "current_note" to get the final result. Before, we inserted a newline character at the beginning of "current_note". Now, we will append a newline to the end of "prev_note" instead, this will give the consisitent results.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/notes.c | 19 +++++++++++--------
 1 file changed, 11 insertions(+), 8 deletions(-)
Show changes to builtin/notes.c +11 −8
diff --git a/builtin/notes.c b/builtin/notes.c
index 3215bce1..e32f2453 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -229,12 +229,12 @@ static void write_note_data(struct note_data *d, struct object_id *oid)
 	}
 }
 
-static void insert_separator(struct strbuf *message, size_t pos)
+static void append_separator(struct strbuf *message)
 {
 	if (separator[strlen(separator) - 1] == '\n')
 		strbuf_addstr(message, separator);
 	else
-		strbuf_insertf(message, pos, "%s%s", separator, "\n");
+		strbuf_addf(message, "%s%s", separator, "\n");
 }
 
 static void concat_messages(struct note_data *d)
@@ -244,7 +244,7 @@ static void concat_messages(struct note_data *d)
 	size_t i;
 	for (i = 0; i < d->msg_nr ; i++) {
 		if (d->buf.len)
-			insert_separator(&d->buf, d->buf.len);
+			append_separator(&d->buf);
 		strbuf_add(&msg, d->messages[i]->buf.buf, d->messages[i]->buf.len);
 		strbuf_addbuf(&d->buf, &msg);
 		if (d->messages[i]->stripspace)
@@ -679,14 +679,17 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		/* Append buf to previous note contents */
 		unsigned long size;
 		enum object_type type;
-		char *prev_buf = repo_read_object_file(the_repository, note,
-						       &type, &size);
+		struct strbuf buf = STRBUF_INIT;
+		char *prev_buf = repo_read_object_file(the_repository, note, &type, &size);
 
-		if (d.buf.len && prev_buf && size)
-			insert_separator(&d.buf, 0);
 		if (prev_buf && size)
-			strbuf_insert(&d.buf, 0, prev_buf, size);
+			strbuf_add(&buf, prev_buf, size);
+		if (d.buf.len && prev_buf && size)
+			append_separator(&buf);
+		strbuf_insert(&d.buf, 0, buf.buf, buf.len);
+
 		free(prev_buf);
+		strbuf_release(&buf);
 	}
 
 	if (d.buf.len || allow_empty) {
-- 
2.40.0.358.g2947072e.dirty
Teng Long· Apr 28, 2023, 09:23 UTC · re: Teng Long · lore

[PATCH v9 6/6] notes.c: introduce "--[no-]stripspace" option

From: Teng Long <dyroneteng@gmail.com>

This commit introduces a new option "--[no-]stripspace" to git notes append, git notes edit, and git notes add. This option allows users to control whether the note message need to stripped out.

For the consideration of backward compatibility, let's look at the behavior about "stripspace" in "git notes" command:

1. "Edit Message" case: using the default editor to edit the note
message.
    In "edit" case, the edited message will always be stripped out, the
    implementation which can be found in the "prepare_note_data()". In
    addition, the "-c" option supports to reuse an existing blob as a
    note message, then open the editor to make a further edition on it,
    the edited message will be stripped.
    This commit doesn't change the default behavior of "edit" case by
    using an enum "notes_stripspace", only when "--no-stripspace" option
    is specified, the note message will not be stripped out. If you do
    not specify the option or you specify "--stripspace", clearly, the
    note message will be stripped out.
2. "Assign Message" case: using the "-m"/"-F"/"-C" option to specify the
note message.
    In "assign" case, when specify message by "-m" or "-F", the message
    will be stripped out by default, but when specify message by "-C",
    the message will be copied verbatim, in other word, the message will
    not be stripped out. One more thing need to note is "the order of
    the options matter", that is, if you specify "-C" before "-m" or
    "-F", the reused message by "-C" will be stripped out together,
    because everytime concat "-m" or "-F" message, the concated message
    will be stripped together. Oppositely, if you specify "-m" or "-F"
    before "-C", the reused message by "-C" will not be stripped out.
    This commit doesn't change the default behavior of "assign" case by
    extending the "stripspace" field in "struct note_msg", so we can
    distinguish the different behavior of "-m"/"-F" and "-C" options
    when we need to parse and concat the message.
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-notes.txt |  25 ++-
 builtin/notes.c             |  30 +++-
 t/t3321-notes-stripspace.sh | 296 +++++++++++++++++++++++++++++++++++-
 3 files changed, 332 insertions(+), 19 deletions(-)
Show changes to 3 files +332 −19

Documentation/git-notes.txt, builtin/notes.c, t/t3321-notes-stripspace.sh

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 59980b21..5f3a9479 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -9,10 +9,10 @@ SYNOPSIS
 --------
 [verse]
 'git notes' [list [<object>]]
-'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
+'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
 'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )
-'git notes' append [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
-'git notes' edit [--allow-empty] [<object>]
+'git notes' append [--allow-empty] [--separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
+'git notes' edit [--allow-empty] [<object>] [--[no-]stripspace]
 'git notes' show [<object>]
 'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>
 'git notes' merge --commit [-v | -q]
@@ -141,20 +141,26 @@ OPTIONS
 	If multiple `-m` options are given, their values
 	are concatenated as separate paragraphs.
 	Lines starting with `#` and empty lines other than a
-	single line between paragraphs will be stripped out.
+	single line between paragraphs will be stripped out,
+	if you wish to keep them verbatim, use `--no-stripspace`.
 
 -F <file>::
 --file=<file>::
 	Take the note message from the given file.  Use '-' to
 	read the note message from the standard input.
 	Lines starting with `#` and empty lines other than a
-	single line between paragraphs will be stripped out.
+	single line between paragraphs will be stripped out,
+	if you wish to keep them verbatim, use with
+	`--no-stripspace` option.
 
 -C <object>::
 --reuse-message=<object>::
 	Take the given blob object (for example, another note) as the
 	note message. (Use `git notes copy <object>` instead to
-	copy notes between objects.)
+	copy notes between objects.).  By default, message will be
+	copied verbatim, but if you wish to strip out the lines
+	starting with `#` and empty lines other than a single line
+	between paragraphs, use with`--stripspace` option.
 
 -c <object>::
 --reedit-message=<object>::
@@ -170,6 +176,13 @@ OPTIONS
 	(a newline is added at the end as needed).  Defaults to a
 	blank line.
 
+--[no-]stripspace::
+	Strip leading and trailing whitespace from the note message.
+	Also strip out empty lines other than a single line between
+	paragraphs. For lines starting with `#` will be stripped out
+	in non-editor cases like "-m", "-F" and "-C", but not in
+	editor case like "git notes edit", "-c", etc.
+
 --ref <ref>::
 	Manipulate the notes tree in <ref>.  This overrides
 	`GIT_NOTES_REF` and the "core.notesRef" configuration.  The ref
diff --git a/builtin/notes.c b/builtin/notes.c
index e32f2453..6eede305 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -101,14 +101,21 @@ static const char * const git_notes_get_ref_usage[] = {
 static const char note_template[] =
 	N_("Write/edit the notes for the following object:");
 
+enum notes_stripspace {
+	UNSPECIFIED = -1,
+	NO_STRIPSPACE = 0,
+	STRIPSPACE = 1,
+};
+
 struct note_msg {
-	int stripspace;
+	enum notes_stripspace stripspace;
 	struct strbuf buf;
 };
 
 struct note_data {
 	int given;
 	int use_editor;
+	int stripspace;
 	char *edit_path;
 	struct strbuf buf;
 	struct note_msg **messages;
@@ -213,7 +220,8 @@ static void prepare_note_data(const struct object_id *object, struct note_data *
 		if (launch_editor(d->edit_path, &d->buf, NULL)) {
 			die(_("please supply the note contents using either -m or -F option"));
 		}
-		strbuf_stripspace(&d->buf, 1);
+		if (d->stripspace)
+			strbuf_stripspace(&d->buf, 1);
 	}
 }
 
@@ -247,7 +255,9 @@ static void concat_messages(struct note_data *d)
 			append_separator(&d->buf);
 		strbuf_add(&msg, d->messages[i]->buf.buf, d->messages[i]->buf.len);
 		strbuf_addbuf(&d->buf, &msg);
-		if (d->messages[i]->stripspace)
+		if ((d->stripspace == UNSPECIFIED &&
+		     d->messages[i]->stripspace == STRIPSPACE) ||
+		    d->stripspace == STRIPSPACE)
 			strbuf_stripspace(&d->buf, 0);
 		strbuf_reset(&msg);
 	}
@@ -265,7 +275,7 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
 	strbuf_addstr(&msg->buf, arg);
 	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
 	d->messages[d->msg_nr - 1] = msg;
-	msg->stripspace = 1;
+	msg->stripspace = STRIPSPACE;
 	return 0;
 }
 
@@ -285,7 +295,7 @@ static int parse_file_arg(const struct option *opt, const char *arg, int unset)
 
 	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
 	d->messages[d->msg_nr - 1] = msg;
-	msg->stripspace = 1;
+	msg->stripspace = STRIPSPACE;
 	return 0;
 }
 
@@ -318,7 +328,7 @@ static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)
 	msg->buf.len = len;
 	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
 	d->messages[d->msg_nr - 1] = msg;
-	msg->stripspace = 0;
+	msg->stripspace = NO_STRIPSPACE;
 	return 0;
 }
 
@@ -452,7 +462,7 @@ static int add(int argc, const char **argv, const char *prefix)
 	struct notes_tree *t;
 	struct object_id object, new_note;
 	const struct object_id *note;
-	struct note_data d = { .buf = STRBUF_INIT };
+	struct note_data d = { .buf = STRBUF_INIT, .stripspace = UNSPECIFIED };
 
 	struct option options[] = {
 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
@@ -472,6 +482,8 @@ static int add(int argc, const char **argv, const char *prefix)
 		OPT__FORCE(&force, N_("replace existing notes"), PARSE_OPT_NOCOMPLETE),
 		OPT_STRING(0, "separator", &separator, N_("separator"),
 			N_("insert <paragraph-break> between paragraphs")),
+		OPT_BOOL(0, "stripspace", &d.stripspace,
+			N_("remove unnecessary whitespace")),
 		OPT_END()
 	};
 
@@ -625,7 +637,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 	const struct object_id *note;
 	char *logmsg;
 	const char * const *usage;
-	struct note_data d = { .buf = STRBUF_INIT };
+	struct note_data d = { .buf = STRBUF_INIT, .stripspace = UNSPECIFIED };
 	struct option options[] = {
 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
 			N_("note contents as a string"), PARSE_OPT_NONEG,
@@ -643,6 +655,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 			N_("allow storing empty note")),
 		OPT_STRING(0, "separator", &separator, N_("separator"),
 			N_("insert <paragraph-break> between paragraphs")),
+		OPT_BOOL(0, "stripspace", &d.stripspace,
+			N_("remove unnecessary whitespace")),
 		OPT_END()
 	};
 	int edit = !strcmp(argv[0], "edit");
diff --git a/t/t3321-notes-stripspace.sh b/t/t3321-notes-stripspace.sh
index 89977873..028d825e 100755
--- a/t/t3321-notes-stripspace.sh
+++ b/t/t3321-notes-stripspace.sh
@@ -32,7 +32,7 @@ test_expect_success 'add note by editor' '
 	test_cmp expect actual
 '
 
-test_expect_success 'add note by specifying single "-m"' '
+test_expect_success 'add note by specifying single "-m", "--stripspace" is the default behavior' '
 	test_when_finished "git notes remove" &&
 	cat >expect <<-EOF &&
 	first-line
@@ -42,10 +42,26 @@ test_expect_success 'add note by specifying single "-m"' '
 
 	git notes add -m "${LF}first-line${MULTI_LF}second-line${LF}" &&
 	git notes show >actual &&
+	test_cmp expect actual &&
+	git notes remove &&
+	git notes add --stripspace -m "${LF}first-line${MULTI_LF}second-line${LF}" &&
+	git notes show >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success 'add note by specifying multiple "-m"' '
+test_expect_success 'add note by specifying single "-m" and "--no-stripspace" ' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	${LF}first-line${MULTI_LF}second-line
+	EOF
+
+	git notes add --no-stripspace \
+		      -m "${LF}first-line${MULTI_LF}second-line${LF}" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying multiple "-m", "--stripspace" is the default behavior' '
 	test_when_finished "git notes remove" &&
 	cat >expect <<-EOF &&
 	first-line
@@ -59,9 +75,156 @@ test_expect_success 'add note by specifying multiple "-m"' '
 		      -m "second-line" \
 		      -m "${LF}" &&
 	git notes show >actual &&
+	test_cmp expect actual &&
+	git notes remove &&
+	git notes add --stripspace -m "${LF}" \
+		      -m "first-line" \
+		      -m "${MULTI_LF}" \
+		      -m "second-line" \
+		      -m "${LF}" &&
+	git notes show >actual &&
 	test_cmp expect actual
 '
 
+test_expect_success 'add notes by specifying multiple "-m" and "--no-stripspace"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line${LF}
+	EOF
+
+	git notes add --no-stripspace \
+		      -m "${LF}" \
+		      -m "first-line" \
+		      -m "${MULTI_LF}" \
+		      -m "second-line" \
+		      -m "${LF}" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying single "-F", "--stripspace" is the default behavior' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	cat >note-file <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	git notes add -F note-file &&
+	git notes show >actual &&
+	test_cmp expect actual &&
+	git notes remove &&
+	git notes add --stripspace -F note-file &&
+	git notes show >actual
+'
+
+test_expect_success 'add note by specifying single "-F" and "--no-stripspace"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	cat >note-file <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	git notes add --no-stripspace -F note-file &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying multiple "-F", "--stripspace" is the default behavior' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	file-1-first-line
+
+	file-1-second-line
+
+	file-2-first-line
+
+	file-2-second-line
+	EOF
+
+	cat >note-file-1 <<-EOF &&
+	${LF}
+	file-1-first-line
+	${MULTI_LF}
+	file-1-second-line
+	${LF}
+	EOF
+
+	cat >note-file-2 <<-EOF &&
+	${LF}
+	file-2-first-line
+	${MULTI_LF}
+	file-2-second-line
+	${LF}
+	EOF
+
+	git notes add -F note-file-1 -F note-file-2 &&
+	git notes show >actual &&
+	test_cmp expect actual &&
+	git notes remove &&
+	git notes add --stripspace -F note-file-1 -F note-file-2 &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying multiple "-F" with "--no-stripspace"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	${LF}
+	file-1-first-line
+	${MULTI_LF}
+	file-1-second-line
+	${LF}
+
+	${LF}
+	file-2-first-line
+	${MULTI_LF}
+	file-2-second-line
+	${LF}
+	EOF
+
+	cat >note-file-1 <<-EOF &&
+	${LF}
+	file-1-first-line
+	${MULTI_LF}
+	file-1-second-line
+	${LF}
+	EOF
+
+	cat >note-file-2 <<-EOF &&
+	${LF}
+	file-2-first-line
+	${MULTI_LF}
+	file-2-second-line
+	${LF}
+	EOF
+
+	git notes add --no-stripspace -F note-file-1 -F note-file-2 &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
 
 test_expect_success 'append note by editor' '
 	test_when_finished "git notes remove" &&
@@ -221,6 +384,45 @@ test_expect_success 'append notes by specifying multiple "-F"' '
 	test_cmp expect actual
 '
 
+test_expect_success 'append note by specifying multiple "-F" with "--no-stripspace"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	initial-line
+	${LF}${LF}
+	file-1-first-line
+	${MULTI_LF}
+	file-1-second-line
+	${LF}
+
+	${LF}
+	file-2-first-line
+	${MULTI_LF}
+	file-2-second-line
+	${LF}
+	EOF
+
+	cat >note-file-1 <<-EOF &&
+	${LF}
+	file-1-first-line
+	${MULTI_LF}
+	file-1-second-line
+	${LF}
+	EOF
+
+	cat >note-file-2 <<-EOF &&
+	${LF}
+	file-2-first-line
+	${MULTI_LF}
+	file-2-second-line
+	${LF}
+	EOF
+
+	git notes add -m "initial-line" &&
+	git notes append --no-stripspace -F note-file-1 -F note-file-2 &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'add notes with empty messages' '
 	rev=$(git rev-parse HEAD) &&
 	git notes add -m "${LF}" \
@@ -229,7 +431,7 @@ test_expect_success 'add notes with empty messages' '
 	test_i18ngrep "Removing note for object" actual
 '
 
-test_expect_success 'add note by specifying "-C" , do not stripspace is the default behavior' '
+test_expect_success 'add note by specifying "-C", "--no-stripspace" is the default behavior' '
 	test_when_finished "git notes remove" &&
 	cat >expect <<-EOF &&
 	${LF}
@@ -242,10 +444,36 @@ test_expect_success 'add note by specifying "-C" , do not stripspace is the defa
 	cat expect | git hash-object -w --stdin >blob &&
 	git notes add -C $(cat blob) &&
 	git notes show >actual &&
+	test_cmp expect actual &&
+	git notes remove &&
+	git notes add --no-stripspace -C $(cat blob) &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'reuse note by specifying "-C" and "--stripspace"' '
+	test_when_finished "git notes remove" &&
+	cat >data <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	cat data | git hash-object -w --stdin >blob &&
+	git notes add --stripspace -C $(cat blob) &&
+	git notes show >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success 'add notes with "-C" and "-m", "-m" will stripspace all together' '
+test_expect_success 'reuse with "-C" and add note with "-m", "-m" will stripspace all together' '
 	test_when_finished "git notes remove" &&
 	cat >data <<-EOF &&
 	${LF}
@@ -269,7 +497,7 @@ test_expect_success 'add notes with "-C" and "-m", "-m" will stripspace all toge
 	test_cmp expect actual
 '
 
-test_expect_success 'add notes with "-m" and "-C", "-C" will not stripspace all together' '
+test_expect_success 'add note with "-m" and reuse note with "-C", "-C" will not stripspace all together' '
 	test_when_finished "git notes remove" &&
 	cat >data <<-EOF &&
 
@@ -288,4 +516,62 @@ test_expect_success 'add notes with "-m" and "-C", "-C" will not stripspace all
 	test_cmp expect actual
 '
 
+test_expect_success 'add note by specifying "-c", "--stripspace" is the default behavior' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	echo "initial-line" | git hash-object -w --stdin >blob &&
+	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes add -c $(cat blob) &&
+	git notes show >actual &&
+	test_cmp expect actual &&
+	git notes remove &&
+	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes add --stripspace -c $(cat blob) &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying "-c" with "--no-stripspace"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	${LF}first-line${MULTI_LF}second-line${LF}
+	EOF
+
+	echo "initial-line" | git hash-object -w --stdin >blob &&
+	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes add --no-stripspace -c $(cat blob) &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'edit note by specifying "-c", "--stripspace" is the default behavior' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes edit &&
+	git notes show >actual &&
+	test_cmp expect actual &&
+	git notes remove &&
+	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes edit --stripspace &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'edit note by specifying "-c" with "--no-stripspace"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	${LF}first-line${MULTI_LF}second-line${LF}
+	EOF
+
+	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes add --no-stripspace &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
 test_done
-- 
2.40.0.358.g2947072e.dirty
Junio C Hamano· Apr 28, 2023, 20:46 UTC · re: Teng Long · lore

Re: [PATCH v9 0/6] notes.c: introduce "--separator" option

Teng Long <dyroneteng@gmail.com> writes:
> From: Teng Long <dyroneteng@gmail.com>

It may help newcomers to briefly mention what the purpose of this series is here. Not everybody necessarily has followed previous iterations, especially when it comes to a series with this many iterations.

Show 9 quoted lines
> Thanks.
>
> Teng Long (6):
>   notes.c: cleanup 'strbuf_grow' call in 'append_edit'
>   notes.c: use designated initializers for clarity
>   t3321: add test cases about the notes stripspace behavior
>   notes.c: introduce '--separator=<paragraph-break>' option
>   notes.c: append separator instead of insert by pos
>   notes.c: introduce "--[no-]stripspace" option
Looks quite well done.  Will replace.
Thanks.
Junio C Hamano· May 1, 2023, 22:29 UTC · re: Teng Long · lore

Re: [PATCH v9 0/6] notes.c: introduce "--separator" option

Teng Long <dyroneteng@gmail.com> writes:
Show 23 quoted lines
> From: Teng Long <dyroneteng@gmail.com>
>
> Diff since v8:
>
> 1. test case: Uniform indent format, as recommended by Junio C Hamano.
>
> 2. [4/6] make the static var "separator" initialized as "\n", simplify
>    the code in "insert_separator(...)".
>
> 3. [4/6] I don't change the other parts about the "struct note_msg", the
>    stripspace way (Junio suggest to consider about the stripspace each messge
>    individually, but I found it will break the compatibility about "-C",
>    which can be found the case of 'reuse with "-C" and add note with "-m",
>    "-m" will stripspace all together').
> 4. [5/6] Optimized the commit message and replace "strbuf_insert*(...)" with
>    "strbuf_add*(...)".
>
> 5. [6/6] As Junio replied, I'm not sure whether the "-C" problem (When the
>    "-C" argument is used with "-m/-F", the order of "-C" in the options will
>    affect the result of stripspace differently,) is need to be fixed or keep
>    as is, I choose to do not break the old behaviour (In fact, I hope to fix
>    this issue in another patch, if at all, and let this long-tailed patchset
>    to mature faster, maybe).

I am inclined to say that we should declare victory and merge this down to 'next' soonish, unless somebody spots a big hole in the logic, or finds a nicer way to "solve" the "-C problem" (to which I suspect there is no clean solution, as the original behaviour is more or less inconsistent---that is probably because the feature was designed assuming that nobody will combine -C with other options).

Thanks.
Teng Long· May 18, 2023, 12:02 UTC · re: Teng Long · lore

[PATCH v10 0/6] notes.c: introduce "--separator" option

From: Teng Long <dyroneteng@gmail.com>
Diff since v9:
1. [4/6] support `--no-separator` which means not to add any paragraph-breaks.
2. [4/6] Fix the problems by the Junio's suggestion [1]
[1] https://public-inbox.org/git/xmqqsfcjbuud.fsf@gitster.g/
Thanks.
Teng Long (6):
  notes.c: cleanup 'strbuf_grow' call in 'append_edit'
  notes.c: use designated initializers for clarity
  t3321: add test cases about the notes stripspace behavior
  notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]'
    option
  notes.c: append separator instead of insert by pos
  notes.c: introduce "--[no-]stripspace" option
 Documentation/git-notes.txt |  43 ++-
 builtin/notes.c             | 157 +++++++---
 t/t3301-notes.sh            | 169 +++++++++++
 t/t3321-notes-stripspace.sh | 577 ++++++++++++++++++++++++++++++++++++
 4 files changed, 904 insertions(+), 42 deletions(-)
 create mode 100755 t/t3321-notes-stripspace.sh
Range-diff against v9:
1:  0634434e = 1:  0634434e notes.c: cleanup 'strbuf_grow' call in 'append_edit'
2:  4ad78405 = 2:  4ad78405 notes.c: use designated initializers for clarity
3:  c2fc2091 = 3:  c2fc2091 t3321: add test cases about the notes stripspace behavior
4:  ed930ef4 ! 4:  820dda04 notes.c: introduce '--separator=<paragraph-break>' option
    @@ Metadata
     Author: Teng Long <dyroneteng@gmail.com>
     
      ## Commit message ##
    -    notes.c: introduce '--separator=<paragraph-break>' option
    +    notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option
     
         When adding new notes or appending to an existing notes, we will
         insert a blank line between the paragraphs, like:
    @@ Commit message
     
         Both the two exections produce the same result.
     
    +    Alternatively, if you do not want any new paragraph
    +    separators, even a newline by default, you can specify
    +    '--no-separator'.
    +
         The reason we use a "strbuf" array to concat but not "string_list", is
         that the binary file content may contain '\0' in the middle, this will
         cause the corrupt result if using a string to save.
    @@ Documentation/git-notes.txt: SYNOPSIS
      [verse]
      'git notes' [list [<object>]]
     -'git notes' add [-f] [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
    -+'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
    ++'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
      'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )
     -'git notes' append [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
    -+'git notes' append [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
    ++'git notes' append [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
      'git notes' edit [--allow-empty] [<object>]
      'git notes' show [<object>]
      'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>
    @@ Documentation/git-notes.txt: OPTIONS
      	Allow an empty note object to be stored. The default behavior is
      	to automatically remove empty notes.
      
    -+--separator <paragraph-break>::
    ++--[no-]separator, --separator=<paragraph-break>::
     +	Specify a string used as a custom inter-paragraph separator
    -+	(a newline is added at the end as needed).  Defaults to a
    -+	blank line.
    ++	(a newline is added at the end as needed). If `--no-separator`, no
    ++	separators will be added between paragraphs.  Defaults to a blank
    ++	line.
     +
      --ref <ref>::
      	Manipulate the notes tree in <ref>.  This overrides
    @@ builtin/notes.c
      #include "worktree.h"
      #include "write-or-die.h"
      
    -+static char *separator = "\n";
    ++static const char *separator = "\n";
      static const char * const git_notes_usage[] = {
      	N_("git notes [--ref <notes-ref>] [list [<object>]]"),
     -	N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
    -+	N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
    ++	N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
      	N_("git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>"),
     -	N_("git notes [--ref <notes-ref>] append [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
    -+	N_("git notes [--ref <notes-ref>] append [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
    ++	N_("git notes [--ref <notes-ref>] append [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
      	N_("git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]"),
      	N_("git notes [--ref <notes-ref>] show [<object>]"),
      	N_("git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>"),
    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_
      
     +static void insert_separator(struct strbuf *message, size_t pos)
     +{
    -+	if (separator[strlen(separator) - 1] == '\n')
    -+		strbuf_addstr(message, separator);
    ++	if (!separator)
    ++		return;
    ++	else if (separator[strlen(separator) - 1] == '\n')
    ++		strbuf_insertstr(message, pos, separator);
     +	else
     +		strbuf_insertf(message, pos, "%s%s", separator, "\n");
     +}
    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_
     +static void concat_messages(struct note_data *d)
     +{
     +	struct strbuf msg = STRBUF_INIT;
    -+
     +	size_t i;
    ++
     +	for (i = 0; i < d->msg_nr ; i++) {
     +		if (d->buf.len)
     +			insert_separator(&d->buf, d->buf.len);
    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_
      	return 0;
      }
      
    +@@ builtin/notes.c: static int parse_reedit_arg(const struct option *opt, const char *arg, int unset
    + 	return parse_reuse_arg(opt, arg, unset);
    + }
    + 
    ++static int parse_separator_arg(const struct option *opt, const char *arg,
    ++			       int unset)
    ++{
    ++	if (unset)
    ++		*(const char **)opt->value = NULL;
    ++	else
    ++		*(const char **)opt->value = arg ? arg : "\n";
    ++	return 0;
    ++}
    ++
    + static int notes_copy_from_stdin(int force, const char *rewrite_cmd)
    + {
    + 	struct strbuf buf = STRBUF_INIT;
     @@ builtin/notes.c: static int add(int argc, const char **argv, const char *prefix)
      	struct object_id object, new_note;
      	const struct object_id *note;
    @@ builtin/notes.c: static int add(int argc, const char **argv, const char *prefix)
      		OPT_BOOL(0, "allow-empty", &allow_empty,
      			N_("allow storing empty note")),
      		OPT__FORCE(&force, N_("replace existing notes"), PARSE_OPT_NOCOMPLETE),
    -+		OPT_STRING(0, "separator", &separator, N_("separator"),
    -+			N_("insert <paragraph-break> between paragraphs")),
    ++		OPT_CALLBACK_F(0, "separator", &separator,
    ++			N_("<paragraph-break>"),
    ++			N_("insert <paragraph-break> between paragraphs"),
    ++			PARSE_OPT_OPTARG, parse_separator_arg),
      		OPT_END()
      	};
      
    @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char
      			parse_reuse_arg),
      		OPT_BOOL(0, "allow-empty", &allow_empty,
      			N_("allow storing empty note")),
    -+		OPT_STRING(0, "separator", &separator, N_("separator"),
    -+			N_("insert <paragraph-break> between paragraphs")),
    ++		OPT_CALLBACK_F(0, "separator", &separator,
    ++			N_("<paragraph-break>"),
    ++			N_("insert <paragraph-break> between paragraphs"),
    ++			PARSE_OPT_OPTARG, parse_separator_arg),
      		OPT_END()
      	};
      	int edit = !strcmp(argv[0], "edit");
    @@ t/t3301-notes.sh: test_expect_success 'create note with combination of -m and -F
      '
      
     +test_expect_success 'create note with combination of -m and -F and --separator' '
    ++	test_when_finished git notes remove HEAD &&
     +	cat >expect-combine_m_and_F <<-\EOF &&
     +	foo
     +	-------
    @@ t/t3301-notes.sh: test_expect_success 'create note with combination of -m and -F
     +	EOF
     +	echo "xyzzy" >note_a &&
     +	echo "zyxxy" >note_b &&
    -+	git notes add -m "foo" -F note_a -m "bar" -F note_b -m "baz" --separator "-------" &&
    ++	git notes add -m "foo" -F note_a -m "bar" -F note_b -m "baz" --separator="-------" &&
    ++	git notes show >actual &&
    ++	test_cmp expect-combine_m_and_F actual
    ++'
    ++
    ++test_expect_success 'create note with combination of -m and -F and --no-separator' '
    ++	cat >expect-combine_m_and_F <<-\EOF &&
    ++	foo
    ++	xyzzy
    ++	bar
    ++	zyxxy
    ++	baz
    ++	EOF
    ++	echo "xyzzy" >note_a &&
    ++	echo "zyxxy" >note_b &&
    ++	git notes add -m "foo" -F note_a -m "bar" -F note_b -m "baz" --no-separator &&
     +	git notes show >actual &&
     +	test_cmp expect-combine_m_and_F actual
     +'
    @@ t/t3301-notes.sh: test_expect_success 'listing non-existing notes fails' '
      	test_must_be_empty actual
      '
      
    -+test_expect_success 'append: specify an empty separator' '
    ++test_expect_success 'append: specify a separator with an empty arg' '
     +	test_when_finished git notes remove HEAD &&
     +	cat >expect <<-\EOF &&
     +	notes-1
    @@ t/t3301-notes.sh: test_expect_success 'listing non-existing notes fails' '
     +	test_cmp expect actual
     +'
     +
    ++test_expect_success 'append: specify a separator without arg' '
    ++	test_when_finished git notes remove HEAD &&
    ++	cat >expect <<-\EOF &&
    ++	notes-1
    ++
    ++	notes-2
    ++	EOF
    ++
    ++	git notes add -m "notes-1" &&
    ++	git notes append --separator -m "notes-2" &&
    ++	git notes show >actual &&
    ++	test_cmp expect actual
    ++'
    ++
    ++test_expect_success 'append: specify as --no-separator' '
    ++	test_when_finished git notes remove HEAD &&
    ++	cat >expect <<-\EOF &&
    ++	notes-1
    ++	notes-2
    ++	EOF
    ++
    ++	git notes add -m "notes-1" &&
    ++	git notes append --no-separator -m "notes-2" &&
    ++	git notes show >actual &&
    ++	test_cmp expect actual
    ++'
    ++
     +test_expect_success 'append: specify separator with line break' '
     +	test_when_finished git notes remove HEAD &&
     +	cat >expect <<-\EOF &&
    @@ t/t3301-notes.sh: test_expect_success 'listing non-existing notes fails' '
     +
     +	echo "f-notes-1" >note_a &&
     +	echo "f-notes-2" >note_b &&
    -+	git notes append -m "m-notes-1" -F note_a -m "m-notes-2" -F note_b -m "m-notes-3" --separator "-------" &&
    ++	git notes append -m "m-notes-1" -F note_a -m "m-notes-2" -F note_b -m "m-notes-3" --separator="-------" &&
     +	git notes show >actual &&
     +	test_cmp expect-combine_m_and_F actual
     +'
5:  eea2246f ! 5:  76c93f19 notes.c: append separator instead of insert by pos
    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_
     -static void insert_separator(struct strbuf *message, size_t pos)
     +static void append_separator(struct strbuf *message)
      {
    - 	if (separator[strlen(separator) - 1] == '\n')
    - 		strbuf_addstr(message, separator);
    + 	if (!separator)
    + 		return;
    + 	else if (separator[strlen(separator) - 1] == '\n')
    +-		strbuf_insertstr(message, pos, separator);
    ++		strbuf_addstr(message, separator);
      	else
     -		strbuf_insertf(message, pos, "%s%s", separator, "\n");
     +		strbuf_addf(message, "%s%s", separator, "\n");
    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_
      
      static void concat_messages(struct note_data *d)
     @@ builtin/notes.c: static void concat_messages(struct note_data *d)
    - 	size_t i;
    + 
      	for (i = 0; i < d->msg_nr ; i++) {
      		if (d->buf.len)
     -			insert_separator(&d->buf, d->buf.len);
6:  20063bea ! 6:  d65f067c notes.c: introduce "--[no-]stripspace" option
    @@ Documentation/git-notes.txt: SYNOPSIS
      --------
      [verse]
      'git notes' [list [<object>]]
    --'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
    -+'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
    +-'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
    ++'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
      'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )
    --'git notes' append [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
    +-'git notes' append [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
     -'git notes' edit [--allow-empty] [<object>]
    -+'git notes' append [--allow-empty] [--separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
    ++'git notes' append [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
     +'git notes' edit [--allow-empty] [<object>] [--[no-]stripspace]
      'git notes' show [<object>]
      'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>
    @@ Documentation/git-notes.txt: OPTIONS
      -c <object>::
      --reedit-message=<object>::
     @@ Documentation/git-notes.txt: OPTIONS
    - 	(a newline is added at the end as needed).  Defaults to a
    - 	blank line.
    + 	separators will be added between paragraphs.  Defaults to a blank
    + 	line.
      
     +--[no-]stripspace::
     +	Strip leading and trailing whitespace from the note message.
    @@ Documentation/git-notes.txt: OPTIONS
      	`GIT_NOTES_REF` and the "core.notesRef" configuration.  The ref
     
      ## builtin/notes.c ##
    +@@
    + static const char *separator = "\n";
    + static const char * const git_notes_usage[] = {
    + 	N_("git notes [--ref <notes-ref>] [list [<object>]]"),
    +-	N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
    ++	N_("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_("git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>"),
    +-	N_("git notes [--ref <notes-ref>] append [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
    ++	N_("git notes [--ref <notes-ref>] append [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [--[no-]stripspace] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
    + 	N_("git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]"),
    + 	N_("git notes [--ref <notes-ref>] show [<object>]"),
    + 	N_("git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>"),
     @@ builtin/notes.c: static const char * const git_notes_get_ref_usage[] = {
      static const char note_template[] =
      	N_("Write/edit the notes for the following object:");
    @@ builtin/notes.c: static int add(int argc, const char **argv, const char *prefix)
      	struct option options[] = {
      		OPT_CALLBACK_F('m', "message", &d, N_("message"),
     @@ builtin/notes.c: static int add(int argc, const char **argv, const char *prefix)
    - 		OPT__FORCE(&force, N_("replace existing notes"), PARSE_OPT_NOCOMPLETE),
    - 		OPT_STRING(0, "separator", &separator, N_("separator"),
    - 			N_("insert <paragraph-break> between paragraphs")),
    + 			N_("<paragraph-break>"),
    + 			N_("insert <paragraph-break> between paragraphs"),
    + 			PARSE_OPT_OPTARG, parse_separator_arg),
     +		OPT_BOOL(0, "stripspace", &d.stripspace,
     +			N_("remove unnecessary whitespace")),
      		OPT_END()
    @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char
      		OPT_CALLBACK_F('m', "message", &d, N_("message"),
      			N_("note contents as a string"), PARSE_OPT_NONEG,
     @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char *prefix)
    - 			N_("allow storing empty note")),
    - 		OPT_STRING(0, "separator", &separator, N_("separator"),
    - 			N_("insert <paragraph-break> between paragraphs")),
    + 			N_("<paragraph-break>"),
    + 			N_("insert <paragraph-break> between paragraphs"),
    + 			PARSE_OPT_OPTARG, parse_separator_arg),
     +		OPT_BOOL(0, "stripspace", &d.stripspace,
     +			N_("remove unnecessary whitespace")),
      		OPT_END()
-- 
2.40.0.356.g67a1c1d0
Teng Long· May 18, 2023, 12:02 UTC · re: Teng Long · lore

[PATCH v10 1/6] notes.c: cleanup 'strbuf_grow' call in 'append_edit'

From: Teng Long <dyroneteng@gmail.com>

Let's cleanup the unnecessary 'strbuf_grow' call in 'append_edit'. This "strbuf_grow(&d.buf, size + 1);" is prepared for insert a blank line if needed, but actually when inserting, "strbuf_insertstr(&d.buf, 0, "\n");" will do the "grow" for us.

348f199b (builtin-notes: Refactor handling of -F option to allow combining -m and -F, 2010-02-13) added these to mimic the code introduced by 2347fae5 (builtin-notes: Add "append" subcommand for appending to note objects, 2010-02-13) that reads in previous note before the message. And the resulting code with explicit sizing is carried to this day.

In the context of reading an existing note in, exact sizing may have made sense, but because the resulting note needs cleansing with stripspace() when appending with this option, such an exact sizing does not buy us all that much in practice.

It may help avoiding overallocation due to ALLOC_GROW() slop, but nobody can feed so many long messages for it to matter from the command line.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Helped-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/notes.c | 2 --
 1 file changed, 2 deletions(-)
Show changes to builtin/notes.c +0 −2
diff --git a/builtin/notes.c b/builtin/notes.c
index 4ff44f1e..c501c6ee 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -219,7 +219,6 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
 
 	BUG_ON_OPT_NEG(unset);
 
-	strbuf_grow(&d->buf, strlen(arg) + 2);
 	if (d->buf.len)
 		strbuf_addch(&d->buf, '\n');
 	strbuf_addstr(&d->buf, arg);
@@ -623,7 +622,6 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		char *prev_buf = repo_read_object_file(the_repository, note,
 						       &type, &size);
 
-		strbuf_grow(&d.buf, size + 1);
 		if (d.buf.len && prev_buf && size)
 			strbuf_insertstr(&d.buf, 0, "\n");
 		if (prev_buf && size)
-- 
2.40.0.356.g67a1c1d0
Teng Long· May 18, 2023, 12:02 UTC · re: Teng Long · lore

[PATCH v10 2/6] notes.c: use designated initializers for clarity

From: Teng Long <dyroneteng@gmail.com>

The "struct note_data d = { 0, 0, NULL, STRBUF_INIT };" style could be replaced with designated initializer for clarity.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/notes.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
Show changes to builtin/notes.c +2 −2
diff --git a/builtin/notes.c b/builtin/notes.c
index c501c6ee..9d8ca795 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -405,7 +405,7 @@ static int add(int argc, const char **argv, const char *prefix)
 	struct notes_tree *t;
 	struct object_id object, new_note;
 	const struct object_id *note;
-	struct note_data d = { 0, 0, NULL, STRBUF_INIT };
+	struct note_data d = { .buf = STRBUF_INIT };
 	struct option options[] = {
 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
 			N_("note contents as a string"), PARSE_OPT_NONEG,
@@ -571,7 +571,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 	const struct object_id *note;
 	char *logmsg;
 	const char * const *usage;
-	struct note_data d = { 0, 0, NULL, STRBUF_INIT };
+	struct note_data d = { .buf = STRBUF_INIT };
 	struct option options[] = {
 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
 			N_("note contents as a string"), PARSE_OPT_NONEG,
-- 
2.40.0.356.g67a1c1d0
Teng Long· May 18, 2023, 12:02 UTC · re: Teng Long · lore

[PATCH v10 3/6] t3321: add test cases about the notes stripspace behavior

From: Teng Long <dyroneteng@gmail.com>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 t/t3321-notes-stripspace.sh | 291 ++++++++++++++++++++++++++++++++++++
 1 file changed, 291 insertions(+)
 create mode 100755 t/t3321-notes-stripspace.sh
Show changes to t/t3321-notes-stripspace.sh +291 −0
diff --git a/t/t3321-notes-stripspace.sh b/t/t3321-notes-stripspace.sh
new file mode 100755
index 00000000..89977873
--- /dev/null
+++ b/t/t3321-notes-stripspace.sh
@@ -0,0 +1,291 @@
+#!/bin/sh
+#
+# Copyright (c) 2023 Teng Long
+#
+
+test_description='Test commit notes with stripspace behavior'
+
+. ./test-lib.sh
+
+MULTI_LF="$LF$LF$LF"
+write_script fake_editor <<\EOF
+echo "$MSG" >"$1"
+echo "$MSG" >&2
+EOF
+GIT_EDITOR=./fake_editor
+export GIT_EDITOR
+
+test_expect_success 'setup the commit' '
+	test_commit 1st
+'
+
+test_expect_success 'add note by editor' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes add  &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying single "-m"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	git notes add -m "${LF}first-line${MULTI_LF}second-line${LF}" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying multiple "-m"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	git notes add -m "${LF}" \
+		      -m "first-line" \
+		      -m "${MULTI_LF}" \
+		      -m "second-line" \
+		      -m "${LF}" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+
+test_expect_success 'append note by editor' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	git notes add -m "first-line" &&
+	MSG="${MULTI_LF}second-line${LF}" git notes append  &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append note by specifying single "-m"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	git notes add -m "${LF}first-line" &&
+	git notes append -m "${MULTI_LF}second-line${LF}" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append note by specifying multiple "-m"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	git notes add -m "${LF}first-line" &&
+	git notes append -m "${MULTI_LF}" \
+		      -m "second-line" \
+		      -m "${LF}" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying single "-F"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	cat >note-file <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	git notes add -F note-file &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add notes by specifying multiple "-F"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	file-1-first-line
+
+	file-1-second-line
+
+	file-2-first-line
+
+	file-2-second-line
+	EOF
+
+	cat >note-file-1 <<-EOF &&
+	${LF}
+	file-1-first-line
+	${MULTI_LF}
+	file-1-second-line
+	${LF}
+	EOF
+
+	cat >note-file-2 <<-EOF &&
+	${LF}
+	file-2-first-line
+	${MULTI_LF}
+	file-2-second-line
+	${LF}
+	EOF
+
+	git notes add -F note-file-1 -F note-file-2 &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append note by specifying single "-F"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	initial-line
+
+	first-line
+
+	second-line
+	EOF
+
+	cat >note-file <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	git notes add -m "initial-line" &&
+	git notes append -F note-file &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append notes by specifying multiple "-F"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	initial-line
+
+	file-1-first-line
+
+	file-1-second-line
+
+	file-2-first-line
+
+	file-2-second-line
+	EOF
+
+	cat >note-file-1 <<-EOF &&
+	${LF}
+	file-1-first-line
+	${MULTI_LF}
+	file-1-second-line
+	${LF}
+	EOF
+
+	cat >note-file-2 <<-EOF &&
+	${LF}
+	file-2-first-line
+	${MULTI_LF}
+	file-2-second-line
+	${LF}
+	EOF
+
+	git notes add -m "initial-line" &&
+	git notes append -F note-file-1 -F note-file-2 &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add notes with empty messages' '
+	rev=$(git rev-parse HEAD) &&
+	git notes add -m "${LF}" \
+		      -m "${MULTI_LF}" \
+		      -m "${LF}" >actual 2>&1 &&
+	test_i18ngrep "Removing note for object" actual
+'
+
+test_expect_success 'add note by specifying "-C" , do not stripspace is the default behavior' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	cat expect | git hash-object -w --stdin >blob &&
+	git notes add -C $(cat blob) &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add notes with "-C" and "-m", "-m" will stripspace all together' '
+	test_when_finished "git notes remove" &&
+	cat >data <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+
+	third-line
+	EOF
+
+	cat data | git hash-object -w --stdin >blob &&
+	git notes add -C $(cat blob) -m "third-line" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add notes with "-m" and "-C", "-C" will not stripspace all together' '
+	test_when_finished "git notes remove" &&
+	cat >data <<-EOF &&
+
+	second-line
+	EOF
+
+	cat >expect <<-EOF &&
+	first-line
+	${LF}
+	second-line
+	EOF
+
+	cat data | git hash-object -w --stdin >blob &&
+	git notes add -m "first-line" -C $(cat blob)  &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_done
-- 
2.40.0.356.g67a1c1d0
Teng Long· May 18, 2023, 12:02 UTC · re: Teng Long · lore

[PATCH v10 5/6] notes.c: append separator instead of insert by pos

From: Teng Long <dyroneteng@gmail.com>

Rename "insert_separator" to "append_separator" and also remove the "postion" argument, this serves two purpose:

The first is that when specifying more than one "-m" ( like "-F", etc) to "git notes add" or "git notes append", the order of them matters, which means we need to append the each separator and message in turn, so we don't have to make the caller specify the position, the "append" operation is enough and clear.

The second is that when we execute the "git notes append" subcommand, we need to combine the "prev_note" and "current_note" to get the final result. Before, we inserted a newline character at the beginning of "current_note". Now, we will append a newline to the end of "prev_note" instead, this will give the consisitent results.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/notes.c | 21 ++++++++++++---------
 1 file changed, 12 insertions(+), 9 deletions(-)
Show changes to builtin/notes.c +12 −9
diff --git a/builtin/notes.c b/builtin/notes.c
index 7aa2f923..84c010fe 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -229,14 +229,14 @@ static void write_note_data(struct note_data *d, struct object_id *oid)
 	}
 }
 
-static void insert_separator(struct strbuf *message, size_t pos)
+static void append_separator(struct strbuf *message)
 {
 	if (!separator)
 		return;
 	else if (separator[strlen(separator) - 1] == '\n')
-		strbuf_insertstr(message, pos, separator);
+		strbuf_addstr(message, separator);
 	else
-		strbuf_insertf(message, pos, "%s%s", separator, "\n");
+		strbuf_addf(message, "%s%s", separator, "\n");
 }
 
 static void concat_messages(struct note_data *d)
@@ -246,7 +246,7 @@ static void concat_messages(struct note_data *d)
 
 	for (i = 0; i < d->msg_nr ; i++) {
 		if (d->buf.len)
-			insert_separator(&d->buf, d->buf.len);
+			append_separator(&d->buf);
 		strbuf_add(&msg, d->messages[i]->buf.buf, d->messages[i]->buf.len);
 		strbuf_addbuf(&d->buf, &msg);
 		if (d->messages[i]->stripspace)
@@ -695,14 +695,17 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		/* Append buf to previous note contents */
 		unsigned long size;
 		enum object_type type;
-		char *prev_buf = repo_read_object_file(the_repository, note,
-						       &type, &size);
+		struct strbuf buf = STRBUF_INIT;
+		char *prev_buf = repo_read_object_file(the_repository, note, &type, &size);
 
-		if (d.buf.len && prev_buf && size)
-			insert_separator(&d.buf, 0);
 		if (prev_buf && size)
-			strbuf_insert(&d.buf, 0, prev_buf, size);
+			strbuf_add(&buf, prev_buf, size);
+		if (d.buf.len && prev_buf && size)
+			append_separator(&buf);
+		strbuf_insert(&d.buf, 0, buf.buf, buf.len);
+
 		free(prev_buf);
+		strbuf_release(&buf);
 	}
 
 	if (d.buf.len || allow_empty) {
-- 
2.40.0.356.g67a1c1d0
Teng Long· May 18, 2023, 12:02 UTC · re: Teng Long · lore

[PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option

From: Teng Long <dyroneteng@gmail.com>

When adding new notes or appending to an existing notes, we will insert a blank line between the paragraphs, like:

     $ git notes add -m foo -m bar
     $ git notes show HEAD
     foo
     bar

The default behavour sometimes is not enough, the user may want to use a custom delimiter between paragraphs, like when specifying '-m', '-F', '-C', '-c' options. So this commit introduce a new '--separator' option for 'git notes add' and 'git notes append', for example when executing:

    $ git notes add -m foo -m bar --separator="-"
    $ git notes show HEAD
    foo
    -
    bar

a newline is added to the value given to --separator if it does not end with one already. So when executing:

      $ git notes add -m foo -m bar --separator="-"
and
      $ export LF="
      "
      $ git notes add -m foo -m bar --separator="-$LF"
Both the two exections produce the same result.

Alternatively, if you do not want any new paragraph separators, even a newline by default, you can specify '--no-separator'.

The reason we use a "strbuf" array to concat but not "string_list", is that the binary file content may contain '\0' in the middle, this will cause the corrupt result if using a string to save.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-notes.txt |  22 +++--
 builtin/notes.c             | 124 +++++++++++++++++++++-----
 t/t3301-notes.sh            | 169 ++++++++++++++++++++++++++++++++++++
 3 files changed, 286 insertions(+), 29 deletions(-)
Show changes to 3 files +286 −29

Documentation/git-notes.txt, builtin/notes.c, t/t3301-notes.sh

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index efbc10f0..56d25a79 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -9,9 +9,9 @@ SYNOPSIS
 --------
 [verse]
 'git notes' [list [<object>]]
-'git notes' add [-f] [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
+'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
 'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )
-'git notes' append [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
+'git notes' append [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
 'git notes' edit [--allow-empty] [<object>]
 'git notes' show [<object>]
 'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>
@@ -65,7 +65,9 @@ add::
 	However, if you're using `add` interactively (using an editor
 	to supply the notes contents), then - instead of aborting -
 	the existing notes will be opened in the editor (like the `edit`
-	subcommand).
+	subcommand). If you specify multiple `-m` and `-F`, a blank
+	line will be inserted between the messages. Use the `--separator`
+	option to insert other delimiters.
 
 copy::
 	Copy the notes for the first object onto the second object (defaults to
@@ -85,8 +87,12 @@ corresponding <to-object>.  (The optional `<rest>` is ignored so that
 the command can read the input given to the `post-rewrite` hook.)
 
 append::
-	Append to the notes of an existing object (defaults to HEAD).
-	Creates a new notes object if needed.
+	Append new message(s) given by `-m` or `-F` options to an
+	existing note, or add them as a new note if one does not
+	exist, for the object (defaults to HEAD).  When appending to
+	an existing note, a blank line is added before each new
+	message as an inter-paragraph separator.  The separator can
+	be customized with the `--separator` option.
 
 edit::
 	Edit the notes for a given object (defaults to HEAD).
@@ -159,6 +165,12 @@ OPTIONS
 	Allow an empty note object to be stored. The default behavior is
 	to automatically remove empty notes.
 
+--[no-]separator, --separator=<paragraph-break>::
+	Specify a string used as a custom inter-paragraph separator
+	(a newline is added at the end as needed). If `--no-separator`, no
+	separators will be added between paragraphs.  Defaults to a blank
+	line.
+
 --ref <ref>::
 	Manipulate the notes tree in <ref>.  This overrides
 	`GIT_NOTES_REF` and the "core.notesRef" configuration.  The ref
diff --git a/builtin/notes.c b/builtin/notes.c
index 9d8ca795..7aa2f923 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -8,6 +8,7 @@
  */
 
 #include "cache.h"
+#include "alloc.h"
 #include "config.h"
 #include "builtin.h"
 #include "gettext.h"
@@ -27,11 +28,12 @@
 #include "worktree.h"
 #include "write-or-die.h"
 
+static const char *separator = "\n";
 static const char * const git_notes_usage[] = {
 	N_("git notes [--ref <notes-ref>] [list [<object>]]"),
-	N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
+	N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
 	N_("git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>"),
-	N_("git notes [--ref <notes-ref>] append [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
+	N_("git notes [--ref <notes-ref>] append [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
 	N_("git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]"),
 	N_("git notes [--ref <notes-ref>] show [<object>]"),
 	N_("git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>"),
@@ -99,11 +101,19 @@ static const char * const git_notes_get_ref_usage[] = {
 static const char note_template[] =
 	N_("Write/edit the notes for the following object:");
 
+struct note_msg {
+	int stripspace;
+	struct strbuf buf;
+};
+
 struct note_data {
 	int given;
 	int use_editor;
 	char *edit_path;
 	struct strbuf buf;
+	struct note_msg **messages;
+	size_t msg_nr;
+	size_t msg_alloc;
 };
 
 static void free_note_data(struct note_data *d)
@@ -113,6 +123,12 @@ static void free_note_data(struct note_data *d)
 		free(d->edit_path);
 	}
 	strbuf_release(&d->buf);
+
+	while (d->msg_nr--) {
+		strbuf_release(&d->messages[d->msg_nr]->buf);
+		free(d->messages[d->msg_nr]);
+	}
+	free(d->messages);
 }
 
 static int list_each_note(const struct object_id *object_oid,
@@ -213,65 +229,98 @@ static void write_note_data(struct note_data *d, struct object_id *oid)
 	}
 }
 
+static void insert_separator(struct strbuf *message, size_t pos)
+{
+	if (!separator)
+		return;
+	else if (separator[strlen(separator) - 1] == '\n')
+		strbuf_insertstr(message, pos, separator);
+	else
+		strbuf_insertf(message, pos, "%s%s", separator, "\n");
+}
+
+static void concat_messages(struct note_data *d)
+{
+	struct strbuf msg = STRBUF_INIT;
+	size_t i;
+
+	for (i = 0; i < d->msg_nr ; i++) {
+		if (d->buf.len)
+			insert_separator(&d->buf, d->buf.len);
+		strbuf_add(&msg, d->messages[i]->buf.buf, d->messages[i]->buf.len);
+		strbuf_addbuf(&d->buf, &msg);
+		if (d->messages[i]->stripspace)
+			strbuf_stripspace(&d->buf, 0);
+		strbuf_reset(&msg);
+	}
+	strbuf_release(&msg);
+}
+
 static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
 {
 	struct note_data *d = opt->value;
+	struct note_msg *msg = xmalloc(sizeof(*msg));
 
 	BUG_ON_OPT_NEG(unset);
 
-	if (d->buf.len)
-		strbuf_addch(&d->buf, '\n');
-	strbuf_addstr(&d->buf, arg);
-	strbuf_stripspace(&d->buf, 0);
-
-	d->given = 1;
+	strbuf_init(&msg->buf, strlen(arg));
+	strbuf_addstr(&msg->buf, arg);
+	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
+	d->messages[d->msg_nr - 1] = msg;
+	msg->stripspace = 1;
 	return 0;
 }
 
 static int parse_file_arg(const struct option *opt, const char *arg, int unset)
 {
 	struct note_data *d = opt->value;
+	struct note_msg *msg = xmalloc(sizeof(*msg));
 
 	BUG_ON_OPT_NEG(unset);
 
-	if (d->buf.len)
-		strbuf_addch(&d->buf, '\n');
+	strbuf_init(&msg->buf , 0);
 	if (!strcmp(arg, "-")) {
-		if (strbuf_read(&d->buf, 0, 1024) < 0)
+		if (strbuf_read(&msg->buf, 0, 1024) < 0)
 			die_errno(_("cannot read '%s'"), arg);
-	} else if (strbuf_read_file(&d->buf, arg, 1024) < 0)
+	} else if (strbuf_read_file(&msg->buf, arg, 1024) < 0)
 		die_errno(_("could not open or read '%s'"), arg);
-	strbuf_stripspace(&d->buf, 0);
 
-	d->given = 1;
+	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
+	d->messages[d->msg_nr - 1] = msg;
+	msg->stripspace = 1;
 	return 0;
 }
 
 static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)
 {
 	struct note_data *d = opt->value;
-	char *buf;
+	struct note_msg *msg = xmalloc(sizeof(*msg));
+	char *value;
 	struct object_id object;
 	enum object_type type;
 	unsigned long len;
 
 	BUG_ON_OPT_NEG(unset);
 
-	if (d->buf.len)
-		strbuf_addch(&d->buf, '\n');
-
+	strbuf_init(&msg->buf, 0);
 	if (repo_get_oid(the_repository, arg, &object))
 		die(_("failed to resolve '%s' as a valid ref."), arg);
-	if (!(buf = repo_read_object_file(the_repository, &object, &type, &len)))
+	if (!(value = repo_read_object_file(the_repository, &object, &type, &len)))
 		die(_("failed to read object '%s'."), arg);
 	if (type != OBJ_BLOB) {
-		free(buf);
+		strbuf_release(&msg->buf);
+		free(value);
+		free(msg);
 		die(_("cannot read note data from non-blob object '%s'."), arg);
 	}
-	strbuf_add(&d->buf, buf, len);
-	free(buf);
 
-	d->given = 1;
+	strbuf_add(&msg->buf, value, len);
+	free(value);
+
+	msg->buf.len = len;
+	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
+	d->messages[d->msg_nr - 1] = msg;
+	msg->stripspace = 0;
 	return 0;
 }
 
@@ -283,6 +332,16 @@ static int parse_reedit_arg(const struct option *opt, const char *arg, int unset
 	return parse_reuse_arg(opt, arg, unset);
 }
 
+static int parse_separator_arg(const struct option *opt, const char *arg,
+			       int unset)
+{
+	if (unset)
+		*(const char **)opt->value = NULL;
+	else
+		*(const char **)opt->value = arg ? arg : "\n";
+	return 0;
+}
+
 static int notes_copy_from_stdin(int force, const char *rewrite_cmd)
 {
 	struct strbuf buf = STRBUF_INIT;
@@ -406,6 +465,7 @@ static int add(int argc, const char **argv, const char *prefix)
 	struct object_id object, new_note;
 	const struct object_id *note;
 	struct note_data d = { .buf = STRBUF_INIT };
+
 	struct option options[] = {
 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
 			N_("note contents as a string"), PARSE_OPT_NONEG,
@@ -422,6 +482,10 @@ static int add(int argc, const char **argv, const char *prefix)
 		OPT_BOOL(0, "allow-empty", &allow_empty,
 			N_("allow storing empty note")),
 		OPT__FORCE(&force, N_("replace existing notes"), PARSE_OPT_NOCOMPLETE),
+		OPT_CALLBACK_F(0, "separator", &separator,
+			N_("<paragraph-break>"),
+			N_("insert <paragraph-break> between paragraphs"),
+			PARSE_OPT_OPTARG, parse_separator_arg),
 		OPT_END()
 	};
 
@@ -433,6 +497,10 @@ static int add(int argc, const char **argv, const char *prefix)
 		usage_with_options(git_notes_add_usage, options);
 	}
 
+	if (d.msg_nr)
+		concat_messages(&d);
+	d.given = !!d.buf.len;
+
 	object_ref = argc > 1 ? argv[1] : "HEAD";
 
 	if (repo_get_oid(the_repository, object_ref, &object))
@@ -587,6 +655,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 			parse_reuse_arg),
 		OPT_BOOL(0, "allow-empty", &allow_empty,
 			N_("allow storing empty note")),
+		OPT_CALLBACK_F(0, "separator", &separator,
+			N_("<paragraph-break>"),
+			N_("insert <paragraph-break> between paragraphs"),
+			PARSE_OPT_OPTARG, parse_separator_arg),
 		OPT_END()
 	};
 	int edit = !strcmp(argv[0], "edit");
@@ -600,6 +672,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		usage_with_options(usage, options);
 	}
 
+	if (d.msg_nr)
+		concat_messages(&d);
+	d.given = !!d.buf.len;
+
 	if (d.given && edit)
 		fprintf(stderr, _("The -m/-F/-c/-C options have been deprecated "
 			"for the 'edit' subcommand.\n"
@@ -623,7 +699,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 						       &type, &size);
 
 		if (d.buf.len && prev_buf && size)
-			strbuf_insertstr(&d.buf, 0, "\n");
+			insert_separator(&d.buf, 0);
 		if (prev_buf && size)
 			strbuf_insert(&d.buf, 0, prev_buf, size);
 		free(prev_buf);
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 3288aaec..d734000d 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -362,6 +362,7 @@ test_expect_success 'do not create empty note with -m ""' '
 '
 
 test_expect_success 'create note with combination of -m and -F' '
+	test_when_finished git notes remove HEAD &&
 	cat >expect-combine_m_and_F <<-EOF &&
 		foo
 
@@ -380,6 +381,41 @@ test_expect_success 'create note with combination of -m and -F' '
 	test_cmp expect-combine_m_and_F actual
 '
 
+test_expect_success 'create note with combination of -m and -F and --separator' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect-combine_m_and_F <<-\EOF &&
+	foo
+	-------
+	xyzzy
+	-------
+	bar
+	-------
+	zyxxy
+	-------
+	baz
+	EOF
+	echo "xyzzy" >note_a &&
+	echo "zyxxy" >note_b &&
+	git notes add -m "foo" -F note_a -m "bar" -F note_b -m "baz" --separator="-------" &&
+	git notes show >actual &&
+	test_cmp expect-combine_m_and_F actual
+'
+
+test_expect_success 'create note with combination of -m and -F and --no-separator' '
+	cat >expect-combine_m_and_F <<-\EOF &&
+	foo
+	xyzzy
+	bar
+	zyxxy
+	baz
+	EOF
+	echo "xyzzy" >note_a &&
+	echo "zyxxy" >note_b &&
+	git notes add -m "foo" -F note_a -m "bar" -F note_b -m "baz" --no-separator &&
+	git notes show >actual &&
+	test_cmp expect-combine_m_and_F actual
+'
+
 test_expect_success 'remove note with "git notes remove"' '
 	git notes remove HEAD^ &&
 	git notes remove &&
@@ -521,6 +557,112 @@ test_expect_success 'listing non-existing notes fails' '
 	test_must_be_empty actual
 '
 
+test_expect_success 'append: specify a separator with an empty arg' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect <<-\EOF &&
+	notes-1
+
+	notes-2
+	EOF
+
+	git notes add -m "notes-1" &&
+	git notes append --separator="" -m "notes-2" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append: specify a separator without arg' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect <<-\EOF &&
+	notes-1
+
+	notes-2
+	EOF
+
+	git notes add -m "notes-1" &&
+	git notes append --separator -m "notes-2" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append: specify as --no-separator' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect <<-\EOF &&
+	notes-1
+	notes-2
+	EOF
+
+	git notes add -m "notes-1" &&
+	git notes append --no-separator -m "notes-2" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append: specify separator with line break' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect <<-\EOF &&
+	notes-1
+	-------
+	notes-2
+	EOF
+
+	git notes add -m "notes-1" &&
+	git notes append --separator="-------$LF" -m "notes-2" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append: specify separator without line break' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect <<-\EOF &&
+	notes-1
+	-------
+	notes-2
+	EOF
+
+	git notes add -m "notes-1" &&
+	git notes append --separator="-------" -m "notes-2" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append: specify separator with multiple messages' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect <<-\EOF &&
+	notes-1
+	-------
+	notes-2
+	-------
+	notes-3
+	EOF
+
+	git notes add -m "notes-1" &&
+	git notes append --separator="-------" -m "notes-2" -m "notes-3" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append note with combination of -m and -F and --separator' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect-combine_m_and_F <<-\EOF &&
+	m-notes-1
+	-------
+	f-notes-1
+	-------
+	m-notes-2
+	-------
+	f-notes-2
+	-------
+	m-notes-3
+	EOF
+
+	echo "f-notes-1" >note_a &&
+	echo "f-notes-2" >note_b &&
+	git notes append -m "m-notes-1" -F note_a -m "m-notes-2" -F note_b -m "m-notes-3" --separator="-------" &&
+	git notes show >actual &&
+	test_cmp expect-combine_m_and_F actual
+'
+
 test_expect_success 'append to existing note with "git notes append"' '
 	cat >expect <<-EOF &&
 		Initial set of notes
@@ -818,6 +960,33 @@ test_expect_success 'create note from blob with "git notes add -C" reuses blob i
 	test_cmp blob actual
 '
 
+test_expect_success 'create note from blob with "-C", also specify "-m", "-F" and "--separator"' '
+	# 8th will be reuseed in following tests, so rollback when the test is done
+	test_when_finished "git notes remove && git notes add -C $(cat blob)" &&
+	commit=$(git rev-parse HEAD) &&
+	cat >expect <<-EOF &&
+		commit $commit
+		Author: A U Thor <author@example.com>
+		Date:   Thu Apr 7 15:20:13 2005 -0700
+
+		${indent}8th
+
+		Notes:
+		${indent}This is a blob object
+		${indent}-------
+		${indent}This is created by -m
+		${indent}-------
+		${indent}This is created by -F
+	EOF
+
+	git notes remove &&
+	echo "This is a blob object" | git hash-object -w --stdin >blob &&
+	echo "This is created by -F" >note_a &&
+	git notes add -C $(cat blob) -m "This is created by -m" -F note_a --separator="-------" &&
+	git log -1 >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'create note from other note with "git notes add -c"' '
 	test_commit 9th &&
 	commit=$(git rev-parse HEAD) &&
-- 
2.40.0.356.g67a1c1d0
Kristoffer Haugsbakk· May 18, 2023, 14:34 UTC · re: Teng Long · lore

Re: [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option

Hi
On Thu, May 18, 2023, at 14:02, Teng Long wrote:
> From: Teng Long <dyroneteng@gmail.com>
>
> When adding new notes or appending to an existing notes, we will
> insert a blank line between the paragraphs, like:

I was wondering why it acts this way. So of minor historical note (may be obvious to everyone else): it was in order to mirror how `git commit -m` works. From commit d9246d4303f (Teach "-m <msg>" and "-F <file>" to "git notes edit", 2009-10-09):

Show 10 quoted lines
> Teach "-m <msg>" and "-F <file>" to "git notes edit"
>
> The "-m" and "-F" options are already the established method
> (in both git-commit and git-tag) to specify a commit/tag message
> without invoking the editor. This patch teaches "git notes edit"
> to respect the same options for specifying a notes message without
> invoking the editor.
>
> Multiple "-m" and/or "-F" options are concatenated as separate
> paragraphs.
-- 
Kristoffer Haugsbakk
Teng Long· May 20, 2023, 10:41 UTC · re: Kristoffer Haugsbakk · lore

Re: [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option

"Kristoffer Haugsbakk" <code@khaugsbakk.name> writes:
Show 9 quoted lines
> > From: Teng Long <dyroneteng@gmail.com>
> >
> > When adding new notes or appending to an existing notes, we will
> > insert a blank line between the paragraphs, like:
> 
> I was wondering why it acts this way. So of minor historical note (may
> be obvious to everyone else): it was in order to mirror how `git commit
> -m` works. From commit d9246d4303f (Teach "-m <msg>" and "-F <file>" to
> "git notes edit", 2009-10-09):

Yes, I noticed it too, but I'm not sure whether there exists the same requirement in `git commit -m <message> -F <file> ...`, `git commit` is usually a more frequently used subcommand, firstly introduced it to `git notes` maybe is a good idea, but I'm not going to touch `git commit` yet, unless we find someone who has a strong need.

Thanks.
Kristoffer Haugsbakk· May 20, 2023, 16:12 UTC · re: Teng Long · lore

Re: [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option

Hi Teng Long
On Sat, May 20, 2023, at 12:41, Teng Long wrote:
Show 5 quoted lines
> Yes, I noticed it too, but I'm not sure whether there exists the same
> requirement in `git commit -m <message> -F <file> ...`, `git commit` is
> usually a more frequently used subcommand, firstly introduced it to
> `git notes` maybe is a good idea, but I'm not going to touch `git commit`
> yet, unless we find someone who has a strong need.

Yep, you’re correct. My intention wasn’t to imply that git-commit(1) needed something like this, rather to explain (mostly to myself) why git-notes(1) learned a sort of git-commit(1) option in the first place.

`git commit -m` with its multiple paragraphs behavior makes perfect sense, since you are writing paragraphs of text, anyway. However, Git notes are used for other things than that—like line-delimited data—so a `--[no-]-separator[=]` option makes great sense for git-notes(1) and complements it well.

I wanted to figure out why `git notes append -m` works like it does since I’ve been idly wondering about it, and I have seen others around the Web who have been a little puzzled as well.

Cheers
-- 
Kristoffer Haugsbakk
Jeff King· May 19, 2023, 00:54 UTC · re: Teng Long · lore

Re: [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option

On Thu, May 18, 2023 at 08:02:09PM +0800, Teng Long wrote:
Show 9 quoted lines
> +static void insert_separator(struct strbuf *message, size_t pos)
> +{
> +	if (!separator)
> +		return;
> +	else if (separator[strlen(separator) - 1] == '\n')
> +		strbuf_insertstr(message, pos, separator);
> +	else
> +		strbuf_insertf(message, pos, "%s%s", separator, "\n");
> +}

This function causes UBSan to complain on 'next' (though curiously only with clang, not with gcc[1]). The version in next seems to be from your v9, but it's largely the same except for the "if (!separator)" condition.

The problem is in the middle condition here. If "separator" is non-NULL, but is an empty string, then strlen() will return 0, and we will look at the out-of-bounds byte just before the string.

We'd probably want something like this:
Show changes to builtin/notes.c +2 −2
diff --git a/builtin/notes.c b/builtin/notes.c
index 3215bce19b..a46d6dac5c 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -231,7 +231,8 @@ static void write_note_data(struct note_data *d, struct object_id *oid)
 
 static void insert_separator(struct strbuf *message, size_t pos)
 {
-	if (separator[strlen(separator) - 1] == '\n')
+	size_t sep_len = strlen(separator);
+	if (sep_len && separator[sep_len - 1] == '\n')
 		strbuf_addstr(message, separator);
 	else
 		strbuf_insertf(message, pos, "%s%s", separator, "\n");

to fix it, though I am not 100% clear on what is supposed to happen for
an empty separator here.

I was also confused that applying the fix on top of the culprit in
'next', 3993a53a13 (notes.c: introduce '--separator=<paragraph-break>'
option, 2023-04-28), still leads to test failures in t3301. But I think
that is independent of this fix. It fails even without my patch above
(and without UBSan) in test 66, "append: specify separator with line
break". But the failure goes away in the following patch, ad3d1f8feb
(notes.c: append separator instead of insert by pos, 2023-04-28).

I haven't been following this series enough to know what's going on, but
you may want to figure out where the failure is coming from in
3993a53a13. If the change in ad3d1f8feb is merely papering over it, then
we'd need to find and fix the true cause. If the bug is really fixed by
ad3d1f8feb, we might want to squash those two together to avoid broken
bisections.

-Peff

[1] To reproduce, I did:

      git checkout 3993a53a13
      make SANITIZE=address,undefined CC=clang
      cd t && ./t3301-notes.sh -v -i

    I'm using clang-14 on a Debian machine.
Teng Long· May 27, 2023, 07:17 UTC · re: Jeff King · lore

Re: [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option

Jeff King <peff@peff.net> writes:
Show 9 quoted lines
> > +static void insert_separator(struct strbuf *message, size_t pos)
> > +{
> > +	if (!separator)
> > +		return;
> > +	else if (separator[strlen(separator) - 1] == '\n')
> > +		strbuf_insertstr(message, pos, separator);
> > +	else
> > +		strbuf_insertf(message, pos, "%s%s", separator, "\n");
> > +}
Show 8 quoted lines
> This function causes UBSan to complain on 'next' (though curiously only
> with clang, not with gcc[1]). The version in next seems to be from your
> v9, but it's largely the same except for the "if (!separator)"
> condition.
> 
> The problem is in the middle condition here. If "separator" is non-NULL,
> but is an empty string, then strlen() will return 0, and we will look at
> the out-of-bounds byte just before the string.
You definitely correct, will fix.
Show 28 quoted lines
> This function causes UBSan to complain on 'next' (though curiously only
> with clang, not with gcc[1]). The version in next seems to be from your
> v9, but it's largely the same except for the "if (!separator)"
> condition.
> 
> The problem is in the middle condition here. If "separator" is non-NULL,
> but is an empty string, then strlen() will return 0, and we will look at
> the out-of-bounds byte just before the string.
> 
> We'd probably want something like this:
> 
> diff --git a/builtin/notes.c b/builtin/notes.c
> index 3215bce19b..a46d6dac5c 100644
> --- a/builtin/notes.c
> +++ b/builtin/notes.c
> @@ -231,7 +231,8 @@ static void write_note_data(struct note_data *d, struct object_id *oid)
>  
>  static void insert_separator(struct strbuf *message, size_t pos)
>  {
> -	if (separator[strlen(separator) - 1] == '\n')
> +	size_t sep_len = strlen(separator);
> +	if (sep_len && separator[sep_len - 1] == '\n')
>  		strbuf_addstr(message, separator);
>  	else
>  		strbuf_insertf(message, pos, "%s%s", separator, "\n");
> 
> to fix it, though I am not 100% clear on what is supposed to happen for
> an empty separator here.

It's supposed to be the same behaviour with not to specify the option, which is the default behaviour(to use a '\n' as the separator).

The diff looks good to me, will apply.
Show 7 quoted lines
> I was also confused that applying the fix on top of the culprit in
> 'next', 3993a53a13 (notes.c: introduce '--separator=<paragraph-break>'
> option, 2023-04-28), still leads to test failures in t3301. But I think
> that is independent of this fix. It fails even without my patch above
> (and without UBSan) in test 66, "append: specify separator with line
> break". But the failure goes away in the following patch, ad3d1f8feb
> (notes.c: append separator instead of insert by pos, 2023-04-28).

Yes, that's a problem which be taken in patch v9 4/6[1] at insert_separator(...) , we should use strbuf_insert* api here, otherwise will always do append but not to do insert with the position, finally break the test.

In the v9 5/6 patch[2], I tried to remove the postion to simply the logic from insert with position to just append, and this patch cover the test case failure in 4/6.

Show 6 quoted lines
> I haven't been following this series enough to know what's going on, but
> you may want to figure out where the failure is coming from in
> 3993a53a13. If the change in ad3d1f8feb is merely papering over it, then
> we'd need to find and fix the true cause. If the bug is really fixed by
> ad3d1f8feb, we might want to squash those two together to avoid broken
> bisections.
Sure, we should avoid that, will fix.
Show 7 quoted lines
> [1] To reproduce, I did:
> 
>       git checkout 3993a53a13
>       make SANITIZE=address,undefined CC=clang
>       cd t && ./t3301-notes.sh -v -i
> 
>     I'm using clang-14 on a Debian machine.

Do you always do the 'make' with 'SANITIZE=address,undefined', should I follow that approach, may I ask you to give some advices about it?

Thanks.

[1] https://public-inbox.org/git/ed930ef4f795f30792bc14d9c1939484e4976db8.1682671758.git.dyroneteng@gmail.com/ [2] https://public-inbox.org/git/eea2246f44a3adfc4888db93975854448271032b.1682671758.git.dyroneteng@gmail.com/

Jeff King· May 27, 2023, 17:19 UTC · re: Teng Long · lore

Re: [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option

On Sat, May 27, 2023 at 03:17:21PM +0800, Teng Long wrote:
Show 10 quoted lines
> > [1] To reproduce, I did:
> > 
> >       git checkout 3993a53a13
> >       make SANITIZE=address,undefined CC=clang
> >       cd t && ./t3301-notes.sh -v -i
> > 
> >     I'm using clang-14 on a Debian machine.
> 
> Do you always do the 'make' with 'SANITIZE=address,undefined', should I
> follow that approach, may I ask you to give some advices about it?

No, not usually. I usually run it when I am working on a patch that does something tricky with memory handling, and I want extra confidence that I haven't screwed anything up. :)

We do run CI jobs for both ASan and UBSan, as of 1c0962c0c4 (ci: add address and undefined sanitizer tasks, 2022-10-20). But they run with gcc, which doesn't seem to catch this particular case.

IMHO it might be worth switching to clang for those jobs. My vague recollection was that I've seen it catch more things, but now we have a concrete example. I think it might also run faster (for some reason doing both sanitizers at once is slower for gcc, but faster for clang).

-Peff
Teng Long· May 29, 2023, 11:48 UTC · re: Jeff King · lore

Re: [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option

Jeff King <peff@peff.net> writes:
Show 12 quoted lines
> No, not usually. I usually run it when I am working on a patch that does
> something tricky with memory handling, and I want extra confidence that
> I haven't screwed anything up. :)
> 
> We do run CI jobs for both ASan and UBSan, as of 1c0962c0c4 (ci: add
> address and undefined sanitizer tasks, 2022-10-20). But they run with
> gcc, which doesn't seem to catch this particular case.
> 
> IMHO it might be worth switching to clang for those jobs. My vague
> recollection was that I've seen it catch more things, but now we have a
> concrete example. I think it might also run faster (for some reason
> doing both sanitizers at once is slower for gcc, but faster for clang).
Thanks for explaining this.
Teng Long· May 18, 2023, 12:02 UTC · re: Teng Long · lore

[PATCH v10 6/6] notes.c: introduce "--[no-]stripspace" option

From: Teng Long <dyroneteng@gmail.com>

This commit introduces a new option "--[no-]stripspace" to git notes append, git notes edit, and git notes add. This option allows users to control whether the note message need to stripped out.

For the consideration of backward compatibility, let's look at the behavior about "stripspace" in "git notes" command:

1. "Edit Message" case: using the default editor to edit the note
message.
    In "edit" case, the edited message will always be stripped out, the
    implementation which can be found in the "prepare_note_data()". In
    addition, the "-c" option supports to reuse an existing blob as a
    note message, then open the editor to make a further edition on it,
    the edited message will be stripped.
    This commit doesn't change the default behavior of "edit" case by
    using an enum "notes_stripspace", only when "--no-stripspace" option
    is specified, the note message will not be stripped out. If you do
    not specify the option or you specify "--stripspace", clearly, the
    note message will be stripped out.
2. "Assign Message" case: using the "-m"/"-F"/"-C" option to specify the
note message.
    In "assign" case, when specify message by "-m" or "-F", the message
    will be stripped out by default, but when specify message by "-C",
    the message will be copied verbatim, in other word, the message will
    not be stripped out. One more thing need to note is "the order of
    the options matter", that is, if you specify "-C" before "-m" or
    "-F", the reused message by "-C" will be stripped out together,
    because everytime concat "-m" or "-F" message, the concated message
    will be stripped together. Oppositely, if you specify "-m" or "-F"
    before "-C", the reused message by "-C" will not be stripped out.
    This commit doesn't change the default behavior of "assign" case by
    extending the "stripspace" field in "struct note_msg", so we can
    distinguish the different behavior of "-m"/"-F" and "-C" options
    when we need to parse and concat the message.
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-notes.txt |  25 ++-
 builtin/notes.c             |  34 +++--
 t/t3321-notes-stripspace.sh | 296 +++++++++++++++++++++++++++++++++++-
 3 files changed, 334 insertions(+), 21 deletions(-)
Show changes to 3 files +334 −21

Documentation/git-notes.txt, builtin/notes.c, t/t3321-notes-stripspace.sh

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 56d25a79..bc1bfa37 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -9,10 +9,10 @@ SYNOPSIS
 --------
 [verse]
 'git notes' [list [<object>]]
-'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
+'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
 'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )
-'git notes' append [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
-'git notes' edit [--allow-empty] [<object>]
+'git notes' append [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
+'git notes' edit [--allow-empty] [<object>] [--[no-]stripspace]
 'git notes' show [<object>]
 'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>
 'git notes' merge --commit [-v | -q]
@@ -141,20 +141,26 @@ OPTIONS
 	If multiple `-m` options are given, their values
 	are concatenated as separate paragraphs.
 	Lines starting with `#` and empty lines other than a
-	single line between paragraphs will be stripped out.
+	single line between paragraphs will be stripped out,
+	if you wish to keep them verbatim, use `--no-stripspace`.
 
 -F <file>::
 --file=<file>::
 	Take the note message from the given file.  Use '-' to
 	read the note message from the standard input.
 	Lines starting with `#` and empty lines other than a
-	single line between paragraphs will be stripped out.
+	single line between paragraphs will be stripped out,
+	if you wish to keep them verbatim, use with
+	`--no-stripspace` option.
 
 -C <object>::
 --reuse-message=<object>::
 	Take the given blob object (for example, another note) as the
 	note message. (Use `git notes copy <object>` instead to
-	copy notes between objects.)
+	copy notes between objects.).  By default, message will be
+	copied verbatim, but if you wish to strip out the lines
+	starting with `#` and empty lines other than a single line
+	between paragraphs, use with`--stripspace` option.
 
 -c <object>::
 --reedit-message=<object>::
@@ -171,6 +177,13 @@ OPTIONS
 	separators will be added between paragraphs.  Defaults to a blank
 	line.
 
+--[no-]stripspace::
+	Strip leading and trailing whitespace from the note message.
+	Also strip out empty lines other than a single line between
+	paragraphs. For lines starting with `#` will be stripped out
+	in non-editor cases like "-m", "-F" and "-C", but not in
+	editor case like "git notes edit", "-c", etc.
+
 --ref <ref>::
 	Manipulate the notes tree in <ref>.  This overrides
 	`GIT_NOTES_REF` and the "core.notesRef" configuration.  The ref
diff --git a/builtin/notes.c b/builtin/notes.c
index 84c010fe..61e0b227 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -31,9 +31,9 @@
 static const char *separator = "\n";
 static const char * const git_notes_usage[] = {
 	N_("git notes [--ref <notes-ref>] [list [<object>]]"),
-	N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
+	N_("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_("git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>"),
-	N_("git notes [--ref <notes-ref>] append [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
+	N_("git notes [--ref <notes-ref>] append [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [--[no-]stripspace] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
 	N_("git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]"),
 	N_("git notes [--ref <notes-ref>] show [<object>]"),
 	N_("git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>"),
@@ -101,14 +101,21 @@ static const char * const git_notes_get_ref_usage[] = {
 static const char note_template[] =
 	N_("Write/edit the notes for the following object:");
 
+enum notes_stripspace {
+	UNSPECIFIED = -1,
+	NO_STRIPSPACE = 0,
+	STRIPSPACE = 1,
+};
+
 struct note_msg {
-	int stripspace;
+	enum notes_stripspace stripspace;
 	struct strbuf buf;
 };
 
 struct note_data {
 	int given;
 	int use_editor;
+	int stripspace;
 	char *edit_path;
 	struct strbuf buf;
 	struct note_msg **messages;
@@ -213,7 +220,8 @@ static void prepare_note_data(const struct object_id *object, struct note_data *
 		if (launch_editor(d->edit_path, &d->buf, NULL)) {
 			die(_("please supply the note contents using either -m or -F option"));
 		}
-		strbuf_stripspace(&d->buf, 1);
+		if (d->stripspace)
+			strbuf_stripspace(&d->buf, 1);
 	}
 }
 
@@ -249,7 +257,9 @@ static void concat_messages(struct note_data *d)
 			append_separator(&d->buf);
 		strbuf_add(&msg, d->messages[i]->buf.buf, d->messages[i]->buf.len);
 		strbuf_addbuf(&d->buf, &msg);
-		if (d->messages[i]->stripspace)
+		if ((d->stripspace == UNSPECIFIED &&
+		     d->messages[i]->stripspace == STRIPSPACE) ||
+		    d->stripspace == STRIPSPACE)
 			strbuf_stripspace(&d->buf, 0);
 		strbuf_reset(&msg);
 	}
@@ -267,7 +277,7 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
 	strbuf_addstr(&msg->buf, arg);
 	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
 	d->messages[d->msg_nr - 1] = msg;
-	msg->stripspace = 1;
+	msg->stripspace = STRIPSPACE;
 	return 0;
 }
 
@@ -287,7 +297,7 @@ static int parse_file_arg(const struct option *opt, const char *arg, int unset)
 
 	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
 	d->messages[d->msg_nr - 1] = msg;
-	msg->stripspace = 1;
+	msg->stripspace = STRIPSPACE;
 	return 0;
 }
 
@@ -320,7 +330,7 @@ static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)
 	msg->buf.len = len;
 	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
 	d->messages[d->msg_nr - 1] = msg;
-	msg->stripspace = 0;
+	msg->stripspace = NO_STRIPSPACE;
 	return 0;
 }
 
@@ -464,7 +474,7 @@ static int add(int argc, const char **argv, const char *prefix)
 	struct notes_tree *t;
 	struct object_id object, new_note;
 	const struct object_id *note;
-	struct note_data d = { .buf = STRBUF_INIT };
+	struct note_data d = { .buf = STRBUF_INIT, .stripspace = UNSPECIFIED };
 
 	struct option options[] = {
 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
@@ -486,6 +496,8 @@ static int add(int argc, const char **argv, const char *prefix)
 			N_("<paragraph-break>"),
 			N_("insert <paragraph-break> between paragraphs"),
 			PARSE_OPT_OPTARG, parse_separator_arg),
+		OPT_BOOL(0, "stripspace", &d.stripspace,
+			N_("remove unnecessary whitespace")),
 		OPT_END()
 	};
 
@@ -639,7 +651,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 	const struct object_id *note;
 	char *logmsg;
 	const char * const *usage;
-	struct note_data d = { .buf = STRBUF_INIT };
+	struct note_data d = { .buf = STRBUF_INIT, .stripspace = UNSPECIFIED };
 	struct option options[] = {
 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
 			N_("note contents as a string"), PARSE_OPT_NONEG,
@@ -659,6 +671,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 			N_("<paragraph-break>"),
 			N_("insert <paragraph-break> between paragraphs"),
 			PARSE_OPT_OPTARG, parse_separator_arg),
+		OPT_BOOL(0, "stripspace", &d.stripspace,
+			N_("remove unnecessary whitespace")),
 		OPT_END()
 	};
 	int edit = !strcmp(argv[0], "edit");
diff --git a/t/t3321-notes-stripspace.sh b/t/t3321-notes-stripspace.sh
index 89977873..028d825e 100755
--- a/t/t3321-notes-stripspace.sh
+++ b/t/t3321-notes-stripspace.sh
@@ -32,7 +32,7 @@ test_expect_success 'add note by editor' '
 	test_cmp expect actual
 '
 
-test_expect_success 'add note by specifying single "-m"' '
+test_expect_success 'add note by specifying single "-m", "--stripspace" is the default behavior' '
 	test_when_finished "git notes remove" &&
 	cat >expect <<-EOF &&
 	first-line
@@ -42,10 +42,26 @@ test_expect_success 'add note by specifying single "-m"' '
 
 	git notes add -m "${LF}first-line${MULTI_LF}second-line${LF}" &&
 	git notes show >actual &&
+	test_cmp expect actual &&
+	git notes remove &&
+	git notes add --stripspace -m "${LF}first-line${MULTI_LF}second-line${LF}" &&
+	git notes show >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success 'add note by specifying multiple "-m"' '
+test_expect_success 'add note by specifying single "-m" and "--no-stripspace" ' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	${LF}first-line${MULTI_LF}second-line
+	EOF
+
+	git notes add --no-stripspace \
+		      -m "${LF}first-line${MULTI_LF}second-line${LF}" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying multiple "-m", "--stripspace" is the default behavior' '
 	test_when_finished "git notes remove" &&
 	cat >expect <<-EOF &&
 	first-line
@@ -59,9 +75,156 @@ test_expect_success 'add note by specifying multiple "-m"' '
 		      -m "second-line" \
 		      -m "${LF}" &&
 	git notes show >actual &&
+	test_cmp expect actual &&
+	git notes remove &&
+	git notes add --stripspace -m "${LF}" \
+		      -m "first-line" \
+		      -m "${MULTI_LF}" \
+		      -m "second-line" \
+		      -m "${LF}" &&
+	git notes show >actual &&
 	test_cmp expect actual
 '
 
+test_expect_success 'add notes by specifying multiple "-m" and "--no-stripspace"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line${LF}
+	EOF
+
+	git notes add --no-stripspace \
+		      -m "${LF}" \
+		      -m "first-line" \
+		      -m "${MULTI_LF}" \
+		      -m "second-line" \
+		      -m "${LF}" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying single "-F", "--stripspace" is the default behavior' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	cat >note-file <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	git notes add -F note-file &&
+	git notes show >actual &&
+	test_cmp expect actual &&
+	git notes remove &&
+	git notes add --stripspace -F note-file &&
+	git notes show >actual
+'
+
+test_expect_success 'add note by specifying single "-F" and "--no-stripspace"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	cat >note-file <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	git notes add --no-stripspace -F note-file &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying multiple "-F", "--stripspace" is the default behavior' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	file-1-first-line
+
+	file-1-second-line
+
+	file-2-first-line
+
+	file-2-second-line
+	EOF
+
+	cat >note-file-1 <<-EOF &&
+	${LF}
+	file-1-first-line
+	${MULTI_LF}
+	file-1-second-line
+	${LF}
+	EOF
+
+	cat >note-file-2 <<-EOF &&
+	${LF}
+	file-2-first-line
+	${MULTI_LF}
+	file-2-second-line
+	${LF}
+	EOF
+
+	git notes add -F note-file-1 -F note-file-2 &&
+	git notes show >actual &&
+	test_cmp expect actual &&
+	git notes remove &&
+	git notes add --stripspace -F note-file-1 -F note-file-2 &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying multiple "-F" with "--no-stripspace"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	${LF}
+	file-1-first-line
+	${MULTI_LF}
+	file-1-second-line
+	${LF}
+
+	${LF}
+	file-2-first-line
+	${MULTI_LF}
+	file-2-second-line
+	${LF}
+	EOF
+
+	cat >note-file-1 <<-EOF &&
+	${LF}
+	file-1-first-line
+	${MULTI_LF}
+	file-1-second-line
+	${LF}
+	EOF
+
+	cat >note-file-2 <<-EOF &&
+	${LF}
+	file-2-first-line
+	${MULTI_LF}
+	file-2-second-line
+	${LF}
+	EOF
+
+	git notes add --no-stripspace -F note-file-1 -F note-file-2 &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
 
 test_expect_success 'append note by editor' '
 	test_when_finished "git notes remove" &&
@@ -221,6 +384,45 @@ test_expect_success 'append notes by specifying multiple "-F"' '
 	test_cmp expect actual
 '
 
+test_expect_success 'append note by specifying multiple "-F" with "--no-stripspace"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	initial-line
+	${LF}${LF}
+	file-1-first-line
+	${MULTI_LF}
+	file-1-second-line
+	${LF}
+
+	${LF}
+	file-2-first-line
+	${MULTI_LF}
+	file-2-second-line
+	${LF}
+	EOF
+
+	cat >note-file-1 <<-EOF &&
+	${LF}
+	file-1-first-line
+	${MULTI_LF}
+	file-1-second-line
+	${LF}
+	EOF
+
+	cat >note-file-2 <<-EOF &&
+	${LF}
+	file-2-first-line
+	${MULTI_LF}
+	file-2-second-line
+	${LF}
+	EOF
+
+	git notes add -m "initial-line" &&
+	git notes append --no-stripspace -F note-file-1 -F note-file-2 &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'add notes with empty messages' '
 	rev=$(git rev-parse HEAD) &&
 	git notes add -m "${LF}" \
@@ -229,7 +431,7 @@ test_expect_success 'add notes with empty messages' '
 	test_i18ngrep "Removing note for object" actual
 '
 
-test_expect_success 'add note by specifying "-C" , do not stripspace is the default behavior' '
+test_expect_success 'add note by specifying "-C", "--no-stripspace" is the default behavior' '
 	test_when_finished "git notes remove" &&
 	cat >expect <<-EOF &&
 	${LF}
@@ -242,10 +444,36 @@ test_expect_success 'add note by specifying "-C" , do not stripspace is the defa
 	cat expect | git hash-object -w --stdin >blob &&
 	git notes add -C $(cat blob) &&
 	git notes show >actual &&
+	test_cmp expect actual &&
+	git notes remove &&
+	git notes add --no-stripspace -C $(cat blob) &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'reuse note by specifying "-C" and "--stripspace"' '
+	test_when_finished "git notes remove" &&
+	cat >data <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	cat data | git hash-object -w --stdin >blob &&
+	git notes add --stripspace -C $(cat blob) &&
+	git notes show >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success 'add notes with "-C" and "-m", "-m" will stripspace all together' '
+test_expect_success 'reuse with "-C" and add note with "-m", "-m" will stripspace all together' '
 	test_when_finished "git notes remove" &&
 	cat >data <<-EOF &&
 	${LF}
@@ -269,7 +497,7 @@ test_expect_success 'add notes with "-C" and "-m", "-m" will stripspace all toge
 	test_cmp expect actual
 '
 
-test_expect_success 'add notes with "-m" and "-C", "-C" will not stripspace all together' '
+test_expect_success 'add note with "-m" and reuse note with "-C", "-C" will not stripspace all together' '
 	test_when_finished "git notes remove" &&
 	cat >data <<-EOF &&
 
@@ -288,4 +516,62 @@ test_expect_success 'add notes with "-m" and "-C", "-C" will not stripspace all
 	test_cmp expect actual
 '
 
+test_expect_success 'add note by specifying "-c", "--stripspace" is the default behavior' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	echo "initial-line" | git hash-object -w --stdin >blob &&
+	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes add -c $(cat blob) &&
+	git notes show >actual &&
+	test_cmp expect actual &&
+	git notes remove &&
+	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes add --stripspace -c $(cat blob) &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying "-c" with "--no-stripspace"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	${LF}first-line${MULTI_LF}second-line${LF}
+	EOF
+
+	echo "initial-line" | git hash-object -w --stdin >blob &&
+	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes add --no-stripspace -c $(cat blob) &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'edit note by specifying "-c", "--stripspace" is the default behavior' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes edit &&
+	git notes show >actual &&
+	test_cmp expect actual &&
+	git notes remove &&
+	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes edit --stripspace &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'edit note by specifying "-c" with "--no-stripspace"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	${LF}first-line${MULTI_LF}second-line${LF}
+	EOF
+
+	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes add --no-stripspace &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
 test_done
-- 
2.40.0.356.g67a1c1d0
Kristoffer Haugsbakk· May 18, 2023, 13:56 UTC · re: Teng Long · lore

Re: [PATCH v10 0/6] notes.c: introduce "--separator" option

On Thu, May 18, 2023, at 14:02, Teng Long wrote:
Show 5 quoted lines
> From: Teng Long <dyroneteng@gmail.com>
>
> Diff since v9:
>
> 1. [4/6] support `--no-separator` which means not to add any paragraph-breaks.
Nice to see. :) I think this will be useful. Specifically:
Johan Herland wrote (2010):[1]
Show 10 quoted lines
> BTW, since I started talking about git notes, people on this list have found
> more and more interesting use cases for them:
>
> […]
>
> - Help in bug tracking with header-like lines such as:
>     - Causes-Bug: #12345
>     - Fixes-Bug: #54321
>
> - Store after-the-fact "Acked-By", "Reviewed-By", etc. annotations

This switch is nice for that since you you don’t really want blank lines for those kinds of format.

Cheers
[1]: https://lore.kernel.org/git/201001201148.11701.johan@herland.net/
-- 
Kristoffer Haugsbakk
Teng Long· May 20, 2023, 10:22 UTC · re: Kristoffer Haugsbakk · lore

Re: [PATCH v10 0/6] notes.c: introduce "--separator" option

"Kristoffer Haugsbakk" <code@khaugsbakk.name> writes:

#On Thu, May 18, 2023, at 14:02, Teng Long wrote: #> From: Teng Long <dyroneteng@gmail.com> #> #> Diff since v9: #> #> 1. [4/6] support `--no-separator` which means not to add any paragraph-breaks. # #Nice to see. :) I think this will be useful. Specifically: # #Johan Herland wrote (2010):[1] #> BTW, since I started talking about git notes, people on this list have found #> more and more interesting use cases for them: #> #> […] #> #> - Help in bug tracking with header-like lines such as: #> - Causes-Bug: #12345 #> - Fixes-Bug: #54321 #> #> - Store after-the-fact "Acked-By", "Reviewed-By", etc. annotations # #This switch is nice for that since you you don’t really want #blank lines for those kinds of format. # #Cheers # #[1]: https://lore.kernel.org/git/201001201148.11701.johan@herland.net/

Thanks. It's because of you that this function was incubated and possibly born.

Junio C Hamano· May 18, 2023, 15:17 UTC · re: Teng Long · lore

Re: [PATCH v10 0/6] notes.c: introduce "--separator" option

Teng Long <dyroneteng@gmail.com> writes:
Show 8 quoted lines
> From: Teng Long <dyroneteng@gmail.com>
>
> Diff since v9:
>
> 1. [4/6] support `--no-separator` which means not to add any paragraph-breaks.
> 2. [4/6] Fix the problems by the Junio's suggestion [1]
>
> [1] https://public-inbox.org/git/xmqqsfcjbuud.fsf@gitster.g/

Thanks for an update, but it was a bit unexpected to see a full reroll instead of a new patch to add --no-separator support on top of what has already been queued in 'next' for the past 10+ days.

Teng Long· May 20, 2023, 10:59 UTC · re: Junio C Hamano · lore

Re: [PATCH v10 0/6] notes.c: introduce "--separator" option

Junio C Hamano <gitster@pobox.com> writes:
Show 12 quoted lines
> > From: Teng Long <dyroneteng@gmail.com>
> >
> > Diff since v9:
> >
> > 1. [4/6] support `--no-separator` which means not to add any paragraph-breaks.
> > 2. [4/6] Fix the problems by the Junio's suggestion [1]
> >
> > [1] https://public-inbox.org/git/xmqqsfcjbuud.fsf@gitster.g/
> 
> Thanks for an update, but it was a bit unexpected to see a full
> reroll instead of a new patch to add --no-separator support on top
> of what has already been queued in 'next' for the past 10+ days.

Sorry, so if the topic already queued in 'next', make new patch base on it is preferred, if not queued yet, wo should work on keeping commit be atomic on it's duty.

Thanks.
Teng Long· May 27, 2023, 07:57 UTC · re: Teng Long · lore

[PATCH v11 0/7] notes.c: introduce "--separator"

From: Teng Long <dyroneteng@gmail.com>
1.  "--[no-]separator" to an independent patch which suggested by Junio[1].
2.  fix "UBSan" problem which found by Peff[2].

[1] https://public-inbox.org/git/xmqqfs7tvfc7.fsf@gitster.g/ [2] https://public-inbox.org/git/20230519005447.GA2955320@coredump.intra.peff.net/

Thanks.
Teng Long (7):
  notes.c: cleanup 'strbuf_grow' call in 'append_edit'
  notes.c: use designated initializers for clarity
  t3321: add test cases about the notes stripspace behavior
  notes.c: introduce '--separator=<paragraph-break>' option
  notes.c: append separator instead of insert by pos
  notes.c: introduce "--[no-]stripspace" option
  notes: introduce "--no-separator" option
 Documentation/git-notes.txt |  43 ++-
 builtin/notes.c             | 159 +++++++---
 t/t3301-notes.sh            | 169 +++++++++++
 t/t3321-notes-stripspace.sh | 577 ++++++++++++++++++++++++++++++++++++
 4 files changed, 906 insertions(+), 42 deletions(-)
 create mode 100755 t/t3321-notes-stripspace.sh
Range-diff against v10:
1:  0634434e = 1:  0634434e notes.c: cleanup 'strbuf_grow' call in 'append_edit'
2:  4ad78405 = 2:  4ad78405 notes.c: use designated initializers for clarity
3:  c2fc2091 = 3:  c2fc2091 t3321: add test cases about the notes stripspace behavior
4:  820dda04 ! 4:  367cb1d4 notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option
    @@ Metadata
     Author: Teng Long <dyroneteng@gmail.com>
     
      ## Commit message ##
    -    notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option
    +    notes.c: introduce '--separator=<paragraph-break>' option
     
         When adding new notes or appending to an existing notes, we will
         insert a blank line between the paragraphs, like:
    @@ Commit message
     
         Both the two exections produce the same result.
     
    -    Alternatively, if you do not want any new paragraph
    -    separators, even a newline by default, you can specify
    -    '--no-separator'.
    -
         The reason we use a "strbuf" array to concat but not "string_list", is
         that the binary file content may contain '\0' in the middle, this will
         cause the corrupt result if using a string to save.
    @@ Documentation/git-notes.txt: SYNOPSIS
      [verse]
      'git notes' [list [<object>]]
     -'git notes' add [-f] [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
    -+'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
    ++'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
      'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )
     -'git notes' append [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
    -+'git notes' append [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
    ++'git notes' append [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
      'git notes' edit [--allow-empty] [<object>]
      'git notes' show [<object>]
      'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>
    @@ Documentation/git-notes.txt: OPTIONS
      	Allow an empty note object to be stored. The default behavior is
      	to automatically remove empty notes.
      
    -+--[no-]separator, --separator=<paragraph-break>::
    ++--separator <paragraph-break>::
     +	Specify a string used as a custom inter-paragraph separator
    -+	(a newline is added at the end as needed). If `--no-separator`, no
    -+	separators will be added between paragraphs.  Defaults to a blank
    -+	line.
    ++	(a newline is added at the end as needed).  Defaults to a
    ++	blank line.
     +
      --ref <ref>::
      	Manipulate the notes tree in <ref>.  This overrides
    @@ builtin/notes.c
      #include "worktree.h"
      #include "write-or-die.h"
      
    -+static const char *separator = "\n";
    ++static char *separator = "\n";
      static const char * const git_notes_usage[] = {
      	N_("git notes [--ref <notes-ref>] [list [<object>]]"),
     -	N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
    -+	N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
    ++	N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
      	N_("git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>"),
     -	N_("git notes [--ref <notes-ref>] append [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
    -+	N_("git notes [--ref <notes-ref>] append [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
    ++	N_("git notes [--ref <notes-ref>] append [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
      	N_("git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]"),
      	N_("git notes [--ref <notes-ref>] show [<object>]"),
      	N_("git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>"),
    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_
      
     +static void insert_separator(struct strbuf *message, size_t pos)
     +{
    -+	if (!separator)
    -+		return;
    -+	else if (separator[strlen(separator) - 1] == '\n')
    ++	size_t sep_len = strlen(separator);
    ++	if (sep_len && separator[sep_len - 1] == '\n')
     +		strbuf_insertstr(message, pos, separator);
     +	else
     +		strbuf_insertf(message, pos, "%s%s", separator, "\n");
    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_
     +static void concat_messages(struct note_data *d)
     +{
     +	struct strbuf msg = STRBUF_INIT;
    -+	size_t i;
     +
    ++	size_t i;
     +	for (i = 0; i < d->msg_nr ; i++) {
     +		if (d->buf.len)
     +			insert_separator(&d->buf, d->buf.len);
    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_
      	return 0;
      }
      
    -@@ builtin/notes.c: static int parse_reedit_arg(const struct option *opt, const char *arg, int unset
    - 	return parse_reuse_arg(opt, arg, unset);
    - }
    - 
    -+static int parse_separator_arg(const struct option *opt, const char *arg,
    -+			       int unset)
    -+{
    -+	if (unset)
    -+		*(const char **)opt->value = NULL;
    -+	else
    -+		*(const char **)opt->value = arg ? arg : "\n";
    -+	return 0;
    -+}
    -+
    - static int notes_copy_from_stdin(int force, const char *rewrite_cmd)
    - {
    - 	struct strbuf buf = STRBUF_INIT;
     @@ builtin/notes.c: static int add(int argc, const char **argv, const char *prefix)
      	struct object_id object, new_note;
      	const struct object_id *note;
    @@ builtin/notes.c: static int add(int argc, const char **argv, const char *prefix)
      		OPT_BOOL(0, "allow-empty", &allow_empty,
      			N_("allow storing empty note")),
      		OPT__FORCE(&force, N_("replace existing notes"), PARSE_OPT_NOCOMPLETE),
    -+		OPT_CALLBACK_F(0, "separator", &separator,
    -+			N_("<paragraph-break>"),
    -+			N_("insert <paragraph-break> between paragraphs"),
    -+			PARSE_OPT_OPTARG, parse_separator_arg),
    ++		OPT_STRING(0, "separator", &separator, N_("separator"),
    ++			N_("insert <paragraph-break> between paragraphs")),
      		OPT_END()
      	};
      
    @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char
      			parse_reuse_arg),
      		OPT_BOOL(0, "allow-empty", &allow_empty,
      			N_("allow storing empty note")),
    -+		OPT_CALLBACK_F(0, "separator", &separator,
    -+			N_("<paragraph-break>"),
    -+			N_("insert <paragraph-break> between paragraphs"),
    -+			PARSE_OPT_OPTARG, parse_separator_arg),
    ++		OPT_STRING(0, "separator", &separator, N_("separator"),
    ++			N_("insert <paragraph-break> between paragraphs")),
      		OPT_END()
      	};
      	int edit = !strcmp(argv[0], "edit");
    @@ t/t3301-notes.sh: test_expect_success 'create note with combination of -m and -F
      '
      
     +test_expect_success 'create note with combination of -m and -F and --separator' '
    -+	test_when_finished git notes remove HEAD &&
     +	cat >expect-combine_m_and_F <<-\EOF &&
     +	foo
     +	-------
    @@ t/t3301-notes.sh: test_expect_success 'create note with combination of -m and -F
     +	EOF
     +	echo "xyzzy" >note_a &&
     +	echo "zyxxy" >note_b &&
    -+	git notes add -m "foo" -F note_a -m "bar" -F note_b -m "baz" --separator="-------" &&
    -+	git notes show >actual &&
    -+	test_cmp expect-combine_m_and_F actual
    -+'
    -+
    -+test_expect_success 'create note with combination of -m and -F and --no-separator' '
    -+	cat >expect-combine_m_and_F <<-\EOF &&
    -+	foo
    -+	xyzzy
    -+	bar
    -+	zyxxy
    -+	baz
    -+	EOF
    -+	echo "xyzzy" >note_a &&
    -+	echo "zyxxy" >note_b &&
    -+	git notes add -m "foo" -F note_a -m "bar" -F note_b -m "baz" --no-separator &&
    ++	git notes add -m "foo" -F note_a -m "bar" -F note_b -m "baz" --separator "-------" &&
     +	git notes show >actual &&
     +	test_cmp expect-combine_m_and_F actual
     +'
    @@ t/t3301-notes.sh: test_expect_success 'listing non-existing notes fails' '
      	test_must_be_empty actual
      '
      
    -+test_expect_success 'append: specify a separator with an empty arg' '
    ++test_expect_success 'append: specify an empty separator' '
     +	test_when_finished git notes remove HEAD &&
     +	cat >expect <<-\EOF &&
     +	notes-1
    @@ t/t3301-notes.sh: test_expect_success 'listing non-existing notes fails' '
     +	test_cmp expect actual
     +'
     +
    -+test_expect_success 'append: specify a separator without arg' '
    -+	test_when_finished git notes remove HEAD &&
    -+	cat >expect <<-\EOF &&
    -+	notes-1
    -+
    -+	notes-2
    -+	EOF
    -+
    -+	git notes add -m "notes-1" &&
    -+	git notes append --separator -m "notes-2" &&
    -+	git notes show >actual &&
    -+	test_cmp expect actual
    -+'
    -+
    -+test_expect_success 'append: specify as --no-separator' '
    -+	test_when_finished git notes remove HEAD &&
    -+	cat >expect <<-\EOF &&
    -+	notes-1
    -+	notes-2
    -+	EOF
    -+
    -+	git notes add -m "notes-1" &&
    -+	git notes append --no-separator -m "notes-2" &&
    -+	git notes show >actual &&
    -+	test_cmp expect actual
    -+'
    -+
     +test_expect_success 'append: specify separator with line break' '
     +	test_when_finished git notes remove HEAD &&
     +	cat >expect <<-\EOF &&
    @@ t/t3301-notes.sh: test_expect_success 'listing non-existing notes fails' '
     +
     +	echo "f-notes-1" >note_a &&
     +	echo "f-notes-2" >note_b &&
    -+	git notes append -m "m-notes-1" -F note_a -m "m-notes-2" -F note_b -m "m-notes-3" --separator="-------" &&
    ++	git notes append -m "m-notes-1" -F note_a -m "m-notes-2" -F note_b -m "m-notes-3" --separator "-------" &&
     +	git notes show >actual &&
     +	test_cmp expect-combine_m_and_F actual
     +'
5:  76c93f19 ! 5:  4ba62a48 notes.c: append separator instead of insert by pos
    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_
     -static void insert_separator(struct strbuf *message, size_t pos)
     +static void append_separator(struct strbuf *message)
      {
    - 	if (!separator)
    - 		return;
    - 	else if (separator[strlen(separator) - 1] == '\n')
    + 	size_t sep_len = strlen(separator);
    + 	if (sep_len && separator[sep_len - 1] == '\n')
     -		strbuf_insertstr(message, pos, separator);
     +		strbuf_addstr(message, separator);
      	else
    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_
      
      static void concat_messages(struct note_data *d)
     @@ builtin/notes.c: static void concat_messages(struct note_data *d)
    - 
    + 	size_t i;
      	for (i = 0; i < d->msg_nr ; i++) {
      		if (d->buf.len)
     -			insert_separator(&d->buf, d->buf.len);
6:  d65f067c ! 6:  19865941 notes.c: introduce "--[no-]stripspace" option
    @@ Documentation/git-notes.txt: SYNOPSIS
      --------
      [verse]
      'git notes' [list [<object>]]
    --'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
    -+'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
    +-'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
    ++'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
      'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )
    --'git notes' append [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
    +-'git notes' append [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
     -'git notes' edit [--allow-empty] [<object>]
    -+'git notes' append [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
    ++'git notes' append [--allow-empty] [--separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
     +'git notes' edit [--allow-empty] [<object>] [--[no-]stripspace]
      'git notes' show [<object>]
      'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>
    @@ Documentation/git-notes.txt: OPTIONS
      -c <object>::
      --reedit-message=<object>::
     @@ Documentation/git-notes.txt: OPTIONS
    - 	separators will be added between paragraphs.  Defaults to a blank
    - 	line.
    + 	(a newline is added at the end as needed).  Defaults to a
    + 	blank line.
      
     +--[no-]stripspace::
     +	Strip leading and trailing whitespace from the note message.
    @@ Documentation/git-notes.txt: OPTIONS
      	`GIT_NOTES_REF` and the "core.notesRef" configuration.  The ref
     
      ## builtin/notes.c ##
    -@@
    - static const char *separator = "\n";
    - static const char * const git_notes_usage[] = {
    - 	N_("git notes [--ref <notes-ref>] [list [<object>]]"),
    --	N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
    -+	N_("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_("git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>"),
    --	N_("git notes [--ref <notes-ref>] append [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
    -+	N_("git notes [--ref <notes-ref>] append [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [--[no-]stripspace] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
    - 	N_("git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]"),
    - 	N_("git notes [--ref <notes-ref>] show [<object>]"),
    - 	N_("git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>"),
     @@ builtin/notes.c: static const char * const git_notes_get_ref_usage[] = {
      static const char note_template[] =
      	N_("Write/edit the notes for the following object:");
    @@ builtin/notes.c: static int add(int argc, const char **argv, const char *prefix)
      	struct option options[] = {
      		OPT_CALLBACK_F('m', "message", &d, N_("message"),
     @@ builtin/notes.c: static int add(int argc, const char **argv, const char *prefix)
    - 			N_("<paragraph-break>"),
    - 			N_("insert <paragraph-break> between paragraphs"),
    - 			PARSE_OPT_OPTARG, parse_separator_arg),
    + 		OPT__FORCE(&force, N_("replace existing notes"), PARSE_OPT_NOCOMPLETE),
    + 		OPT_STRING(0, "separator", &separator, N_("separator"),
    + 			N_("insert <paragraph-break> between paragraphs")),
     +		OPT_BOOL(0, "stripspace", &d.stripspace,
     +			N_("remove unnecessary whitespace")),
      		OPT_END()
    @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char
      		OPT_CALLBACK_F('m', "message", &d, N_("message"),
      			N_("note contents as a string"), PARSE_OPT_NONEG,
     @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char *prefix)
    - 			N_("<paragraph-break>"),
    - 			N_("insert <paragraph-break> between paragraphs"),
    - 			PARSE_OPT_OPTARG, parse_separator_arg),
    + 			N_("allow storing empty note")),
    + 		OPT_STRING(0, "separator", &separator, N_("separator"),
    + 			N_("insert <paragraph-break> between paragraphs")),
     +		OPT_BOOL(0, "stripspace", &d.stripspace,
     +			N_("remove unnecessary whitespace")),
      		OPT_END()
-:  -------- > 7:  7105778c notes: introduce "--no-separator" option
-- 
2.40.0.356.g367cb1d4
Teng Long· May 27, 2023, 07:57 UTC · re: Teng Long · lore

[PATCH v11 1/7] notes.c: cleanup 'strbuf_grow' call in 'append_edit'

From: Teng Long <dyroneteng@gmail.com>

Let's cleanup the unnecessary 'strbuf_grow' call in 'append_edit'. This "strbuf_grow(&d.buf, size + 1);" is prepared for insert a blank line if needed, but actually when inserting, "strbuf_insertstr(&d.buf, 0, "\n");" will do the "grow" for us.

348f199b (builtin-notes: Refactor handling of -F option to allow combining -m and -F, 2010-02-13) added these to mimic the code introduced by 2347fae5 (builtin-notes: Add "append" subcommand for appending to note objects, 2010-02-13) that reads in previous note before the message. And the resulting code with explicit sizing is carried to this day.

In the context of reading an existing note in, exact sizing may have made sense, but because the resulting note needs cleansing with stripspace() when appending with this option, such an exact sizing does not buy us all that much in practice.

It may help avoiding overallocation due to ALLOC_GROW() slop, but nobody can feed so many long messages for it to matter from the command line.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Helped-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/notes.c | 2 --
 1 file changed, 2 deletions(-)
Show changes to builtin/notes.c +0 −2
diff --git a/builtin/notes.c b/builtin/notes.c
index 4ff44f1e..c501c6ee 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -219,7 +219,6 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
 
 	BUG_ON_OPT_NEG(unset);
 
-	strbuf_grow(&d->buf, strlen(arg) + 2);
 	if (d->buf.len)
 		strbuf_addch(&d->buf, '\n');
 	strbuf_addstr(&d->buf, arg);
@@ -623,7 +622,6 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		char *prev_buf = repo_read_object_file(the_repository, note,
 						       &type, &size);
 
-		strbuf_grow(&d.buf, size + 1);
 		if (d.buf.len && prev_buf && size)
 			strbuf_insertstr(&d.buf, 0, "\n");
 		if (prev_buf && size)
-- 
2.40.0.356.g367cb1d4
Teng Long· May 27, 2023, 07:57 UTC · re: Teng Long · lore

[PATCH v11 2/7] notes.c: use designated initializers for clarity

From: Teng Long <dyroneteng@gmail.com>

The "struct note_data d = { 0, 0, NULL, STRBUF_INIT };" style could be replaced with designated initializer for clarity.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/notes.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
Show changes to builtin/notes.c +2 −2
diff --git a/builtin/notes.c b/builtin/notes.c
index c501c6ee..9d8ca795 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -405,7 +405,7 @@ static int add(int argc, const char **argv, const char *prefix)
 	struct notes_tree *t;
 	struct object_id object, new_note;
 	const struct object_id *note;
-	struct note_data d = { 0, 0, NULL, STRBUF_INIT };
+	struct note_data d = { .buf = STRBUF_INIT };
 	struct option options[] = {
 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
 			N_("note contents as a string"), PARSE_OPT_NONEG,
@@ -571,7 +571,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 	const struct object_id *note;
 	char *logmsg;
 	const char * const *usage;
-	struct note_data d = { 0, 0, NULL, STRBUF_INIT };
+	struct note_data d = { .buf = STRBUF_INIT };
 	struct option options[] = {
 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
 			N_("note contents as a string"), PARSE_OPT_NONEG,
-- 
2.40.0.356.g367cb1d4
Teng Long· May 27, 2023, 07:57 UTC · re: Teng Long · lore

[PATCH v11 3/7] t3321: add test cases about the notes stripspace behavior

From: Teng Long <dyroneteng@gmail.com>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 t/t3321-notes-stripspace.sh | 291 ++++++++++++++++++++++++++++++++++++
 1 file changed, 291 insertions(+)
 create mode 100755 t/t3321-notes-stripspace.sh
Show changes to t/t3321-notes-stripspace.sh +291 −0
diff --git a/t/t3321-notes-stripspace.sh b/t/t3321-notes-stripspace.sh
new file mode 100755
index 00000000..89977873
--- /dev/null
+++ b/t/t3321-notes-stripspace.sh
@@ -0,0 +1,291 @@
+#!/bin/sh
+#
+# Copyright (c) 2023 Teng Long
+#
+
+test_description='Test commit notes with stripspace behavior'
+
+. ./test-lib.sh
+
+MULTI_LF="$LF$LF$LF"
+write_script fake_editor <<\EOF
+echo "$MSG" >"$1"
+echo "$MSG" >&2
+EOF
+GIT_EDITOR=./fake_editor
+export GIT_EDITOR
+
+test_expect_success 'setup the commit' '
+	test_commit 1st
+'
+
+test_expect_success 'add note by editor' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes add  &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying single "-m"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	git notes add -m "${LF}first-line${MULTI_LF}second-line${LF}" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying multiple "-m"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	git notes add -m "${LF}" \
+		      -m "first-line" \
+		      -m "${MULTI_LF}" \
+		      -m "second-line" \
+		      -m "${LF}" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+
+test_expect_success 'append note by editor' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	git notes add -m "first-line" &&
+	MSG="${MULTI_LF}second-line${LF}" git notes append  &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append note by specifying single "-m"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	git notes add -m "${LF}first-line" &&
+	git notes append -m "${MULTI_LF}second-line${LF}" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append note by specifying multiple "-m"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	git notes add -m "${LF}first-line" &&
+	git notes append -m "${MULTI_LF}" \
+		      -m "second-line" \
+		      -m "${LF}" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying single "-F"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	cat >note-file <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	git notes add -F note-file &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add notes by specifying multiple "-F"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	file-1-first-line
+
+	file-1-second-line
+
+	file-2-first-line
+
+	file-2-second-line
+	EOF
+
+	cat >note-file-1 <<-EOF &&
+	${LF}
+	file-1-first-line
+	${MULTI_LF}
+	file-1-second-line
+	${LF}
+	EOF
+
+	cat >note-file-2 <<-EOF &&
+	${LF}
+	file-2-first-line
+	${MULTI_LF}
+	file-2-second-line
+	${LF}
+	EOF
+
+	git notes add -F note-file-1 -F note-file-2 &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append note by specifying single "-F"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	initial-line
+
+	first-line
+
+	second-line
+	EOF
+
+	cat >note-file <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	git notes add -m "initial-line" &&
+	git notes append -F note-file &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append notes by specifying multiple "-F"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	initial-line
+
+	file-1-first-line
+
+	file-1-second-line
+
+	file-2-first-line
+
+	file-2-second-line
+	EOF
+
+	cat >note-file-1 <<-EOF &&
+	${LF}
+	file-1-first-line
+	${MULTI_LF}
+	file-1-second-line
+	${LF}
+	EOF
+
+	cat >note-file-2 <<-EOF &&
+	${LF}
+	file-2-first-line
+	${MULTI_LF}
+	file-2-second-line
+	${LF}
+	EOF
+
+	git notes add -m "initial-line" &&
+	git notes append -F note-file-1 -F note-file-2 &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add notes with empty messages' '
+	rev=$(git rev-parse HEAD) &&
+	git notes add -m "${LF}" \
+		      -m "${MULTI_LF}" \
+		      -m "${LF}" >actual 2>&1 &&
+	test_i18ngrep "Removing note for object" actual
+'
+
+test_expect_success 'add note by specifying "-C" , do not stripspace is the default behavior' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	cat expect | git hash-object -w --stdin >blob &&
+	git notes add -C $(cat blob) &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add notes with "-C" and "-m", "-m" will stripspace all together' '
+	test_when_finished "git notes remove" &&
+	cat >data <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+
+	third-line
+	EOF
+
+	cat data | git hash-object -w --stdin >blob &&
+	git notes add -C $(cat blob) -m "third-line" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add notes with "-m" and "-C", "-C" will not stripspace all together' '
+	test_when_finished "git notes remove" &&
+	cat >data <<-EOF &&
+
+	second-line
+	EOF
+
+	cat >expect <<-EOF &&
+	first-line
+	${LF}
+	second-line
+	EOF
+
+	cat data | git hash-object -w --stdin >blob &&
+	git notes add -m "first-line" -C $(cat blob)  &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_done
-- 
2.40.0.356.g367cb1d4
Teng Long· May 27, 2023, 07:57 UTC · re: Teng Long · lore

[PATCH v11 4/7] notes.c: introduce '--separator=<paragraph-break>' option

From: Teng Long <dyroneteng@gmail.com>

When adding new notes or appending to an existing notes, we will insert a blank line between the paragraphs, like:

     $ git notes add -m foo -m bar
     $ git notes show HEAD
     foo
     bar

The default behavour sometimes is not enough, the user may want to use a custom delimiter between paragraphs, like when specifying '-m', '-F', '-C', '-c' options. So this commit introduce a new '--separator' option for 'git notes add' and 'git notes append', for example when executing:

    $ git notes add -m foo -m bar --separator="-"
    $ git notes show HEAD
    foo
    -
    bar

a newline is added to the value given to --separator if it does not end with one already. So when executing:

      $ git notes add -m foo -m bar --separator="-"
and
      $ export LF="
      "
      $ git notes add -m foo -m bar --separator="-$LF"
Both the two exections produce the same result.

The reason we use a "strbuf" array to concat but not "string_list", is that the binary file content may contain '\0' in the middle, this will cause the corrupt result if using a string to save.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-notes.txt |  21 ++++--
 builtin/notes.c             | 109 ++++++++++++++++++++++++-------
 t/t3301-notes.sh            | 126 ++++++++++++++++++++++++++++++++++++
 3 files changed, 227 insertions(+), 29 deletions(-)
Show changes to 3 files +227 −29

Documentation/git-notes.txt, builtin/notes.c, t/t3301-notes.sh

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index efbc10f0..59980b21 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -9,9 +9,9 @@ SYNOPSIS
 --------
 [verse]
 'git notes' [list [<object>]]
-'git notes' add [-f] [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
+'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
 'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )
-'git notes' append [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
+'git notes' append [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
 'git notes' edit [--allow-empty] [<object>]
 'git notes' show [<object>]
 'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>
@@ -65,7 +65,9 @@ add::
 	However, if you're using `add` interactively (using an editor
 	to supply the notes contents), then - instead of aborting -
 	the existing notes will be opened in the editor (like the `edit`
-	subcommand).
+	subcommand). If you specify multiple `-m` and `-F`, a blank
+	line will be inserted between the messages. Use the `--separator`
+	option to insert other delimiters.
 
 copy::
 	Copy the notes for the first object onto the second object (defaults to
@@ -85,8 +87,12 @@ corresponding <to-object>.  (The optional `<rest>` is ignored so that
 the command can read the input given to the `post-rewrite` hook.)
 
 append::
-	Append to the notes of an existing object (defaults to HEAD).
-	Creates a new notes object if needed.
+	Append new message(s) given by `-m` or `-F` options to an
+	existing note, or add them as a new note if one does not
+	exist, for the object (defaults to HEAD).  When appending to
+	an existing note, a blank line is added before each new
+	message as an inter-paragraph separator.  The separator can
+	be customized with the `--separator` option.
 
 edit::
 	Edit the notes for a given object (defaults to HEAD).
@@ -159,6 +165,11 @@ OPTIONS
 	Allow an empty note object to be stored. The default behavior is
 	to automatically remove empty notes.
 
+--separator <paragraph-break>::
+	Specify a string used as a custom inter-paragraph separator
+	(a newline is added at the end as needed).  Defaults to a
+	blank line.
+
 --ref <ref>::
 	Manipulate the notes tree in <ref>.  This overrides
 	`GIT_NOTES_REF` and the "core.notesRef" configuration.  The ref
diff --git a/builtin/notes.c b/builtin/notes.c
index 9d8ca795..d8a39fe2 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -8,6 +8,7 @@
  */
 
 #include "cache.h"
+#include "alloc.h"
 #include "config.h"
 #include "builtin.h"
 #include "gettext.h"
@@ -27,11 +28,12 @@
 #include "worktree.h"
 #include "write-or-die.h"
 
+static char *separator = "\n";
 static const char * const git_notes_usage[] = {
 	N_("git notes [--ref <notes-ref>] [list [<object>]]"),
-	N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
+	N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
 	N_("git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>"),
-	N_("git notes [--ref <notes-ref>] append [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
+	N_("git notes [--ref <notes-ref>] append [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
 	N_("git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]"),
 	N_("git notes [--ref <notes-ref>] show [<object>]"),
 	N_("git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>"),
@@ -99,11 +101,19 @@ static const char * const git_notes_get_ref_usage[] = {
 static const char note_template[] =
 	N_("Write/edit the notes for the following object:");
 
+struct note_msg {
+	int stripspace;
+	struct strbuf buf;
+};
+
 struct note_data {
 	int given;
 	int use_editor;
 	char *edit_path;
 	struct strbuf buf;
+	struct note_msg **messages;
+	size_t msg_nr;
+	size_t msg_alloc;
 };
 
 static void free_note_data(struct note_data *d)
@@ -113,6 +123,12 @@ static void free_note_data(struct note_data *d)
 		free(d->edit_path);
 	}
 	strbuf_release(&d->buf);
+
+	while (d->msg_nr--) {
+		strbuf_release(&d->messages[d->msg_nr]->buf);
+		free(d->messages[d->msg_nr]);
+	}
+	free(d->messages);
 }
 
 static int list_each_note(const struct object_id *object_oid,
@@ -213,65 +229,97 @@ static void write_note_data(struct note_data *d, struct object_id *oid)
 	}
 }
 
+static void insert_separator(struct strbuf *message, size_t pos)
+{
+	size_t sep_len = strlen(separator);
+	if (sep_len && separator[sep_len - 1] == '\n')
+		strbuf_insertstr(message, pos, separator);
+	else
+		strbuf_insertf(message, pos, "%s%s", separator, "\n");
+}
+
+static void concat_messages(struct note_data *d)
+{
+	struct strbuf msg = STRBUF_INIT;
+
+	size_t i;
+	for (i = 0; i < d->msg_nr ; i++) {
+		if (d->buf.len)
+			insert_separator(&d->buf, d->buf.len);
+		strbuf_add(&msg, d->messages[i]->buf.buf, d->messages[i]->buf.len);
+		strbuf_addbuf(&d->buf, &msg);
+		if (d->messages[i]->stripspace)
+			strbuf_stripspace(&d->buf, 0);
+		strbuf_reset(&msg);
+	}
+	strbuf_release(&msg);
+}
+
 static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
 {
 	struct note_data *d = opt->value;
+	struct note_msg *msg = xmalloc(sizeof(*msg));
 
 	BUG_ON_OPT_NEG(unset);
 
-	if (d->buf.len)
-		strbuf_addch(&d->buf, '\n');
-	strbuf_addstr(&d->buf, arg);
-	strbuf_stripspace(&d->buf, 0);
-
-	d->given = 1;
+	strbuf_init(&msg->buf, strlen(arg));
+	strbuf_addstr(&msg->buf, arg);
+	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
+	d->messages[d->msg_nr - 1] = msg;
+	msg->stripspace = 1;
 	return 0;
 }
 
 static int parse_file_arg(const struct option *opt, const char *arg, int unset)
 {
 	struct note_data *d = opt->value;
+	struct note_msg *msg = xmalloc(sizeof(*msg));
 
 	BUG_ON_OPT_NEG(unset);
 
-	if (d->buf.len)
-		strbuf_addch(&d->buf, '\n');
+	strbuf_init(&msg->buf , 0);
 	if (!strcmp(arg, "-")) {
-		if (strbuf_read(&d->buf, 0, 1024) < 0)
+		if (strbuf_read(&msg->buf, 0, 1024) < 0)
 			die_errno(_("cannot read '%s'"), arg);
-	} else if (strbuf_read_file(&d->buf, arg, 1024) < 0)
+	} else if (strbuf_read_file(&msg->buf, arg, 1024) < 0)
 		die_errno(_("could not open or read '%s'"), arg);
-	strbuf_stripspace(&d->buf, 0);
 
-	d->given = 1;
+	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
+	d->messages[d->msg_nr - 1] = msg;
+	msg->stripspace = 1;
 	return 0;
 }
 
 static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)
 {
 	struct note_data *d = opt->value;
-	char *buf;
+	struct note_msg *msg = xmalloc(sizeof(*msg));
+	char *value;
 	struct object_id object;
 	enum object_type type;
 	unsigned long len;
 
 	BUG_ON_OPT_NEG(unset);
 
-	if (d->buf.len)
-		strbuf_addch(&d->buf, '\n');
-
+	strbuf_init(&msg->buf, 0);
 	if (repo_get_oid(the_repository, arg, &object))
 		die(_("failed to resolve '%s' as a valid ref."), arg);
-	if (!(buf = repo_read_object_file(the_repository, &object, &type, &len)))
+	if (!(value = repo_read_object_file(the_repository, &object, &type, &len)))
 		die(_("failed to read object '%s'."), arg);
 	if (type != OBJ_BLOB) {
-		free(buf);
+		strbuf_release(&msg->buf);
+		free(value);
+		free(msg);
 		die(_("cannot read note data from non-blob object '%s'."), arg);
 	}
-	strbuf_add(&d->buf, buf, len);
-	free(buf);
 
-	d->given = 1;
+	strbuf_add(&msg->buf, value, len);
+	free(value);
+
+	msg->buf.len = len;
+	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
+	d->messages[d->msg_nr - 1] = msg;
+	msg->stripspace = 0;
 	return 0;
 }
 
@@ -406,6 +454,7 @@ static int add(int argc, const char **argv, const char *prefix)
 	struct object_id object, new_note;
 	const struct object_id *note;
 	struct note_data d = { .buf = STRBUF_INIT };
+
 	struct option options[] = {
 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
 			N_("note contents as a string"), PARSE_OPT_NONEG,
@@ -422,6 +471,8 @@ static int add(int argc, const char **argv, const char *prefix)
 		OPT_BOOL(0, "allow-empty", &allow_empty,
 			N_("allow storing empty note")),
 		OPT__FORCE(&force, N_("replace existing notes"), PARSE_OPT_NOCOMPLETE),
+		OPT_STRING(0, "separator", &separator, N_("separator"),
+			N_("insert <paragraph-break> between paragraphs")),
 		OPT_END()
 	};
 
@@ -433,6 +484,10 @@ static int add(int argc, const char **argv, const char *prefix)
 		usage_with_options(git_notes_add_usage, options);
 	}
 
+	if (d.msg_nr)
+		concat_messages(&d);
+	d.given = !!d.buf.len;
+
 	object_ref = argc > 1 ? argv[1] : "HEAD";
 
 	if (repo_get_oid(the_repository, object_ref, &object))
@@ -587,6 +642,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 			parse_reuse_arg),
 		OPT_BOOL(0, "allow-empty", &allow_empty,
 			N_("allow storing empty note")),
+		OPT_STRING(0, "separator", &separator, N_("separator"),
+			N_("insert <paragraph-break> between paragraphs")),
 		OPT_END()
 	};
 	int edit = !strcmp(argv[0], "edit");
@@ -600,6 +657,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		usage_with_options(usage, options);
 	}
 
+	if (d.msg_nr)
+		concat_messages(&d);
+	d.given = !!d.buf.len;
+
 	if (d.given && edit)
 		fprintf(stderr, _("The -m/-F/-c/-C options have been deprecated "
 			"for the 'edit' subcommand.\n"
@@ -623,7 +684,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 						       &type, &size);
 
 		if (d.buf.len && prev_buf && size)
-			strbuf_insertstr(&d.buf, 0, "\n");
+			insert_separator(&d.buf, 0);
 		if (prev_buf && size)
 			strbuf_insert(&d.buf, 0, prev_buf, size);
 		free(prev_buf);
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 3288aaec..dbadcf13 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -362,6 +362,7 @@ test_expect_success 'do not create empty note with -m ""' '
 '
 
 test_expect_success 'create note with combination of -m and -F' '
+	test_when_finished git notes remove HEAD &&
 	cat >expect-combine_m_and_F <<-EOF &&
 		foo
 
@@ -380,6 +381,25 @@ test_expect_success 'create note with combination of -m and -F' '
 	test_cmp expect-combine_m_and_F actual
 '
 
+test_expect_success 'create note with combination of -m and -F and --separator' '
+	cat >expect-combine_m_and_F <<-\EOF &&
+	foo
+	-------
+	xyzzy
+	-------
+	bar
+	-------
+	zyxxy
+	-------
+	baz
+	EOF
+	echo "xyzzy" >note_a &&
+	echo "zyxxy" >note_b &&
+	git notes add -m "foo" -F note_a -m "bar" -F note_b -m "baz" --separator "-------" &&
+	git notes show >actual &&
+	test_cmp expect-combine_m_and_F actual
+'
+
 test_expect_success 'remove note with "git notes remove"' '
 	git notes remove HEAD^ &&
 	git notes remove &&
@@ -521,6 +541,85 @@ test_expect_success 'listing non-existing notes fails' '
 	test_must_be_empty actual
 '
 
+test_expect_success 'append: specify an empty separator' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect <<-\EOF &&
+	notes-1
+
+	notes-2
+	EOF
+
+	git notes add -m "notes-1" &&
+	git notes append --separator="" -m "notes-2" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append: specify separator with line break' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect <<-\EOF &&
+	notes-1
+	-------
+	notes-2
+	EOF
+
+	git notes add -m "notes-1" &&
+	git notes append --separator="-------$LF" -m "notes-2" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append: specify separator without line break' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect <<-\EOF &&
+	notes-1
+	-------
+	notes-2
+	EOF
+
+	git notes add -m "notes-1" &&
+	git notes append --separator="-------" -m "notes-2" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append: specify separator with multiple messages' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect <<-\EOF &&
+	notes-1
+	-------
+	notes-2
+	-------
+	notes-3
+	EOF
+
+	git notes add -m "notes-1" &&
+	git notes append --separator="-------" -m "notes-2" -m "notes-3" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append note with combination of -m and -F and --separator' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect-combine_m_and_F <<-\EOF &&
+	m-notes-1
+	-------
+	f-notes-1
+	-------
+	m-notes-2
+	-------
+	f-notes-2
+	-------
+	m-notes-3
+	EOF
+
+	echo "f-notes-1" >note_a &&
+	echo "f-notes-2" >note_b &&
+	git notes append -m "m-notes-1" -F note_a -m "m-notes-2" -F note_b -m "m-notes-3" --separator "-------" &&
+	git notes show >actual &&
+	test_cmp expect-combine_m_and_F actual
+'
+
 test_expect_success 'append to existing note with "git notes append"' '
 	cat >expect <<-EOF &&
 		Initial set of notes
@@ -818,6 +917,33 @@ test_expect_success 'create note from blob with "git notes add -C" reuses blob i
 	test_cmp blob actual
 '
 
+test_expect_success 'create note from blob with "-C", also specify "-m", "-F" and "--separator"' '
+	# 8th will be reuseed in following tests, so rollback when the test is done
+	test_when_finished "git notes remove && git notes add -C $(cat blob)" &&
+	commit=$(git rev-parse HEAD) &&
+	cat >expect <<-EOF &&
+		commit $commit
+		Author: A U Thor <author@example.com>
+		Date:   Thu Apr 7 15:20:13 2005 -0700
+
+		${indent}8th
+
+		Notes:
+		${indent}This is a blob object
+		${indent}-------
+		${indent}This is created by -m
+		${indent}-------
+		${indent}This is created by -F
+	EOF
+
+	git notes remove &&
+	echo "This is a blob object" | git hash-object -w --stdin >blob &&
+	echo "This is created by -F" >note_a &&
+	git notes add -C $(cat blob) -m "This is created by -m" -F note_a --separator="-------" &&
+	git log -1 >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'create note from other note with "git notes add -c"' '
 	test_commit 9th &&
 	commit=$(git rev-parse HEAD) &&
-- 
2.40.0.356.g367cb1d4
Teng Long· May 27, 2023, 07:57 UTC · re: Teng Long · lore

[PATCH v11 5/7] notes.c: append separator instead of insert by pos

From: Teng Long <dyroneteng@gmail.com>

Rename "insert_separator" to "append_separator" and also remove the "postion" argument, this serves two purpose:

The first is that when specifying more than one "-m" ( like "-F", etc) to "git notes add" or "git notes append", the order of them matters, which means we need to append the each separator and message in turn, so we don't have to make the caller specify the position, the "append" operation is enough and clear.

The second is that when we execute the "git notes append" subcommand, we need to combine the "prev_note" and "current_note" to get the final result. Before, we inserted a newline character at the beginning of "current_note". Now, we will append a newline to the end of "prev_note" instead, this will give the consisitent results.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/notes.c | 21 ++++++++++++---------
 1 file changed, 12 insertions(+), 9 deletions(-)
Show changes to builtin/notes.c +12 −9
diff --git a/builtin/notes.c b/builtin/notes.c
index d8a39fe2..b2804479 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -229,13 +229,13 @@ static void write_note_data(struct note_data *d, struct object_id *oid)
 	}
 }
 
-static void insert_separator(struct strbuf *message, size_t pos)
+static void append_separator(struct strbuf *message)
 {
 	size_t sep_len = strlen(separator);
 	if (sep_len && separator[sep_len - 1] == '\n')
-		strbuf_insertstr(message, pos, separator);
+		strbuf_addstr(message, separator);
 	else
-		strbuf_insertf(message, pos, "%s%s", separator, "\n");
+		strbuf_addf(message, "%s%s", separator, "\n");
 }
 
 static void concat_messages(struct note_data *d)
@@ -245,7 +245,7 @@ static void concat_messages(struct note_data *d)
 	size_t i;
 	for (i = 0; i < d->msg_nr ; i++) {
 		if (d->buf.len)
-			insert_separator(&d->buf, d->buf.len);
+			append_separator(&d->buf);
 		strbuf_add(&msg, d->messages[i]->buf.buf, d->messages[i]->buf.len);
 		strbuf_addbuf(&d->buf, &msg);
 		if (d->messages[i]->stripspace)
@@ -680,14 +680,17 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		/* Append buf to previous note contents */
 		unsigned long size;
 		enum object_type type;
-		char *prev_buf = repo_read_object_file(the_repository, note,
-						       &type, &size);
+		struct strbuf buf = STRBUF_INIT;
+		char *prev_buf = repo_read_object_file(the_repository, note, &type, &size);
 
-		if (d.buf.len && prev_buf && size)
-			insert_separator(&d.buf, 0);
 		if (prev_buf && size)
-			strbuf_insert(&d.buf, 0, prev_buf, size);
+			strbuf_add(&buf, prev_buf, size);
+		if (d.buf.len && prev_buf && size)
+			append_separator(&buf);
+		strbuf_insert(&d.buf, 0, buf.buf, buf.len);
+
 		free(prev_buf);
+		strbuf_release(&buf);
 	}
 
 	if (d.buf.len || allow_empty) {
-- 
2.40.0.356.g367cb1d4
Teng Long· May 27, 2023, 07:57 UTC · re: Teng Long · lore

[PATCH v11 6/7] notes.c: introduce "--[no-]stripspace" option

From: Teng Long <dyroneteng@gmail.com>

This commit introduces a new option "--[no-]stripspace" to git notes append, git notes edit, and git notes add. This option allows users to control whether the note message need to stripped out.

For the consideration of backward compatibility, let's look at the behavior about "stripspace" in "git notes" command:

1. "Edit Message" case: using the default editor to edit the note
message.
    In "edit" case, the edited message will always be stripped out, the
    implementation which can be found in the "prepare_note_data()". In
    addition, the "-c" option supports to reuse an existing blob as a
    note message, then open the editor to make a further edition on it,
    the edited message will be stripped.
    This commit doesn't change the default behavior of "edit" case by
    using an enum "notes_stripspace", only when "--no-stripspace" option
    is specified, the note message will not be stripped out. If you do
    not specify the option or you specify "--stripspace", clearly, the
    note message will be stripped out.
2. "Assign Message" case: using the "-m"/"-F"/"-C" option to specify the
note message.
    In "assign" case, when specify message by "-m" or "-F", the message
    will be stripped out by default, but when specify message by "-C",
    the message will be copied verbatim, in other word, the message will
    not be stripped out. One more thing need to note is "the order of
    the options matter", that is, if you specify "-C" before "-m" or
    "-F", the reused message by "-C" will be stripped out together,
    because everytime concat "-m" or "-F" message, the concated message
    will be stripped together. Oppositely, if you specify "-m" or "-F"
    before "-C", the reused message by "-C" will not be stripped out.
    This commit doesn't change the default behavior of "assign" case by
    extending the "stripspace" field in "struct note_msg", so we can
    distinguish the different behavior of "-m"/"-F" and "-C" options
    when we need to parse and concat the message.
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-notes.txt |  25 ++-
 builtin/notes.c             |  30 +++-
 t/t3321-notes-stripspace.sh | 296 +++++++++++++++++++++++++++++++++++-
 3 files changed, 332 insertions(+), 19 deletions(-)
Show changes to 3 files +332 −19

Documentation/git-notes.txt, builtin/notes.c, t/t3321-notes-stripspace.sh

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 59980b21..5f3a9479 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -9,10 +9,10 @@ SYNOPSIS
 --------
 [verse]
 'git notes' [list [<object>]]
-'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
+'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
 'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )
-'git notes' append [--allow-empty] [--separator=<paragraph-break>] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
-'git notes' edit [--allow-empty] [<object>]
+'git notes' append [--allow-empty] [--separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
+'git notes' edit [--allow-empty] [<object>] [--[no-]stripspace]
 'git notes' show [<object>]
 'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>
 'git notes' merge --commit [-v | -q]
@@ -141,20 +141,26 @@ OPTIONS
 	If multiple `-m` options are given, their values
 	are concatenated as separate paragraphs.
 	Lines starting with `#` and empty lines other than a
-	single line between paragraphs will be stripped out.
+	single line between paragraphs will be stripped out,
+	if you wish to keep them verbatim, use `--no-stripspace`.
 
 -F <file>::
 --file=<file>::
 	Take the note message from the given file.  Use '-' to
 	read the note message from the standard input.
 	Lines starting with `#` and empty lines other than a
-	single line between paragraphs will be stripped out.
+	single line between paragraphs will be stripped out,
+	if you wish to keep them verbatim, use with
+	`--no-stripspace` option.
 
 -C <object>::
 --reuse-message=<object>::
 	Take the given blob object (for example, another note) as the
 	note message. (Use `git notes copy <object>` instead to
-	copy notes between objects.)
+	copy notes between objects.).  By default, message will be
+	copied verbatim, but if you wish to strip out the lines
+	starting with `#` and empty lines other than a single line
+	between paragraphs, use with`--stripspace` option.
 
 -c <object>::
 --reedit-message=<object>::
@@ -170,6 +176,13 @@ OPTIONS
 	(a newline is added at the end as needed).  Defaults to a
 	blank line.
 
+--[no-]stripspace::
+	Strip leading and trailing whitespace from the note message.
+	Also strip out empty lines other than a single line between
+	paragraphs. For lines starting with `#` will be stripped out
+	in non-editor cases like "-m", "-F" and "-C", but not in
+	editor case like "git notes edit", "-c", etc.
+
 --ref <ref>::
 	Manipulate the notes tree in <ref>.  This overrides
 	`GIT_NOTES_REF` and the "core.notesRef" configuration.  The ref
diff --git a/builtin/notes.c b/builtin/notes.c
index b2804479..2140a697 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -101,14 +101,21 @@ static const char * const git_notes_get_ref_usage[] = {
 static const char note_template[] =
 	N_("Write/edit the notes for the following object:");
 
+enum notes_stripspace {
+	UNSPECIFIED = -1,
+	NO_STRIPSPACE = 0,
+	STRIPSPACE = 1,
+};
+
 struct note_msg {
-	int stripspace;
+	enum notes_stripspace stripspace;
 	struct strbuf buf;
 };
 
 struct note_data {
 	int given;
 	int use_editor;
+	int stripspace;
 	char *edit_path;
 	struct strbuf buf;
 	struct note_msg **messages;
@@ -213,7 +220,8 @@ static void prepare_note_data(const struct object_id *object, struct note_data *
 		if (launch_editor(d->edit_path, &d->buf, NULL)) {
 			die(_("please supply the note contents using either -m or -F option"));
 		}
-		strbuf_stripspace(&d->buf, 1);
+		if (d->stripspace)
+			strbuf_stripspace(&d->buf, 1);
 	}
 }
 
@@ -248,7 +256,9 @@ static void concat_messages(struct note_data *d)
 			append_separator(&d->buf);
 		strbuf_add(&msg, d->messages[i]->buf.buf, d->messages[i]->buf.len);
 		strbuf_addbuf(&d->buf, &msg);
-		if (d->messages[i]->stripspace)
+		if ((d->stripspace == UNSPECIFIED &&
+		     d->messages[i]->stripspace == STRIPSPACE) ||
+		    d->stripspace == STRIPSPACE)
 			strbuf_stripspace(&d->buf, 0);
 		strbuf_reset(&msg);
 	}
@@ -266,7 +276,7 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
 	strbuf_addstr(&msg->buf, arg);
 	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
 	d->messages[d->msg_nr - 1] = msg;
-	msg->stripspace = 1;
+	msg->stripspace = STRIPSPACE;
 	return 0;
 }
 
@@ -286,7 +296,7 @@ static int parse_file_arg(const struct option *opt, const char *arg, int unset)
 
 	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
 	d->messages[d->msg_nr - 1] = msg;
-	msg->stripspace = 1;
+	msg->stripspace = STRIPSPACE;
 	return 0;
 }
 
@@ -319,7 +329,7 @@ static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)
 	msg->buf.len = len;
 	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
 	d->messages[d->msg_nr - 1] = msg;
-	msg->stripspace = 0;
+	msg->stripspace = NO_STRIPSPACE;
 	return 0;
 }
 
@@ -453,7 +463,7 @@ static int add(int argc, const char **argv, const char *prefix)
 	struct notes_tree *t;
 	struct object_id object, new_note;
 	const struct object_id *note;
-	struct note_data d = { .buf = STRBUF_INIT };
+	struct note_data d = { .buf = STRBUF_INIT, .stripspace = UNSPECIFIED };
 
 	struct option options[] = {
 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
@@ -473,6 +483,8 @@ static int add(int argc, const char **argv, const char *prefix)
 		OPT__FORCE(&force, N_("replace existing notes"), PARSE_OPT_NOCOMPLETE),
 		OPT_STRING(0, "separator", &separator, N_("separator"),
 			N_("insert <paragraph-break> between paragraphs")),
+		OPT_BOOL(0, "stripspace", &d.stripspace,
+			N_("remove unnecessary whitespace")),
 		OPT_END()
 	};
 
@@ -626,7 +638,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 	const struct object_id *note;
 	char *logmsg;
 	const char * const *usage;
-	struct note_data d = { .buf = STRBUF_INIT };
+	struct note_data d = { .buf = STRBUF_INIT, .stripspace = UNSPECIFIED };
 	struct option options[] = {
 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
 			N_("note contents as a string"), PARSE_OPT_NONEG,
@@ -644,6 +656,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 			N_("allow storing empty note")),
 		OPT_STRING(0, "separator", &separator, N_("separator"),
 			N_("insert <paragraph-break> between paragraphs")),
+		OPT_BOOL(0, "stripspace", &d.stripspace,
+			N_("remove unnecessary whitespace")),
 		OPT_END()
 	};
 	int edit = !strcmp(argv[0], "edit");
diff --git a/t/t3321-notes-stripspace.sh b/t/t3321-notes-stripspace.sh
index 89977873..028d825e 100755
--- a/t/t3321-notes-stripspace.sh
+++ b/t/t3321-notes-stripspace.sh
@@ -32,7 +32,7 @@ test_expect_success 'add note by editor' '
 	test_cmp expect actual
 '
 
-test_expect_success 'add note by specifying single "-m"' '
+test_expect_success 'add note by specifying single "-m", "--stripspace" is the default behavior' '
 	test_when_finished "git notes remove" &&
 	cat >expect <<-EOF &&
 	first-line
@@ -42,10 +42,26 @@ test_expect_success 'add note by specifying single "-m"' '
 
 	git notes add -m "${LF}first-line${MULTI_LF}second-line${LF}" &&
 	git notes show >actual &&
+	test_cmp expect actual &&
+	git notes remove &&
+	git notes add --stripspace -m "${LF}first-line${MULTI_LF}second-line${LF}" &&
+	git notes show >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success 'add note by specifying multiple "-m"' '
+test_expect_success 'add note by specifying single "-m" and "--no-stripspace" ' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	${LF}first-line${MULTI_LF}second-line
+	EOF
+
+	git notes add --no-stripspace \
+		      -m "${LF}first-line${MULTI_LF}second-line${LF}" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying multiple "-m", "--stripspace" is the default behavior' '
 	test_when_finished "git notes remove" &&
 	cat >expect <<-EOF &&
 	first-line
@@ -59,9 +75,156 @@ test_expect_success 'add note by specifying multiple "-m"' '
 		      -m "second-line" \
 		      -m "${LF}" &&
 	git notes show >actual &&
+	test_cmp expect actual &&
+	git notes remove &&
+	git notes add --stripspace -m "${LF}" \
+		      -m "first-line" \
+		      -m "${MULTI_LF}" \
+		      -m "second-line" \
+		      -m "${LF}" &&
+	git notes show >actual &&
 	test_cmp expect actual
 '
 
+test_expect_success 'add notes by specifying multiple "-m" and "--no-stripspace"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line${LF}
+	EOF
+
+	git notes add --no-stripspace \
+		      -m "${LF}" \
+		      -m "first-line" \
+		      -m "${MULTI_LF}" \
+		      -m "second-line" \
+		      -m "${LF}" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying single "-F", "--stripspace" is the default behavior' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	cat >note-file <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	git notes add -F note-file &&
+	git notes show >actual &&
+	test_cmp expect actual &&
+	git notes remove &&
+	git notes add --stripspace -F note-file &&
+	git notes show >actual
+'
+
+test_expect_success 'add note by specifying single "-F" and "--no-stripspace"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	cat >note-file <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	git notes add --no-stripspace -F note-file &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying multiple "-F", "--stripspace" is the default behavior' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	file-1-first-line
+
+	file-1-second-line
+
+	file-2-first-line
+
+	file-2-second-line
+	EOF
+
+	cat >note-file-1 <<-EOF &&
+	${LF}
+	file-1-first-line
+	${MULTI_LF}
+	file-1-second-line
+	${LF}
+	EOF
+
+	cat >note-file-2 <<-EOF &&
+	${LF}
+	file-2-first-line
+	${MULTI_LF}
+	file-2-second-line
+	${LF}
+	EOF
+
+	git notes add -F note-file-1 -F note-file-2 &&
+	git notes show >actual &&
+	test_cmp expect actual &&
+	git notes remove &&
+	git notes add --stripspace -F note-file-1 -F note-file-2 &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying multiple "-F" with "--no-stripspace"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	${LF}
+	file-1-first-line
+	${MULTI_LF}
+	file-1-second-line
+	${LF}
+
+	${LF}
+	file-2-first-line
+	${MULTI_LF}
+	file-2-second-line
+	${LF}
+	EOF
+
+	cat >note-file-1 <<-EOF &&
+	${LF}
+	file-1-first-line
+	${MULTI_LF}
+	file-1-second-line
+	${LF}
+	EOF
+
+	cat >note-file-2 <<-EOF &&
+	${LF}
+	file-2-first-line
+	${MULTI_LF}
+	file-2-second-line
+	${LF}
+	EOF
+
+	git notes add --no-stripspace -F note-file-1 -F note-file-2 &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
 
 test_expect_success 'append note by editor' '
 	test_when_finished "git notes remove" &&
@@ -221,6 +384,45 @@ test_expect_success 'append notes by specifying multiple "-F"' '
 	test_cmp expect actual
 '
 
+test_expect_success 'append note by specifying multiple "-F" with "--no-stripspace"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	initial-line
+	${LF}${LF}
+	file-1-first-line
+	${MULTI_LF}
+	file-1-second-line
+	${LF}
+
+	${LF}
+	file-2-first-line
+	${MULTI_LF}
+	file-2-second-line
+	${LF}
+	EOF
+
+	cat >note-file-1 <<-EOF &&
+	${LF}
+	file-1-first-line
+	${MULTI_LF}
+	file-1-second-line
+	${LF}
+	EOF
+
+	cat >note-file-2 <<-EOF &&
+	${LF}
+	file-2-first-line
+	${MULTI_LF}
+	file-2-second-line
+	${LF}
+	EOF
+
+	git notes add -m "initial-line" &&
+	git notes append --no-stripspace -F note-file-1 -F note-file-2 &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'add notes with empty messages' '
 	rev=$(git rev-parse HEAD) &&
 	git notes add -m "${LF}" \
@@ -229,7 +431,7 @@ test_expect_success 'add notes with empty messages' '
 	test_i18ngrep "Removing note for object" actual
 '
 
-test_expect_success 'add note by specifying "-C" , do not stripspace is the default behavior' '
+test_expect_success 'add note by specifying "-C", "--no-stripspace" is the default behavior' '
 	test_when_finished "git notes remove" &&
 	cat >expect <<-EOF &&
 	${LF}
@@ -242,10 +444,36 @@ test_expect_success 'add note by specifying "-C" , do not stripspace is the defa
 	cat expect | git hash-object -w --stdin >blob &&
 	git notes add -C $(cat blob) &&
 	git notes show >actual &&
+	test_cmp expect actual &&
+	git notes remove &&
+	git notes add --no-stripspace -C $(cat blob) &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'reuse note by specifying "-C" and "--stripspace"' '
+	test_when_finished "git notes remove" &&
+	cat >data <<-EOF &&
+	${LF}
+	first-line
+	${MULTI_LF}
+	second-line
+	${LF}
+	EOF
+
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	cat data | git hash-object -w --stdin >blob &&
+	git notes add --stripspace -C $(cat blob) &&
+	git notes show >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success 'add notes with "-C" and "-m", "-m" will stripspace all together' '
+test_expect_success 'reuse with "-C" and add note with "-m", "-m" will stripspace all together' '
 	test_when_finished "git notes remove" &&
 	cat >data <<-EOF &&
 	${LF}
@@ -269,7 +497,7 @@ test_expect_success 'add notes with "-C" and "-m", "-m" will stripspace all toge
 	test_cmp expect actual
 '
 
-test_expect_success 'add notes with "-m" and "-C", "-C" will not stripspace all together' '
+test_expect_success 'add note with "-m" and reuse note with "-C", "-C" will not stripspace all together' '
 	test_when_finished "git notes remove" &&
 	cat >data <<-EOF &&
 
@@ -288,4 +516,62 @@ test_expect_success 'add notes with "-m" and "-C", "-C" will not stripspace all
 	test_cmp expect actual
 '
 
+test_expect_success 'add note by specifying "-c", "--stripspace" is the default behavior' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	echo "initial-line" | git hash-object -w --stdin >blob &&
+	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes add -c $(cat blob) &&
+	git notes show >actual &&
+	test_cmp expect actual &&
+	git notes remove &&
+	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes add --stripspace -c $(cat blob) &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'add note by specifying "-c" with "--no-stripspace"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	${LF}first-line${MULTI_LF}second-line${LF}
+	EOF
+
+	echo "initial-line" | git hash-object -w --stdin >blob &&
+	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes add --no-stripspace -c $(cat blob) &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'edit note by specifying "-c", "--stripspace" is the default behavior' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	first-line
+
+	second-line
+	EOF
+
+	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes edit &&
+	git notes show >actual &&
+	test_cmp expect actual &&
+	git notes remove &&
+	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes edit --stripspace &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'edit note by specifying "-c" with "--no-stripspace"' '
+	test_when_finished "git notes remove" &&
+	cat >expect <<-EOF &&
+	${LF}first-line${MULTI_LF}second-line${LF}
+	EOF
+
+	MSG="${LF}first-line${MULTI_LF}second-line${LF}" git notes add --no-stripspace &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
 test_done
-- 
2.40.0.356.g367cb1d4
Teng Long· May 27, 2023, 07:57 UTC · re: Teng Long · lore

[PATCH v11 7/7] notes: introduce "--no-separator" option

From: Teng Long <dyroneteng@gmail.com>

Sometimes, the user may want to add or append multiple notes without any separator to be added between them.

Disscussion:
  https://public-inbox.org/git/3f86a553-246a-4626-b1bd-bacd8148318a@app.fastmail.com/
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-notes.txt | 11 +++++----
 builtin/notes.c             | 37 ++++++++++++++++++++--------
 t/t3301-notes.sh            | 49 ++++++++++++++++++++++++++++++++++---
 3 files changed, 79 insertions(+), 18 deletions(-)
Show changes to 3 files +79 −17

Documentation/git-notes.txt, builtin/notes.c, t/t3301-notes.sh

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 5f3a9479..bc1bfa37 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -9,9 +9,9 @@ SYNOPSIS
 --------
 [verse]
 'git notes' [list [<object>]]
-'git notes' add [-f] [--allow-empty] [--separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
+'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
 'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )
-'git notes' append [--allow-empty] [--separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
+'git notes' append [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
 'git notes' edit [--allow-empty] [<object>] [--[no-]stripspace]
 'git notes' show [<object>]
 'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>
@@ -171,10 +171,11 @@ OPTIONS
 	Allow an empty note object to be stored. The default behavior is
 	to automatically remove empty notes.
 
---separator <paragraph-break>::
+--[no-]separator, --separator=<paragraph-break>::
 	Specify a string used as a custom inter-paragraph separator
-	(a newline is added at the end as needed).  Defaults to a
-	blank line.
+	(a newline is added at the end as needed). If `--no-separator`, no
+	separators will be added between paragraphs.  Defaults to a blank
+	line.
 
 --[no-]stripspace::
 	Strip leading and trailing whitespace from the note message.
diff --git a/builtin/notes.c b/builtin/notes.c
index 2140a697..00268e94 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -28,12 +28,12 @@
 #include "worktree.h"
 #include "write-or-die.h"
 
-static char *separator = "\n";
+static const char *separator = "\n";
 static const char * const git_notes_usage[] = {
 	N_("git notes [--ref <notes-ref>] [list [<object>]]"),
-	N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
+	N_("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_("git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>"),
-	N_("git notes [--ref <notes-ref>] append [--allow-empty] [--separator=<paragraph-break>] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
+	N_("git notes [--ref <notes-ref>] append [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [--[no-]stripspace] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
 	N_("git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]"),
 	N_("git notes [--ref <notes-ref>] show [<object>]"),
 	N_("git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>"),
@@ -239,8 +239,11 @@ static void write_note_data(struct note_data *d, struct object_id *oid)
 
 static void append_separator(struct strbuf *message)
 {
-	size_t sep_len = strlen(separator);
-	if (sep_len && separator[sep_len - 1] == '\n')
+	size_t sep_len = 0;
+
+	if (!separator)
+		return;
+	else if ((sep_len = strlen(separator)) && separator[sep_len - 1] == '\n')
 		strbuf_addstr(message, separator);
 	else
 		strbuf_addf(message, "%s%s", separator, "\n");
@@ -249,8 +252,8 @@ static void append_separator(struct strbuf *message)
 static void concat_messages(struct note_data *d)
 {
 	struct strbuf msg = STRBUF_INIT;
-
 	size_t i;
+
 	for (i = 0; i < d->msg_nr ; i++) {
 		if (d->buf.len)
 			append_separator(&d->buf);
@@ -341,6 +344,16 @@ static int parse_reedit_arg(const struct option *opt, const char *arg, int unset
 	return parse_reuse_arg(opt, arg, unset);
 }
 
+static int parse_separator_arg(const struct option *opt, const char *arg,
+			       int unset)
+{
+	if (unset)
+		*(const char **)opt->value = NULL;
+	else
+		*(const char **)opt->value = arg ? arg : "\n";
+	return 0;
+}
+
 static int notes_copy_from_stdin(int force, const char *rewrite_cmd)
 {
 	struct strbuf buf = STRBUF_INIT;
@@ -481,8 +494,10 @@ static int add(int argc, const char **argv, const char *prefix)
 		OPT_BOOL(0, "allow-empty", &allow_empty,
 			N_("allow storing empty note")),
 		OPT__FORCE(&force, N_("replace existing notes"), PARSE_OPT_NOCOMPLETE),
-		OPT_STRING(0, "separator", &separator, N_("separator"),
-			N_("insert <paragraph-break> between paragraphs")),
+		OPT_CALLBACK_F(0, "separator", &separator,
+			N_("<paragraph-break>"),
+			N_("insert <paragraph-break> between paragraphs"),
+			PARSE_OPT_OPTARG, parse_separator_arg),
 		OPT_BOOL(0, "stripspace", &d.stripspace,
 			N_("remove unnecessary whitespace")),
 		OPT_END()
@@ -654,8 +669,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 			parse_reuse_arg),
 		OPT_BOOL(0, "allow-empty", &allow_empty,
 			N_("allow storing empty note")),
-		OPT_STRING(0, "separator", &separator, N_("separator"),
-			N_("insert <paragraph-break> between paragraphs")),
+		OPT_CALLBACK_F(0, "separator", &separator,
+			N_("<paragraph-break>"),
+			N_("insert <paragraph-break> between paragraphs"),
+			PARSE_OPT_OPTARG, parse_separator_arg),
 		OPT_BOOL(0, "stripspace", &d.stripspace,
 			N_("remove unnecessary whitespace")),
 		OPT_END()
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index dbadcf13..d734000d 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -382,6 +382,7 @@ test_expect_success 'create note with combination of -m and -F' '
 '
 
 test_expect_success 'create note with combination of -m and -F and --separator' '
+	test_when_finished git notes remove HEAD &&
 	cat >expect-combine_m_and_F <<-\EOF &&
 	foo
 	-------
@@ -395,7 +396,22 @@ test_expect_success 'create note with combination of -m and -F and --separator'
 	EOF
 	echo "xyzzy" >note_a &&
 	echo "zyxxy" >note_b &&
-	git notes add -m "foo" -F note_a -m "bar" -F note_b -m "baz" --separator "-------" &&
+	git notes add -m "foo" -F note_a -m "bar" -F note_b -m "baz" --separator="-------" &&
+	git notes show >actual &&
+	test_cmp expect-combine_m_and_F actual
+'
+
+test_expect_success 'create note with combination of -m and -F and --no-separator' '
+	cat >expect-combine_m_and_F <<-\EOF &&
+	foo
+	xyzzy
+	bar
+	zyxxy
+	baz
+	EOF
+	echo "xyzzy" >note_a &&
+	echo "zyxxy" >note_b &&
+	git notes add -m "foo" -F note_a -m "bar" -F note_b -m "baz" --no-separator &&
 	git notes show >actual &&
 	test_cmp expect-combine_m_and_F actual
 '
@@ -541,7 +557,7 @@ test_expect_success 'listing non-existing notes fails' '
 	test_must_be_empty actual
 '
 
-test_expect_success 'append: specify an empty separator' '
+test_expect_success 'append: specify a separator with an empty arg' '
 	test_when_finished git notes remove HEAD &&
 	cat >expect <<-\EOF &&
 	notes-1
@@ -555,6 +571,33 @@ test_expect_success 'append: specify an empty separator' '
 	test_cmp expect actual
 '
 
+test_expect_success 'append: specify a separator without arg' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect <<-\EOF &&
+	notes-1
+
+	notes-2
+	EOF
+
+	git notes add -m "notes-1" &&
+	git notes append --separator -m "notes-2" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'append: specify as --no-separator' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect <<-\EOF &&
+	notes-1
+	notes-2
+	EOF
+
+	git notes add -m "notes-1" &&
+	git notes append --no-separator -m "notes-2" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'append: specify separator with line break' '
 	test_when_finished git notes remove HEAD &&
 	cat >expect <<-\EOF &&
@@ -615,7 +658,7 @@ test_expect_success 'append note with combination of -m and -F and --separator'
 
 	echo "f-notes-1" >note_a &&
 	echo "f-notes-2" >note_b &&
-	git notes append -m "m-notes-1" -F note_a -m "m-notes-2" -F note_b -m "m-notes-3" --separator "-------" &&
+	git notes append -m "m-notes-1" -F note_a -m "m-notes-2" -F note_b -m "m-notes-3" --separator="-------" &&
 	git notes show >actual &&
 	test_cmp expect-combine_m_and_F actual
 '
-- 
2.40.0.356.g367cb1d4
Junio C Hamano· Jun 1, 2023, 05:50 UTC · re: Teng Long · lore

Re: [PATCH v11 0/7] notes.c: introduce "--separator"

Teng Long <dyroneteng@gmail.com> writes:
> From: Teng Long <dyroneteng@gmail.com>
>
> 1.  "--[no-]separator" to an independent patch which suggested by Junio[1].
I did suggest but not quite.

The 4th step you are fixing is already in 'next'. You do not replace a commit in a series that has been cooking there for a long time.

In a more perfect world, you would have sent the "--separator" thing that already supported "--no-separator" from the get-go. You would also have sent the 4th step that does not need ubsan fix. You do not have the luxury to do so when your series hit 'next'---you need to live with your earlier mistakes.

Until we cross the release boundary, that is.  

I'll revert the whole thing out of 'next'. Once the next cycle opens after the dust from the release settles (i.e. around 10th of June or so), please resubmit the series that does not need "oops, we probably should have added '--[no-]separator' from the get-go" or "oops, we had undefined behaviour here, and here is a fix", as if you are a more perfect developer than you were when the version that has been queued in 'next' was written (and in fact, you are, after discussing the design and UBSan issues and stuff during the review).

Thanks.
Teng Long· Jun 3, 2023, 10:01 UTC · re: Junio C Hamano · lore

Re: [PATCH v11 0/7] notes.c: introduce "--separator"

Junio C Hamano <gitster@pobox.com> writes:
Show 7 quoted lines
> In a more perfect world, you would have sent the "--separator" thing
> that already supported "--no-separator" from the get-go.  You would
> also have sent the 4th step that does not need ubsan fix.  You do
> not have the luxury to do so when your series hit 'next'---you need
> to live with your earlier mistakes.
> 
> Until we cross the release boundary, that is.

OK, I hope to make the commit more cohesive before they release, but I ignored that the 'next' branch is already in the release period (because I think it might be possible to replace it in the 'next', but actually it shouldn't be done), sorry for the trouble it may cause you.

> I'll revert the whole thing out of 'next'.  Once the next cycle
> opens after the dust from the release settles (i.e. around 10th of
> June or so), ...
OK.
Show 6 quoted lines
> ... please resubmit the series that does not need "oops, we
> probably should have added '--[no-]separator' from the get-go" or
> "oops, we had undefined behaviour here, and here is a fix", as if
> you are a more perfect developer than you were when the version that
> has been queued in 'next' was written (and in fact, you are, after
> discussing the design and UBSan issues and stuff during the review).

I think the v11 should be a version which is acceptable if there is no incoming review conversitions (The current v11 that separates '--no-separator' as a individual commit OR make 7/7 into 4/7, both approaches seem fine)?

Thanks.

← back to recent threads