Git Mailing List Archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH 0/2] notes.c: introduce "--no-blankline" option
@ 2022-10-13  5:56 Teng Long
  2022-10-13  5:56 ` [RFC PATCH 1/2] " Teng Long
                   ` (2 more replies)
  0 siblings, 3 replies; 186+ messages in thread
From: Teng Long @ 2022-10-13  5:56 UTC (permalink / raw)
  To: git; +Cc: --cc=avarab, tenglong.tl, Teng Long

From: Teng Long <dyroneteng@gmail.com>

This RFC patchset includes two patches.

One aims to introduce a new option "--no-blankline" for "git-notes-append". The
another one tries to fix the inaccurate output when target and append note are
both empty.

Thanks.

Teng Long (2):
  notes.c: introduce "--no-blankline" option
  notes.c: fixed tip when target and append note are both empty

 Documentation/git-notes.txt | 10 ++++++++--
 builtin/notes.c             | 18 +++++++++++++++---
 t/t3301-notes.sh            | 15 ++++++++++++++-
 3 files changed, 37 insertions(+), 6 deletions(-)

-- 
2.38.0.rc2


^ permalink raw reply	[flat|nested] 186+ messages in thread

* [RFC PATCH 1/2] notes.c: introduce "--no-blankline" option
  2022-10-13  5:56 [RFC PATCH 0/2] notes.c: introduce "--no-blankline" option Teng Long
@ 2022-10-13  5:56 ` Teng Long
  2022-10-13  6:06   ` Junio C Hamano
  2022-10-13  9:31   ` Ævar Arnfjörð Bjarmason
  2022-10-13  5:56 ` [RFC PATCH 2/2] notes.c: fixed tip when target and append note are both empty Teng Long
  2022-11-07 13:57 ` [PATCH v2 0/3] notes.c: introduce "--blank-line" option Teng Long
  2 siblings, 2 replies; 186+ messages in thread
From: Teng Long @ 2022-10-13  5:56 UTC (permalink / raw)
  To: git; +Cc: --cc=avarab, tenglong.tl, Teng Long

From: Teng Long <dyroneteng@gmail.com>

When appending to a given object which has note and if the appended
note is not empty too, we will insert a blank line at first. The
blank line serves as a split line, but sometimes we just want to
omit it then append on the heels of the target note.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-notes.txt | 10 ++++++++--
 builtin/notes.c             |  5 ++++-
 t/t3301-notes.sh            | 12 ++++++++++++
 3 files changed, 24 insertions(+), 3 deletions(-)

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index efbc10f0f5..fd0fc3d9c7 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -11,7 +11,7 @@ SYNOPSIS
 'git notes' [list [<object>]]
 'git notes' add [-f] [--allow-empty] [-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-blankline] [-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>
@@ -86,7 +86,9 @@ 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.
+	Creates a new notes object if needed. If the note of the given
+	object and the note to be appended are not empty, a blank line
+	will be inserted between them.
 
 edit::
 	Edit the notes for a given object (defaults to HEAD).
@@ -159,6 +161,10 @@ OPTIONS
 	Allow an empty note object to be stored. The default behavior is
 	to automatically remove empty notes.
 
+--no-blank-line::
+	When appending note, do not insert a blank line between
+	the note of given object and the note to be appended.
+
 --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 be51f69225..1ca0476a27 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -562,6 +562,7 @@ static int copy(int argc, const char **argv, const char *prefix)
 static int append_edit(int argc, const char **argv, const char *prefix)
 {
 	int allow_empty = 0;
+	int no_blankline = 0;
 	const char *object_ref;
 	struct notes_tree *t;
 	struct object_id object, new_note;
@@ -584,6 +585,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_BOOL(0, "no-blankline", &no_blankline,
+			N_("do not initially add a blank line")),
 		OPT_END()
 	};
 	int edit = !strcmp(argv[0], "edit");
@@ -619,7 +622,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		char *prev_buf = read_object_file(note, &type, &size);
 
 		strbuf_grow(&d.buf, size + 1);
-		if (d.buf.len && prev_buf && size)
+		if (!no_blankline && d.buf.len && prev_buf && size)
 			strbuf_insertstr(&d.buf, 0, "\n");
 		if (prev_buf && size)
 			strbuf_insert(&d.buf, 0, prev_buf, size);
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 3288aaec7d..43ac3feb78 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -521,12 +521,24 @@ test_expect_success 'listing non-existing notes fails' '
 	test_must_be_empty actual
 '
 
+test_expect_success 'append to existing note without a beginning blank line' '
+	cat >expect <<-EOF &&
+		Initial set of notes
+		Appended notes
+	EOF
+	git notes add -m "Initial set of notes" &&
+	git notes append --no-blankline -m "Appended notes" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'append to existing note with "git notes append"' '
 	cat >expect <<-EOF &&
 		Initial set of notes
 
 		More notes appended with git notes append
 	EOF
+	git notes remove HEAD &&
 	git notes add -m "Initial set of notes" &&
 	git notes append -m "More notes appended with git notes append" &&
 	git notes show >actual &&
-- 
2.38.0.rc2


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [RFC PATCH 2/2] notes.c: fixed tip when target and append note are both empty
  2022-10-13  5:56 [RFC PATCH 0/2] notes.c: introduce "--no-blankline" option Teng Long
  2022-10-13  5:56 ` [RFC PATCH 1/2] " Teng Long
@ 2022-10-13  5:56 ` Teng Long
  2022-10-13  9:36   ` Ævar Arnfjörð Bjarmason
  2022-11-07 13:57 ` [PATCH v2 0/3] notes.c: introduce "--blank-line" option Teng Long
  2 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2022-10-13  5:56 UTC (permalink / raw)
  To: git; +Cc: --cc=avarab, tenglong.tl, Teng Long

From: Teng Long <dyroneteng@gmail.com>

When "git notes append <object>" is executed, if there is no note in
the given object and the appended note is empty, the command line
prompt will be as follows:

     "Removing note for object <object>"

Actually, this tip is not that accurate, because there is no note in
the original <object>, and it also does no remove work on the notes
reference, so let's fix this and give the correct tip.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/notes.c  | 13 +++++++++++--
 t/t3301-notes.sh |  3 ++-
 2 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index 1ca0476a27..cc1e3aa2b6 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -567,9 +567,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 	struct notes_tree *t;
 	struct object_id object, new_note;
 	const struct object_id *note;
-	char *logmsg;
+	char *logmsg = NULL;
 	const char * const *usage;
 	struct note_data d = { 0, 0, NULL, STRBUF_INIT };
+	struct note_data cp = { 0, 0, NULL, STRBUF_INIT };
 	struct option options[] = {
 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
 			N_("note contents as a string"), PARSE_OPT_NONEG,
@@ -615,6 +616,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 
 	prepare_note_data(&object, &d, edit && note ? note : NULL);
 
+	strbuf_addbuf(&cp.buf, &d.buf);
+
 	if (note && !edit) {
 		/* Append buf to previous note contents */
 		unsigned long size;
@@ -634,16 +637,22 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		if (add_note(t, &object, &new_note, combine_notes_overwrite))
 			BUG("combine_notes_overwrite failed");
 		logmsg = xstrfmt("Notes added by 'git notes %s'", argv[0]);
+		commit_notes(the_repository, t, logmsg);
+	} else if (!cp.buf.len) {
+		fprintf(stderr,
+			_("Both original and appended notes are empty in %s, do nothing\n"),
+			oid_to_hex(&object));
 	} else {
 		fprintf(stderr, _("Removing note for object %s\n"),
 			oid_to_hex(&object));
 		remove_note(t, object.hash);
 		logmsg = xstrfmt("Notes removed by 'git notes %s'", argv[0]);
+		commit_notes(the_repository, t, logmsg);
 	}
-	commit_notes(the_repository, t, logmsg);
 
 	free(logmsg);
 	free_note_data(&d);
+	free_note_data(&cp);
 	free_notes(t);
 	return 0;
 }
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 43ac3feb78..967e6bfb67 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -574,7 +574,8 @@ test_expect_success 'git notes append == add when there is no existing note' '
 test_expect_success 'appending empty string to non-existing note does not create note' '
 	git notes remove HEAD &&
 	test_must_fail git notes list HEAD &&
-	git notes append -m "" &&
+	git notes append -m "" >output 2>&1 &&
+	grep "Both original and appended notes are empty" output &&
 	test_must_fail git notes list HEAD
 '
 
-- 
2.38.0.rc2


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* Re: [RFC PATCH 1/2] notes.c: introduce "--no-blankline" option
  2022-10-13  5:56 ` [RFC PATCH 1/2] " Teng Long
@ 2022-10-13  6:06   ` Junio C Hamano
  2022-10-17 13:19     ` Teng Long
  2022-10-13  9:31   ` Ævar Arnfjörð Bjarmason
  1 sibling, 1 reply; 186+ messages in thread
From: Junio C Hamano @ 2022-10-13  6:06 UTC (permalink / raw)
  To: Teng Long; +Cc: git, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> +--no-blank-line::
> +	When appending note, do not insert a blank line between
> +	the note of given object and the note to be appended.
> +

--blank-line::
--no-blank-line::
	Controls if a blank line to split paragraphs is inserted
        when appending (the default is true).

> diff --git a/builtin/notes.c b/builtin/notes.c
> index be51f69225..1ca0476a27 100644
> --- a/builtin/notes.c
> +++ b/builtin/notes.c
> @@ -562,6 +562,7 @@ static int copy(int argc, const char **argv, const char *prefix)
>  static int append_edit(int argc, const char **argv, const char *prefix)
>  {
>  	int allow_empty = 0;
> +	int no_blankline = 0;

Use

	int blankline = 1;

to avoid double negative, which is confusing and error prone.

> @@ -584,6 +585,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_BOOL(0, "no-blankline", &no_blankline,
> +			N_("do not initially add a blank line")),

	OPT_BOOL(0, "blank-line", &blankline,
		 N_("insert paragraph break before appending to an existing note")),

> @@ -619,7 +622,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
>  		char *prev_buf = read_object_file(note, &type, &size);
>  
>  		strbuf_grow(&d.buf, size + 1);
> -		if (d.buf.len && prev_buf && size)
> +		if (!no_blankline && d.buf.len && prev_buf && size)
>  			strbuf_insertstr(&d.buf, 0, "\n");

Then, the conditional would read more naturally without double
negation.

		if (blank_line && d.buf.len && prev_buf && size)

I do not know and I am not judging (yet) if the goal of the patch is
sensible (in other words, if we should have such an option), but if
we were to do so, I would expect the implementation to look more
like what I outlined above.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [RFC PATCH 1/2] notes.c: introduce "--no-blankline" option
  2022-10-13  5:56 ` [RFC PATCH 1/2] " Teng Long
  2022-10-13  6:06   ` Junio C Hamano
@ 2022-10-13  9:31   ` Ævar Arnfjörð Bjarmason
  2022-10-17 13:33     ` Teng Long
  1 sibling, 1 reply; 186+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-10-13  9:31 UTC (permalink / raw)
  To: Teng Long; +Cc: git, --cc=avarab, tenglong.tl


On Thu, Oct 13 2022, Teng Long wrote:

> From: Teng Long <dyroneteng@gmail.com>
> [...]
> +test_expect_success 'append to existing note without a beginning blank line' '
> +	cat >expect <<-EOF &&

Use <<-\EOF here.

> +		Initial set of notes
> +		Appended notes

We usually indent the "EOF" body the same as the "cat", but...

> +	EOF
> +	git notes add -m "Initial set of notes" &&
> +	git notes append --no-blankline -m "Appended notes" &&
> +	git notes show >actual &&
> +	test_cmp expect actual
> +'
> +
>  test_expect_success 'append to existing note with "git notes append"' '
>  	cat >expect <<-EOF &&
>  		Initial set of notes
>  
>  		More notes appended with git notes append
>  	EOF

... I see this test might be an odd one out, so this is fine.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [RFC PATCH 2/2] notes.c: fixed tip when target and append note are both empty
  2022-10-13  5:56 ` [RFC PATCH 2/2] notes.c: fixed tip when target and append note are both empty Teng Long
@ 2022-10-13  9:36   ` Ævar Arnfjörð Bjarmason
  2022-10-13 10:10     ` Phillip Wood
  2022-10-18  3:11     ` Teng Long
  0 siblings, 2 replies; 186+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-10-13  9:36 UTC (permalink / raw)
  To: Teng Long; +Cc: git, --cc=avarab, tenglong.tl


On Thu, Oct 13 2022, Teng Long wrote:

> From: Teng Long <dyroneteng@gmail.com>
>
> When "git notes append <object>" is executed, if there is no note in
> the given object and the appended note is empty, the command line
> prompt will be as follows:
>
>      "Removing note for object <object>"
>
> Actually, this tip is not that accurate, because there is no note in
> the original <object>, and it also does no remove work on the notes
> reference, so let's fix this and give the correct tip.
>
> Signed-off-by: Teng Long <dyroneteng@gmail.com>
> ---
>  builtin/notes.c  | 13 +++++++++++--
>  t/t3301-notes.sh |  3 ++-
>  2 files changed, 13 insertions(+), 3 deletions(-)
>
> diff --git a/builtin/notes.c b/builtin/notes.c
> index 1ca0476a27..cc1e3aa2b6 100644
> --- a/builtin/notes.c
> +++ b/builtin/notes.c
> @@ -567,9 +567,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)
>  	struct notes_tree *t;
>  	struct object_id object, new_note;
>  	const struct object_id *note;
> -	char *logmsg;
> +	char *logmsg = NULL;

Hrm, interesting that (at least my) gcc doesn't catch if we don't
NULL-initialize this, but -fanalyzer does (usually it's not needed for
such trivial cases0. Anyawy...

>  	const char * const *usage;
>  	struct note_data d = { 0, 0, NULL, STRBUF_INIT };
> +	struct note_data cp = { 0, 0, NULL, STRBUF_INIT };

This is probably better "fixed while at it" to set both to use "{ .buf =
STRBUF_INIT }", rather than copying the pre-C99 pattern.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [RFC PATCH 2/2] notes.c: fixed tip when target and append note are both empty
  2022-10-13  9:36   ` Ævar Arnfjörð Bjarmason
@ 2022-10-13 10:10     ` Phillip Wood
  2022-10-13 10:23       ` Ævar Arnfjörð Bjarmason
                         ` (2 more replies)
  2022-10-18  3:11     ` Teng Long
  1 sibling, 3 replies; 186+ messages in thread
From: Phillip Wood @ 2022-10-13 10:10 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason, Teng Long
  Cc: git, --cc=avarab, tenglong.tl

On 13/10/2022 10:36, Ævar Arnfjörð Bjarmason wrote:
> 
> On Thu, Oct 13 2022, Teng Long wrote:
> 
>> From: Teng Long <dyroneteng@gmail.com>
>>
>> When "git notes append <object>" is executed, if there is no note in
>> the given object and the appended note is empty, the command line
>> prompt will be as follows:
>>
>>       "Removing note for object <object>"
>>
>> Actually, this tip is not that accurate, because there is no note in
>> the original <object>, and it also does no remove work on the notes
>> reference, so let's fix this and give the correct tip.
>>
>> Signed-off-by: Teng Long <dyroneteng@gmail.com>
>> ---
>>   builtin/notes.c  | 13 +++++++++++--
>>   t/t3301-notes.sh |  3 ++-
>>   2 files changed, 13 insertions(+), 3 deletions(-)
>>
>> diff --git a/builtin/notes.c b/builtin/notes.c
>> index 1ca0476a27..cc1e3aa2b6 100644
>> --- a/builtin/notes.c
>> +++ b/builtin/notes.c
>> @@ -567,9 +567,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)
>>   	struct notes_tree *t;
>>   	struct object_id object, new_note;
>>   	const struct object_id *note;
>> -	char *logmsg;
>> +	char *logmsg = NULL;
> 
> Hrm, interesting that (at least my) gcc doesn't catch if we don't
> NULL-initialize this, but -fanalyzer does (usually it's not needed for
> such trivial cases0. Anyawy...

I don't think its written to if we take the 'else if' branch added by 
this patch so we need to initialize it for the free() at the end.

>>   	const char * const *usage;
>>   	struct note_data d = { 0, 0, NULL, STRBUF_INIT };
>> +	struct note_data cp = { 0, 0, NULL, STRBUF_INIT };
> 
> This is probably better "fixed while at it" to set both to use "{ .buf =
> STRBUF_INIT }", rather than copying the pre-C99 pattern.

We only seem to be using cp.buf.len so we can test check if the original 
note was empty so I think it would be better just to add

	int note_was_empty;

`	...

	note_was_empty = !d.buf.len

instead.

Best Wishes

Phillip

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [RFC PATCH 2/2] notes.c: fixed tip when target and append note are both empty
  2022-10-13 10:10     ` Phillip Wood
@ 2022-10-13 10:23       ` Ævar Arnfjörð Bjarmason
  2022-10-15 19:40         ` Phillip Wood
  2022-10-18  3:25       ` Teng Long
  2022-10-18  8:08       ` Teng Long
  2 siblings, 1 reply; 186+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-10-13 10:23 UTC (permalink / raw)
  To: phillip.wood; +Cc: Teng Long, git, --cc=avarab, tenglong.tl


On Thu, Oct 13 2022, Phillip Wood wrote:

> On 13/10/2022 10:36, Ævar Arnfjörð Bjarmason wrote:
>> On Thu, Oct 13 2022, Teng Long wrote:
>> 
>>> From: Teng Long <dyroneteng@gmail.com>
>>>
>>> When "git notes append <object>" is executed, if there is no note in
>>> the given object and the appended note is empty, the command line
>>> prompt will be as follows:
>>>
>>>       "Removing note for object <object>"
>>>
>>> Actually, this tip is not that accurate, because there is no note in
>>> the original <object>, and it also does no remove work on the notes
>>> reference, so let's fix this and give the correct tip.
>>>
>>> Signed-off-by: Teng Long <dyroneteng@gmail.com>
>>> ---
>>>   builtin/notes.c  | 13 +++++++++++--
>>>   t/t3301-notes.sh |  3 ++-
>>>   2 files changed, 13 insertions(+), 3 deletions(-)
>>>
>>> diff --git a/builtin/notes.c b/builtin/notes.c
>>> index 1ca0476a27..cc1e3aa2b6 100644
>>> --- a/builtin/notes.c
>>> +++ b/builtin/notes.c
>>> @@ -567,9 +567,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)
>>>   	struct notes_tree *t;
>>>   	struct object_id object, new_note;
>>>   	const struct object_id *note;
>>> -	char *logmsg;
>>> +	char *logmsg = NULL;
>> Hrm, interesting that (at least my) gcc doesn't catch if we don't
>> NULL-initialize this, but -fanalyzer does (usually it's not needed for
>> such trivial cases0. Anyawy...
>
> I don't think its written to if we take the 'else if' branch added by
> this patch so we need to initialize it for the free() at the end.

Yes, sorry about not being clear. It *does* need to be uninitialized, I
was just narrating my surprise at this not being a case where my
compiler caught it when I was locally testing this.

Then I remembered this is one of those cases where clang is slightly
better at analysis, gcc without -fanalyzer says nothing, but clang by
default will note (if you remove the NULL initialization here):

	builtin/notes.c:641:13: error: variable 'logmsg' is used uninitialized whenever 'if' condition is true [-Werror,-Wsometimes-uninitialized]
	        } else if (!cp.buf.len) {
	                   ^~~~~~~~~~~
	builtin/notes.c:653:7: note: uninitialized use occurs here
	        free(logmsg);
	             ^~~~~~
	builtin/notes.c:641:9: note: remove the 'if' if its condition is always false
	        } else if (!cp.buf.len) {
	               ^~~~~~~~~~~~~~~~~~
	builtin/notes.c:570:14: note: initialize the variable 'logmsg' to silence this warning
	        char *logmsg;
	                    ^
	                     = NULL

Anyway, nothing needs to be changed about the code here, sorry about the
distraction. I should have left it at something like "this NULL
initialization is needed".

>>>   	const char * const *usage;
>>>   	struct note_data d = { 0, 0, NULL, STRBUF_INIT };
>>> +	struct note_data cp = { 0, 0, NULL, STRBUF_INIT };
>> This is probably better "fixed while at it" to set both to use "{
>> .buf =
>> STRBUF_INIT }", rather than copying the pre-C99 pattern.
>
> We only seem to be using cp.buf.len so we can test check if the
> original note was empty so I think it would be better just to add
>
> 	int note_was_empty;
>
> `	...
>
> 	note_was_empty = !d.buf.len

That's a good catch, and one I didn't catch on my read-through, i.e. in
general this seems like a "was the strbuf empty?" pattern, before we
re-append to it.

But playing with it further:
	
	diff --git a/builtin/notes.c b/builtin/notes.c
	index cc1e3aa2b6b..262bbffa375 100644
	--- a/builtin/notes.c
	+++ b/builtin/notes.c
	@@ -570,7 +570,6 @@ static int append_edit(int argc, const char **argv, const char *prefix)
	 	char *logmsg = NULL;
	 	const char * const *usage;
	 	struct note_data d = { 0, 0, NULL, STRBUF_INIT };
	-	struct note_data cp = { 0, 0, NULL, STRBUF_INIT };
	 	struct option options[] = {
	 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
	 			N_("note contents as a string"), PARSE_OPT_NONEG,
	@@ -616,8 +615,6 @@ static int append_edit(int argc, const char **argv, const char *prefix)
	 
	 	prepare_note_data(&object, &d, edit && note ? note : NULL);
	 
	-	strbuf_addbuf(&cp.buf, &d.buf);
	-
	 	if (note && !edit) {
	 		/* Append buf to previous note contents */
	 		unsigned long size;
	@@ -638,21 +635,16 @@ static int append_edit(int argc, const char **argv, const char *prefix)
	 			BUG("combine_notes_overwrite failed");
	 		logmsg = xstrfmt("Notes added by 'git notes %s'", argv[0]);
	 		commit_notes(the_repository, t, logmsg);
	-	} else if (!cp.buf.len) {
	+	} else if (!d.buf.len) {
	 		fprintf(stderr,
	 			_("Both original and appended notes are empty in %s, do nothing\n"),
	 			oid_to_hex(&object));
	 	} else {
	-		fprintf(stderr, _("Removing note for object %s\n"),
	-			oid_to_hex(&object));
	-		remove_note(t, object.hash);
	-		logmsg = xstrfmt("Notes removed by 'git notes %s'", argv[0]);
	-		commit_notes(the_repository, t, logmsg);
	+		BUG("this is not reachable by any test now");
	 	}
	 
	 	free(logmsg);
	 	free_note_data(&d);
	-	free_note_data(&cp);
	 	free_notes(t);
	 	return 0;
	 }
	
This 2/2 makes that "else" test-unreachable, so whatever else we do here
we should start by making sure that by adding the "else if" we still
have test coverage for the "else".

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [RFC PATCH 2/2] notes.c: fixed tip when target and append note are both empty
  2022-10-13 10:23       ` Ævar Arnfjörð Bjarmason
@ 2022-10-15 19:40         ` Phillip Wood
  0 siblings, 0 replies; 186+ messages in thread
From: Phillip Wood @ 2022-10-15 19:40 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Teng Long, git, --cc=avarab, tenglong.tl

Hi Ævar

On 13/10/2022 11:23, Ævar Arnfjörð Bjarmason wrote:
> 
> On Thu, Oct 13 2022, Phillip Wood wrote:
>>>> diff --git a/builtin/notes.c b/builtin/notes.c
>>>> index 1ca0476a27..cc1e3aa2b6 100644
>>>> --- a/builtin/notes.c
>>>> +++ b/builtin/notes.c
>>>> @@ -567,9 +567,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)
>>>>    	struct notes_tree *t;
>>>>    	struct object_id object, new_note;
>>>>    	const struct object_id *note;
>>>> -	char *logmsg;
>>>> +	char *logmsg = NULL;
>>> Hrm, interesting that (at least my) gcc doesn't catch if we don't
>>> NULL-initialize this, but -fanalyzer does (usually it's not needed for
>>> such trivial cases0. Anyawy...
>>
>> I don't think its written to if we take the 'else if' branch added by
>> this patch so we need to initialize it for the free() at the end.
> 
> Yes, sorry about not being clear. It *does* need to be uninitialized, I
> was just narrating my surprise at this not being a case where my
> compiler caught it when I was locally testing this.

Ah I think I slightly misunderstood your comment - I agree it is 
surprising that the compiler didn't catch that.

> 	@@ -638,21 +635,16 @@ static int append_edit(int argc, const char **argv, const char *prefix)
> 	 			BUG("combine_notes_overwrite failed");
> 	 		logmsg = xstrfmt("Notes added by 'git notes %s'", argv[0]);
> 	 		commit_notes(the_repository, t, logmsg);
> 	-	} else if (!cp.buf.len) {
> 	+	} else if (!d.buf.len) {
> 	 		fprintf(stderr,
> 	 			_("Both original and appended notes are empty in %s, do nothing\n"),
> 	 			oid_to_hex(&object));
> 	 	} else {
> 	-		fprintf(stderr, _("Removing note for object %s\n"),
> 	-			oid_to_hex(&object));
> 	-		remove_note(t, object.hash);
> 	-		logmsg = xstrfmt("Notes removed by 'git notes %s'", argv[0]);
> 	-		commit_notes(the_repository, t, logmsg);
> 	+		BUG("this is not reachable by any test now");
> 	 	}
> 	
> This 2/2 makes that "else" test-unreachable, so whatever else we do here
> we should start by making sure that by adding the "else if" we still
> have test coverage for the "else".

Oh so we can just use d.buf.len directly - nicely spotted and kudos for 
checking the test coverage. Looking at the existing tests they are 
checking if an empty note is removed which suggests this patch is 
failing to distinguish between an existing empty note and no note. I 
think we probably need to be doing "else if (note && d.buf.len)" but 
I've not looked very closely.

Best Wishes

Phillip


^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [RFC PATCH 1/2] notes.c: introduce "--no-blankline" option
  2022-10-13  6:06   ` Junio C Hamano
@ 2022-10-17 13:19     ` Teng Long
  0 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2022-10-17 13:19 UTC (permalink / raw)
  To: gitster; +Cc: dyroneteng, git, tenglong.tl

Junio C Hamano <gitster@pobox.com> writes:

> --blank-line::
> --no-blank-line::
> 	Controls if a blank line to split paragraphs is inserted
>         when appending (the default is true).

Will fix, "OPT_BOOL" will automatically deal the "--no" prefix, nice design.

> Use
>
> 	int blankline = 1;
>
> to avoid double negative, which is confusing and error prone.

Perfectly reasonable opinion, will fix.

> 	OPT_BOOL(0, "blank-line", &blankline,
> 		 N_("insert paragraph break before appending to an existing note")),
> Then, the conditional would read more naturally without double
> negation.
>
> 		if (blank_line && d.buf.len && prev_buf && size)

Will apply.

> I do not know and I am not judging (yet) if the goal of the patch is
> sensible (in other words, if we should have such an option), but if
> we were to do so, I would expect the implementation to look more
> like what I outlined above.

In fact, as I was learning about this feature, I thought this parameter might be
helpful, so I tried to send this small patch and maybe  I can get some input.

Thank you so much for taking the time to review this patch.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [RFC PATCH 1/2] notes.c: introduce "--no-blankline" option
  2022-10-13  9:31   ` Ævar Arnfjörð Bjarmason
@ 2022-10-17 13:33     ` Teng Long
  0 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2022-10-17 13:33 UTC (permalink / raw)
  To: avarab; +Cc: --cc=avarab, dyroneteng, git, tenglong.tl

"Ævar Arnfjörð Bjarmason" <avarab@gmail.com> writes:

> Use <<-\EOF here.

As do not escape the heredoc, will apply.

> We usually indent the "EOF" body the same as the "cat", but...
> ... I see this test might be an odd one out, so this is fine.

Yes, the indent sometimes make a little confusion unless you want
to keep it as "<<\EOF".

Thanks for your meticulous reading!

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [RFC PATCH 2/2] notes.c: fixed tip when target and append note are both empty
  2022-10-13  9:36   ` Ævar Arnfjörð Bjarmason
  2022-10-13 10:10     ` Phillip Wood
@ 2022-10-18  3:11     ` Teng Long
  2022-10-18  9:23       ` Ævar Arnfjörð Bjarmason
  1 sibling, 1 reply; 186+ messages in thread
From: Teng Long @ 2022-10-18  3:11 UTC (permalink / raw)
  To: avarab; +Cc: --cc=avarab, dyroneteng, git, tenglong.tl


"Ævar Arnfjörð Bjarmason" <avarab@gmail.com> writes:

> Hrm, interesting that (at least my) gcc doesn't catch if we don't
> NULL-initialize this, but -fanalyzer does (usually it's not needed for
> such trivial cases0. Anyawy...

On my local env the warnings shows , show I change the line (initialize with
NULL to "logmsg").

But it seems like different as the last time I built... However now "suggest
braces around initialization of subobject" appears, is it normal or we should
repair this?

builtin/merge-file.c:29:23: warning: suggest braces around initialization of subobject [-Wmissing-braces]
        mmfile_t mmfs[3] = { 0 };
                             ^
                             {}
builtin/merge-file.c:31:20: warning: suggest braces around initialization of subobject [-Wmissing-braces]
        xmparam_t xmp = { 0 };
                          ^
                          {}
2 warnings generated.
builtin/notes.c:641:13: warning: variable 'logmsg' is used uninitialized whenever 'if' condition is true [-Wsometimes-uninitialized]
        } else if (!cp.buf.len) {
                   ^~~~~~~~~~~
builtin/notes.c:653:7: note: uninitialized use occurs here
        free(logmsg);
             ^~~~~~
builtin/notes.c:641:9: note: remove the 'if' if its condition is always false
        } else if (!cp.buf.len) {
               ^~~~~~~~~~~~~~~~~~
builtin/notes.c:570:14: note: initialize the variable 'logmsg' to silence this warning
        char *logmsg;
                    ^
                     = NULL
1 warning generated.
builtin/submodule--helper.c:1749:56: warning: suggest braces around initialization of subobject [-Wmissing-braces]
        struct list_objects_filter_options filter_options = { 0 };
                                                              ^
                                                              {}
builtin/submodule--helper.c:2623:56: warning: suggest braces around initialization of subobject [-Wmissing-braces]
        struct list_objects_filter_options filter_options = { 0 };
                                                              ^
                                                              {}
builtin/unpack-objects.c:388:26: warning: suggest braces around initialization of subobject [-Wmissing-braces]
        git_zstream zstream = { 0 };
                                ^
                                {}


My gcc version:
   Apple clang version 11.0.0 (clang-1100.0.33.17)
   Target: x86_64-apple-darwin19.6.0
   Thread model: posix


> >  	const char * const *usage;
> >  	struct note_data d = { 0, 0, NULL, STRBUF_INIT };
> > +	struct note_data cp = { 0, 0, NULL, STRBUF_INIT };
>
> This is probably better "fixed while at it" to set both to use "{ .buf =
> STRBUF_INIT }", rather than copying the pre-C99 pattern.


Thanks for figuring this out, will fix both.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [RFC PATCH 2/2] notes.c: fixed tip when target and append note are both empty
  2022-10-13 10:10     ` Phillip Wood
  2022-10-13 10:23       ` Ævar Arnfjörð Bjarmason
@ 2022-10-18  3:25       ` Teng Long
  2022-10-18  8:08       ` Teng Long
  2 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2022-10-18  3:25 UTC (permalink / raw)
  To: phillip.wood123
  Cc: --cc=avarab, avarab, dyroneteng, git, phillip.wood, tenglong.tl

Phillip Wood <phillip.wood123@gmail.com> writes:

> I don't think its written to if we take the 'else if' branch added by
> this patch so we need to initialize it for the free() at the end.

Actually, I didn't get it totally (maybe because my English, sorry for that),
but indeed the 'else if' expose this problem out, so I think to initialize it
is needed.

Thanks.


> We only seem to be using cp.buf.len so we can test check if the original
> note was empty so I think it would be better just to add
>
> 	int note_was_empty;
>
> `	...
>
> 	note_was_empty = !d.buf.len
>
> instead.

Yes, it actually does as you describe above, your suggestion makes it look
better.

Thank you very much for your detailed review.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [RFC PATCH 2/2] notes.c: fixed tip when target and append note are both empty
  2022-10-13 10:10     ` Phillip Wood
  2022-10-13 10:23       ` Ævar Arnfjörð Bjarmason
  2022-10-18  3:25       ` Teng Long
@ 2022-10-18  8:08       ` Teng Long
  2 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2022-10-18  8:08 UTC (permalink / raw)
  To: phillip.wood123
  Cc: --cc=avarab, avarab, dyroneteng, git, phillip.wood, tenglong.tl

Phillip Wood <phillip.wood123@gmail.com> writes:

> I don't think its written to if we take the 'else if' branch added by
> this patch so we need to initialize it for the free() at the end.

Actually, I didn't get it totally (maybe because my English, sorry for that),
but indeed the 'else if' expose this problem out, so I think to initialize it
is needed.

Thanks.

> We only seem to be using cp.buf.len so we can test check if the original
> note was empty so I think it would be better just to add
>
> 	int note_was_empty;
>
> `	...
>
> 	note_was_empty = !d.buf.len
>
> instead.

Yes, it actually does as you describe above, your suggestion makes it look
better, will apply.

Thank you very much for your detailed review.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [RFC PATCH 2/2] notes.c: fixed tip when target and append note are both empty
  2022-10-18  3:11     ` Teng Long
@ 2022-10-18  9:23       ` Ævar Arnfjörð Bjarmason
  0 siblings, 0 replies; 186+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-10-18  9:23 UTC (permalink / raw)
  To: Teng Long; +Cc: --cc=avarab, git, tenglong.tl


On Tue, Oct 18 2022, Teng Long wrote:

> "Ævar Arnfjörð Bjarmason" <avarab@gmail.com> writes:
>
>> Hrm, interesting that (at least my) gcc doesn't catch if we don't
>> NULL-initialize this, but -fanalyzer does (usually it's not needed for
>> such trivial cases0. Anyawy...
>
> On my local env the warnings shows , show I change the line (initialize with
> NULL to "logmsg").
>
> But it seems like different as the last time I built... However now "suggest
> braces around initialization of subobject" appears, is it normal or we should
> repair this?
>
> builtin/merge-file.c:29:23: warning: suggest braces around initialization of subobject [-Wmissing-braces]
>         mmfile_t mmfs[3] = { 0 };
>                              ^
>                              {}
> builtin/merge-file.c:31:20: warning: suggest braces around initialization of subobject [-Wmissing-braces]
>         xmparam_t xmp = { 0 };
>                           ^
>                           {}

The fix for this is in "next": 54795d37d9e (config.mak.dev: disable
suggest braces error on old clang versions, 2022-10-10)


> 2 warnings generated.
> builtin/notes.c:641:13: warning: variable 'logmsg' is used uninitialized whenever 'if' condition is true [-Wsometimes-uninitialized]
>         } else if (!cp.buf.len) {
>                    ^~~~~~~~~~~
> builtin/notes.c:653:7: note: uninitialized use occurs here
>         free(logmsg);
>              ^~~~~~
> builtin/notes.c:641:9: note: remove the 'if' if its condition is always false
>         } else if (!cp.buf.len) {
>                ^~~~~~~~~~~~~~~~~~
> builtin/notes.c:570:14: note: initialize the variable 'logmsg' to silence this warning
>         char *logmsg;
>                     ^
>                      = NULL
> 1 warning generated.

Yes, we should initialize it to NULL, so this is the expected
warning. Clang spots it.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* [PATCH v2 0/3] notes.c: introduce "--blank-line" option
  2022-10-13  5:56 [RFC PATCH 0/2] notes.c: introduce "--no-blankline" option Teng Long
  2022-10-13  5:56 ` [RFC PATCH 1/2] " Teng Long
  2022-10-13  5:56 ` [RFC PATCH 2/2] notes.c: fixed tip when target and append note are both empty Teng Long
@ 2022-11-07 13:57 ` Teng Long
  2022-11-07 13:57   ` [PATCH v2 1/3] " Teng Long
                     ` (4 more replies)
  2 siblings, 5 replies; 186+ messages in thread
From: Teng Long @ 2022-11-07 13:57 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, tenglong.tl

From: Teng Long <dyroneteng@gmail.com>

Diff from RFC Patch v1:

* optimize the commit-msg and docs of introducing new "--blank-line" option.

* drop unreachable code in "append_edit()". Ævar found that some code has been
unreachable in patch v1. I think it's because, after the commit "notes.c: fixed
tip when target and append note are both empty", for example in this patch, the
situation of "removing an existing note" should be impossible unless a BUG when
trying to do append. The tests are passed, but I'm not sure I fully understand
the original design.

Thanks to Junio C Hamano, Ævar Arnfjörð Bjarmason and Phillip Wood for
the help in v1.

Teng Long (3):
  notes.c: introduce "--blank-line" option
  notes.c: fixed tip when target and append note are both empty
  notes.c: drop unreachable code in "append_edit()"

 Documentation/git-notes.txt | 11 +++++++++--
 builtin/notes.c             | 27 +++++++++++++++++++--------
 t/t3301-notes.sh            | 15 ++++++++++++++-
 3 files changed, 42 insertions(+), 11 deletions(-)

Range-diff against v1:
1:  d69bd0a011 ! 1:  2381947abd notes.c: introduce "--no-blankline" option
    @@ Metadata
     Author: Teng Long <dyroneteng@gmail.com>
     
      ## Commit message ##
    -    notes.c: introduce "--no-blankline" option
    +    notes.c: introduce "--blank-line" option
     
         When appending to a given object which has note and if the appended
         note is not empty too, we will insert a blank line at first. The
         blank line serves as a split line, but sometimes we just want to
    -    omit it then append on the heels of the target note.
    +    omit it then append on the heels of the target note. So, we add
    +    a new "OPT_BOOL()" option to determain whether a new blank line
    +    is insert at first.
     
         Signed-off-by: Teng Long <dyroneteng@gmail.com>
     
    @@ Documentation/git-notes.txt: SYNOPSIS
      'git notes' add [-f] [--allow-empty] [-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-blankline] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
    ++'git notes' append [--allow-empty] [--blank-line] [-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.
      
    ++--blank-line::
     +--no-blank-line::
    -+	When appending note, do not insert a blank line between
    -+	the note of given object and the note to be appended.
    ++	Controls if a blank line to split paragraphs is inserted
    ++	when appending (the default is true).
     +
      --ref <ref>::
      	Manipulate the notes tree in <ref>.  This overrides
    @@ builtin/notes.c: static int copy(int argc, const char **argv, const char *prefix
      static int append_edit(int argc, const char **argv, const char *prefix)
      {
      	int allow_empty = 0;
    -+	int no_blankline = 0;
    ++	int blankline = 1;
      	const char *object_ref;
      	struct notes_tree *t;
      	struct object_id object, new_note;
    @@ 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_BOOL(0, "no-blankline", &no_blankline,
    -+			N_("do not initially add a blank line")),
    ++		OPT_BOOL(0, "blank-line", &blankline,
    ++			N_("insert paragraph break before appending to an existing note")),
      		OPT_END()
      	};
      	int edit = !strcmp(argv[0], "edit");
    @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char
      
      		strbuf_grow(&d.buf, size + 1);
     -		if (d.buf.len && prev_buf && size)
    -+		if (!no_blankline && d.buf.len && prev_buf && size)
    ++		if (blankline && d.buf.len && prev_buf && size)
      			strbuf_insertstr(&d.buf, 0, "\n");
      		if (prev_buf && size)
      			strbuf_insert(&d.buf, 0, prev_buf, size);
    @@ t/t3301-notes.sh: test_expect_success 'listing non-existing notes fails' '
      '
      
     +test_expect_success 'append to existing note without a beginning blank line' '
    -+	cat >expect <<-EOF &&
    ++	cat >expect <<-\EOF &&
     +		Initial set of notes
     +		Appended notes
     +	EOF
     +	git notes add -m "Initial set of notes" &&
    -+	git notes append --no-blankline -m "Appended notes" &&
    ++	git notes append --no-blank-line -m "Appended notes" &&
     +	git notes show >actual &&
     +	test_cmp expect actual
     +'
2:  c581cb24b6 ! 2:  5dbe014a09 notes.c: fixed tip when target and append note are both empty
    @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char
     -	char *logmsg;
     +	char *logmsg = NULL;
      	const char * const *usage;
    - 	struct note_data d = { 0, 0, NULL, STRBUF_INIT };
    -+	struct note_data cp = { 0, 0, NULL, STRBUF_INIT };
    +-	struct note_data d = { 0, 0, NULL, STRBUF_INIT };
    ++	struct note_data d = {
    ++		.given = 0,
    ++		.use_editor = 0,
    ++		.edit_path = NULL,
    ++		.buf = STRBUF_INIT
    ++	};
    ++
      	struct option options[] = {
      		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)
    - 
    - 	prepare_note_data(&object, &d, edit && note ? note : NULL);
    - 
    -+	strbuf_addbuf(&cp.buf, &d.buf);
    -+
    - 	if (note && !edit) {
    - 		/* Append buf to previous note contents */
    - 		unsigned long size;
    -@@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char *prefix)
      		if (add_note(t, &object, &new_note, combine_notes_overwrite))
      			BUG("combine_notes_overwrite failed");
      		logmsg = xstrfmt("Notes added by 'git notes %s'", argv[0]);
     +		commit_notes(the_repository, t, logmsg);
    -+	} else if (!cp.buf.len) {
    ++	} else if (!d.buf.len && !note) {
     +		fprintf(stderr,
     +			_("Both original and appended notes are empty in %s, do nothing\n"),
     +			oid_to_hex(&object));
    @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char
      
      	free(logmsg);
      	free_note_data(&d);
    -+	free_note_data(&cp);
    - 	free_notes(t);
    - 	return 0;
    - }
     
      ## t/t3301-notes.sh ##
     @@ t/t3301-notes.sh: test_expect_success 'git notes append == add when there is no existing note' '
-:  ---------- > 3:  2475ea0c04 notes.c: drop unreachable code in "append_edit()"
-- 
2.38.1.383.ge7205ac0a40.dirty


^ permalink raw reply	[flat|nested] 186+ messages in thread

* [PATCH v2 1/3] notes.c: introduce "--blank-line" option
  2022-11-07 13:57 ` [PATCH v2 0/3] notes.c: introduce "--blank-line" option Teng Long
@ 2022-11-07 13:57   ` Teng Long
  2022-11-07 14:45     ` Ævar Arnfjörð Bjarmason
                       ` (2 more replies)
  2022-11-07 13:57   ` [PATCH v2 2/3] notes.c: fixed tip when target and append note are both empty Teng Long
                     ` (3 subsequent siblings)
  4 siblings, 3 replies; 186+ messages in thread
From: Teng Long @ 2022-11-07 13:57 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, tenglong.tl

From: Teng Long <dyroneteng@gmail.com>

When appending to a given object which has note and if the appended
note is not empty too, we will insert a blank line at first. The
blank line serves as a split line, but sometimes we just want to
omit it then append on the heels of the target note. So, we add
a new "OPT_BOOL()" option to determain whether a new blank line
is insert at first.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-notes.txt | 11 +++++++++--
 builtin/notes.c             |  5 ++++-
 t/t3301-notes.sh            | 12 ++++++++++++
 3 files changed, 25 insertions(+), 3 deletions(-)

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index efbc10f0f5..43770ddf84 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -11,7 +11,7 @@ SYNOPSIS
 'git notes' [list [<object>]]
 'git notes' add [-f] [--allow-empty] [-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] [--blank-line] [-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>
@@ -86,7 +86,9 @@ 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.
+	Creates a new notes object if needed. If the note of the given
+	object and the note to be appended are not empty, a blank line
+	will be inserted between them.
 
 edit::
 	Edit the notes for a given object (defaults to HEAD).
@@ -159,6 +161,11 @@ OPTIONS
 	Allow an empty note object to be stored. The default behavior is
 	to automatically remove empty notes.
 
+--blank-line::
+--no-blank-line::
+	Controls if a blank line to split paragraphs is inserted
+	when appending (the default is true).
+
 --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 be51f69225..f0fa337e8c 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -562,6 +562,7 @@ static int copy(int argc, const char **argv, const char *prefix)
 static int append_edit(int argc, const char **argv, const char *prefix)
 {
 	int allow_empty = 0;
+	int blankline = 1;
 	const char *object_ref;
 	struct notes_tree *t;
 	struct object_id object, new_note;
@@ -584,6 +585,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_BOOL(0, "blank-line", &blankline,
+			N_("insert paragraph break before appending to an existing note")),
 		OPT_END()
 	};
 	int edit = !strcmp(argv[0], "edit");
@@ -619,7 +622,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		char *prev_buf = read_object_file(note, &type, &size);
 
 		strbuf_grow(&d.buf, size + 1);
-		if (d.buf.len && prev_buf && size)
+		if (blankline && d.buf.len && prev_buf && size)
 			strbuf_insertstr(&d.buf, 0, "\n");
 		if (prev_buf && size)
 			strbuf_insert(&d.buf, 0, prev_buf, size);
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 3288aaec7d..76beafdeb8 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -521,12 +521,24 @@ test_expect_success 'listing non-existing notes fails' '
 	test_must_be_empty actual
 '
 
+test_expect_success 'append to existing note without a beginning blank line' '
+	cat >expect <<-\EOF &&
+		Initial set of notes
+		Appended notes
+	EOF
+	git notes add -m "Initial set of notes" &&
+	git notes append --no-blank-line -m "Appended notes" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'append to existing note with "git notes append"' '
 	cat >expect <<-EOF &&
 		Initial set of notes
 
 		More notes appended with git notes append
 	EOF
+	git notes remove HEAD &&
 	git notes add -m "Initial set of notes" &&
 	git notes append -m "More notes appended with git notes append" &&
 	git notes show >actual &&
-- 
2.38.1.383.ge7205ac0a40.dirty


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v2 2/3] notes.c: fixed tip when target and append note are both empty
  2022-11-07 13:57 ` [PATCH v2 0/3] notes.c: introduce "--blank-line" option Teng Long
  2022-11-07 13:57   ` [PATCH v2 1/3] " Teng Long
@ 2022-11-07 13:57   ` Teng Long
  2022-11-07 14:40     ` Ævar Arnfjörð Bjarmason
  2022-11-07 13:57   ` [PATCH v2 3/3] notes.c: drop unreachable code in "append_edit()" Teng Long
                     ` (2 subsequent siblings)
  4 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2022-11-07 13:57 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, tenglong.tl

From: Teng Long <dyroneteng@gmail.com>

When "git notes append <object>" is executed, if there is no note in
the given object and the appended note is empty, the command line
prompt will be as follows:

     "Removing note for object <object>"

Actually, this tip is not that accurate, because there is no note in
the original <object>, and it also does no remove work on the notes
reference, so let's fix this and give the correct tip.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/notes.c  | 17 ++++++++++++++---
 t/t3301-notes.sh |  3 ++-
 2 files changed, 16 insertions(+), 4 deletions(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index f0fa337e8c..02b88e54d8 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -567,9 +567,15 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 	struct notes_tree *t;
 	struct object_id object, new_note;
 	const struct object_id *note;
-	char *logmsg;
+	char *logmsg = NULL;
 	const char * const *usage;
-	struct note_data d = { 0, 0, NULL, STRBUF_INIT };
+	struct note_data d = {
+		.given = 0,
+		.use_editor = 0,
+		.edit_path = NULL,
+		.buf = STRBUF_INIT
+	};
+
 	struct option options[] = {
 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
 			N_("note contents as a string"), PARSE_OPT_NONEG,
@@ -634,13 +640,18 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		if (add_note(t, &object, &new_note, combine_notes_overwrite))
 			BUG("combine_notes_overwrite failed");
 		logmsg = xstrfmt("Notes added by 'git notes %s'", argv[0]);
+		commit_notes(the_repository, t, logmsg);
+	} else if (!d.buf.len && !note) {
+		fprintf(stderr,
+			_("Both original and appended notes are empty in %s, do nothing\n"),
+			oid_to_hex(&object));
 	} else {
 		fprintf(stderr, _("Removing note for object %s\n"),
 			oid_to_hex(&object));
 		remove_note(t, object.hash);
 		logmsg = xstrfmt("Notes removed by 'git notes %s'", argv[0]);
+		commit_notes(the_repository, t, logmsg);
 	}
-	commit_notes(the_repository, t, logmsg);
 
 	free(logmsg);
 	free_note_data(&d);
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 76beafdeb8..1a0fd157a5 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -574,7 +574,8 @@ test_expect_success 'git notes append == add when there is no existing note' '
 test_expect_success 'appending empty string to non-existing note does not create note' '
 	git notes remove HEAD &&
 	test_must_fail git notes list HEAD &&
-	git notes append -m "" &&
+	git notes append -m "" >output 2>&1 &&
+	grep "Both original and appended notes are empty" output &&
 	test_must_fail git notes list HEAD
 '
 
-- 
2.38.1.383.ge7205ac0a40.dirty


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v2 3/3] notes.c: drop unreachable code in "append_edit()"
  2022-11-07 13:57 ` [PATCH v2 0/3] notes.c: introduce "--blank-line" option Teng Long
  2022-11-07 13:57   ` [PATCH v2 1/3] " Teng Long
  2022-11-07 13:57   ` [PATCH v2 2/3] notes.c: fixed tip when target and append note are both empty Teng Long
@ 2022-11-07 13:57   ` Teng Long
  2022-11-07 14:41     ` Ævar Arnfjörð Bjarmason
  2022-11-07 14:57   ` [PATCH v2 0/3] notes.c: introduce "--blank-line" option Ævar Arnfjörð Bjarmason
  2022-11-09  9:06   ` [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option Teng Long
  4 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2022-11-07 13:57 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, tenglong.tl

From: Teng Long <dyroneteng@gmail.com>

Situation of removing note shouldn't happen in "append_edit()",
unless it's a bug. So, let's drop the unreachable "else" code
in "append_edit()".

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/notes.c | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index 02b88e54d8..6d42592c79 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -10,6 +10,7 @@
 #include "cache.h"
 #include "config.h"
 #include "builtin.h"
+#include "git-compat-util.h"
 #include "notes.h"
 #include "object-store.h"
 #include "repository.h"
@@ -646,11 +647,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 			_("Both original and appended notes are empty in %s, do nothing\n"),
 			oid_to_hex(&object));
 	} else {
-		fprintf(stderr, _("Removing note for object %s\n"),
-			oid_to_hex(&object));
-		remove_note(t, object.hash);
-		logmsg = xstrfmt("Notes removed by 'git notes %s'", argv[0]);
-		commit_notes(the_repository, t, logmsg);
+		BUG("compute_notes failed");
 	}
 
 	free(logmsg);
-- 
2.38.1.383.ge7205ac0a40.dirty


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 2/3] notes.c: fixed tip when target and append note are both empty
  2022-11-07 13:57   ` [PATCH v2 2/3] notes.c: fixed tip when target and append note are both empty Teng Long
@ 2022-11-07 14:40     ` Ævar Arnfjörð Bjarmason
  2022-11-07 21:51       ` Taylor Blau
  0 siblings, 1 reply; 186+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-11-07 14:40 UTC (permalink / raw)
  To: Teng Long; +Cc: git, tenglong.tl


On Mon, Nov 07 2022, Teng Long wrote:

> From: Teng Long <dyroneteng@gmail.com>
>
> When "git notes append <object>" is executed, if there is no note in
> the given object and the appended note is empty, the command line
> prompt will be as follows:
>
>      "Removing note for object <object>"
>
> Actually, this tip is not that accurate, because there is no note in
> the original <object>, and it also does no remove work on the notes
> reference, so let's fix this and give the correct tip.
>
> Signed-off-by: Teng Long <dyroneteng@gmail.com>
> ---
>  builtin/notes.c  | 17 ++++++++++++++---
>  t/t3301-notes.sh |  3 ++-
>  2 files changed, 16 insertions(+), 4 deletions(-)
>
> diff --git a/builtin/notes.c b/builtin/notes.c
> index f0fa337e8c..02b88e54d8 100644
> --- a/builtin/notes.c
> +++ b/builtin/notes.c
> @@ -567,9 +567,15 @@ static int append_edit(int argc, const char **argv, const char *prefix)
>  	struct notes_tree *t;
>  	struct object_id object, new_note;
>  	const struct object_id *note;
> -	char *logmsg;
> +	char *logmsg = NULL;
>  	const char * const *usage;
> -	struct note_data d = { 0, 0, NULL, STRBUF_INIT };
> +	struct note_data d = {
> +		.given = 0,
> +		.use_editor = 0,
> +		.edit_path = NULL,

Most of this is an unrelated "use designated init" cleanup, which is
good, but let's do that in a different commit if we need it. And then
you can drop all of these...

> +		.buf = STRBUF_INIT

...and only need to keep this one.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 3/3] notes.c: drop unreachable code in "append_edit()"
  2022-11-07 13:57   ` [PATCH v2 3/3] notes.c: drop unreachable code in "append_edit()" Teng Long
@ 2022-11-07 14:41     ` Ævar Arnfjörð Bjarmason
  0 siblings, 0 replies; 186+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-11-07 14:41 UTC (permalink / raw)
  To: Teng Long; +Cc: git, tenglong.tl


On Mon, Nov 07 2022, Teng Long wrote:

> From: Teng Long <dyroneteng@gmail.com>
>
> Situation of removing note shouldn't happen in "append_edit()",
> unless it's a bug. So, let's drop the unreachable "else" code
> in "append_edit()".
>
> Signed-off-by: Teng Long <dyroneteng@gmail.com>
> ---
>  builtin/notes.c | 7 ++-----
>  1 file changed, 2 insertions(+), 5 deletions(-)
>
> diff --git a/builtin/notes.c b/builtin/notes.c
> index 02b88e54d8..6d42592c79 100644
> --- a/builtin/notes.c
> +++ b/builtin/notes.c
> @@ -10,6 +10,7 @@
>  #include "cache.h"
>  #include "config.h"
>  #include "builtin.h"
> +#include "git-compat-util.h"

Huh? cache.h includes this already, why are we doing it again?

>  #include "notes.h"
>  #include "object-store.h"
>  #include "repository.h"
> @@ -646,11 +647,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
>  			_("Both original and appended notes are empty in %s, do nothing\n"),
>  			oid_to_hex(&object));
>  	} else {
> -		fprintf(stderr, _("Removing note for object %s\n"),
> -			oid_to_hex(&object));
> -		remove_note(t, object.hash);
> -		logmsg = xstrfmt("Notes removed by 'git notes %s'", argv[0]);
> -		commit_notes(the_repository, t, logmsg);
> +		BUG("compute_notes failed");
>  	}

Let's squash this into 2/3. So you were adding to it then, but we didn't
need to add this commit_notes() at all?

Also, there you did:

	-	char *logmsg;
	+	char *logmsg = NULL;

But here we see that it's only used in the initial "if" arm, so the
reason to have it declared earlier has disappeared in this 3/3. Instead
just have 2/3 do:

	if (...) {
		char *logmsg;
		[...]

		logmsg = xstrfmt(...);
	        commit_notes(....);
		free(logmsg);
	} else if (...) {
		...
	}

This commit also shows that there wasn't any reason for your "struct
note_data d" cleanup, i.e. we weren't adding a field or anything...

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 1/3] notes.c: introduce "--blank-line" option
  2022-11-07 13:57   ` [PATCH v2 1/3] " Teng Long
@ 2022-11-07 14:45     ` Ævar Arnfjörð Bjarmason
  2022-11-07 15:45       ` Eric Sunshine
                         ` (2 more replies)
  2022-11-07 15:06     ` Ævar Arnfjörð Bjarmason
  2022-11-07 21:47     ` Taylor Blau
  2 siblings, 3 replies; 186+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-11-07 14:45 UTC (permalink / raw)
  To: Teng Long; +Cc: git, tenglong.tl


On Mon, Nov 07 2022, Teng Long wrote:

> When appending to a given object which has note and if the appended
> note is not empty too, we will insert a blank line at first. The
> blank line serves as a split line, but sometimes we just want to
> omit it then append on the heels of the target note. So, we add
> a new "OPT_BOOL()" option to determain whether a new blank line
> is insert at first.
>
> Signed-off-by: Teng Long <dyroneteng@gmail.com>
> ---
>  Documentation/git-notes.txt | 11 +++++++++--
>  builtin/notes.c             |  5 ++++-
>  t/t3301-notes.sh            | 12 ++++++++++++
>  3 files changed, 25 insertions(+), 3 deletions(-)
>
> diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
> index efbc10f0f5..43770ddf84 100644
> --- a/Documentation/git-notes.txt
> +++ b/Documentation/git-notes.txt
> @@ -11,7 +11,7 @@ SYNOPSIS
>  'git notes' [list [<object>]]
>  'git notes' add [-f] [--allow-empty] [-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] [--blank-line] [-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>
> @@ -86,7 +86,9 @@ 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.
> +	Creates a new notes object if needed. If the note of the given
> +	object and the note to be appended are not empty, a blank line
> +	will be inserted between them.
>  
>  edit::
>  	Edit the notes for a given object (defaults to HEAD).
> @@ -159,6 +161,11 @@ OPTIONS
>  	Allow an empty note object to be stored. The default behavior is
>  	to automatically remove empty notes.
>  
> +--blank-line::
> +--no-blank-line::
> +	Controls if a blank line to split paragraphs is inserted
> +	when appending (the default is true).

Just make this:

	--no-blank-line:
		Suppress the insertion of a blank line before the
		inserted notes.

Or something, i.e. when adding a "true by default" let's add a "no-..." variant directly.

>  	int allow_empty = 0;
> +	int blankline = 1;

So keep this...

>  	const char *object_ref;
>  	struct notes_tree *t;
>  	struct object_id object, new_note;
> @@ -584,6 +585,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_BOOL(0, "blank-line", &blankline,

...and just make this "no-blank-line", and parse_options() will do the
right thing. I.e. PARSE_OPT_NONEG is implied.

> -		if (d.buf.len && prev_buf && size)
> +		if (blankline && d.buf.len && prev_buf && size)
>  			strbuf_insertstr(&d.buf, 0, "\n");

Maybe this needs to be elaborated in the docs? I.e. it sounds as if
we'll insert a \n unconditionally, which this shows isn't the case.

>  		if (prev_buf && size)
>  			strbuf_insert(&d.buf, 0, prev_buf, size);
> diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
> index 3288aaec7d..76beafdeb8 100755
> --- a/t/t3301-notes.sh
> +++ b/t/t3301-notes.sh
> @@ -521,12 +521,24 @@ test_expect_success 'listing non-existing notes fails' '
>  	test_must_be_empty actual
>  '
>  
> +test_expect_success 'append to existing note without a beginning blank line' '
> +	cat >expect <<-\EOF &&
> +		Initial set of notes
> +		Appended notes
> +	EOF
> +	git notes add -m "Initial set of notes" &&
> +	git notes append --no-blank-line -m "Appended notes" &&
> +	git notes show >actual &&
> +	test_cmp expect actual
> +'
> +
>  test_expect_success 'append to existing note with "git notes append"' '
>  	cat >expect <<-EOF &&
>  		Initial set of notes
>  
>  		More notes appended with git notes append
>  	EOF
> +	git notes remove HEAD &&

This should be a test_when_finished "", for the previous test, otherwise
this one will presumably fail if you use the "wrong" --run="" arguments
to skip the last test.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 0/3] notes.c: introduce "--blank-line" option
  2022-11-07 13:57 ` [PATCH v2 0/3] notes.c: introduce "--blank-line" option Teng Long
                     ` (2 preceding siblings ...)
  2022-11-07 13:57   ` [PATCH v2 3/3] notes.c: drop unreachable code in "append_edit()" Teng Long
@ 2022-11-07 14:57   ` Ævar Arnfjörð Bjarmason
  2022-11-09  7:05     ` Teng Long
  2022-11-09  7:06     ` Teng Long
  2022-11-09  9:06   ` [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option Teng Long
  4 siblings, 2 replies; 186+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-11-07 14:57 UTC (permalink / raw)
  To: Teng Long; +Cc: git, tenglong.tl


On Mon, Nov 07 2022, Teng Long wrote:

> From: Teng Long <dyroneteng@gmail.com>
> [...]
> * drop unreachable code in "append_edit()". Ævar found that some code has been
> unreachable in patch v1. I think it's because, after the commit "notes.c: fixed
> tip when target and append note are both empty", for example in this patch, the
> situation of "removing an existing note" should be impossible unless a BUG when
> trying to do append. The tests are passed, but I'm not sure I fully understand
> the original design.

I suggested squashing that BUG() in 3/3 into 2/3, but reading this again
I think it should come first.

I.e. this seems to me like the code in cd067d3bf4e (Builtin-ify
git-notes, 2010-02-13) might have just been blindly carried forward to
both "create" and "edit" in 52694cdabbf (builtin/notes: split
create_note() to clarify add vs. remove logic, 2014-11-12).

But it would be good to have confirmation, e.g. if you check out
52694cdabbf and remove that "Removing note" branch from add() does it
fail tests at the time, but not in the case of append_edit()?


^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 1/3] notes.c: introduce "--blank-line" option
  2022-11-07 13:57   ` [PATCH v2 1/3] " Teng Long
  2022-11-07 14:45     ` Ævar Arnfjörð Bjarmason
@ 2022-11-07 15:06     ` Ævar Arnfjörð Bjarmason
  2022-11-08  6:32       ` Teng Long
  2022-11-07 21:47     ` Taylor Blau
  2 siblings, 1 reply; 186+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-11-07 15:06 UTC (permalink / raw)
  To: Teng Long; +Cc: git, tenglong.tl


On Mon, Nov 07 2022, Teng Long wrote:

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

Something I didn't notice on the first reading:

> @@ -619,7 +622,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
>  		char *prev_buf = read_object_file(note, &type, &size);
>  
>  		strbuf_grow(&d.buf, size + 1);

Here we're growing it to add the size plus the \n.

> -		if (d.buf.len && prev_buf && size)
> +		if (blankline && d.buf.len && prev_buf && size)
>  			strbuf_insertstr(&d.buf, 0, "\n");

But now we're not going to make use of that "+ 1" anymore...

>  		if (prev_buf && size)
>  			strbuf_insert(&d.buf, 0, prev_buf, size);

..and here the prev_buf && size condition is duplicated.

I think the right thin to do is to just drop the strbuf_grow() here
altogether, since strbuf_insert() will handle it for us, but that would
make sense before this change, so maybe in pre-cleanup?

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 1/3] notes.c: introduce "--blank-line" option
  2022-11-07 14:45     ` Ævar Arnfjörð Bjarmason
@ 2022-11-07 15:45       ` Eric Sunshine
  2022-11-07 17:22         ` Ævar Arnfjörð Bjarmason
  2022-11-08  3:45       ` Teng Long
  2022-11-08 13:06       ` Teng Long
  2 siblings, 1 reply; 186+ messages in thread
From: Eric Sunshine @ 2022-11-07 15:45 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: Teng Long, git, tenglong.tl

On Mon, Nov 7, 2022 at 9:56 AM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:
> On Mon, Nov 07 2022, Teng Long wrote:
> > When appending to a given object which has note and if the appended
> > note is not empty too, we will insert a blank line at first. The
> > blank line serves as a split line, but sometimes we just want to
> > omit it then append on the heels of the target note. So, we add
> > a new "OPT_BOOL()" option to determain whether a new blank line
> > is insert at first.
> >
> > Signed-off-by: Teng Long <dyroneteng@gmail.com>
> > ---
> > diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
> > @@ -159,6 +161,11 @@ OPTIONS
> > +--blank-line::
> > +--no-blank-line::
> > +     Controls if a blank line to split paragraphs is inserted
> > +     when appending (the default is true).
>
> Just make this:
>
>         --no-blank-line:
>                 Suppress the insertion of a blank line before the
>                 inserted notes.
>
> Or something, i.e. when adding a "true by default" let's add a "no-..." variant directly.

This is the exact opposite of Junio's advice[1], isn't it?

[1]: https://lore.kernel.org/git/xmqqsfjsi7eq.fsf@gitster.g/

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 1/3] notes.c: introduce "--blank-line" option
  2022-11-07 15:45       ` Eric Sunshine
@ 2022-11-07 17:22         ` Ævar Arnfjörð Bjarmason
  2022-11-07 21:46           ` Taylor Blau
  0 siblings, 1 reply; 186+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-11-07 17:22 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: Teng Long, git, tenglong.tl


On Mon, Nov 07 2022, Eric Sunshine wrote:

> On Mon, Nov 7, 2022 at 9:56 AM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:
>> On Mon, Nov 07 2022, Teng Long wrote:
>> > When appending to a given object which has note and if the appended
>> > note is not empty too, we will insert a blank line at first. The
>> > blank line serves as a split line, but sometimes we just want to
>> > omit it then append on the heels of the target note. So, we add
>> > a new "OPT_BOOL()" option to determain whether a new blank line
>> > is insert at first.
>> >
>> > Signed-off-by: Teng Long <dyroneteng@gmail.com>
>> > ---
>> > diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
>> > @@ -159,6 +161,11 @@ OPTIONS
>> > +--blank-line::
>> > +--no-blank-line::
>> > +     Controls if a blank line to split paragraphs is inserted
>> > +     when appending (the default is true).
>>
>> Just make this:
>>
>>         --no-blank-line:
>>                 Suppress the insertion of a blank line before the
>>                 inserted notes.
>>
>> Or something, i.e. when adding a "true by default" let's add a "no-..." variant directly.
>
> This is the exact opposite of Junio's advice[1], isn't it?
>
> [1]: https://lore.kernel.org/git/xmqqsfjsi7eq.fsf@gitster.g/

I read that as him mainly talking about what we name the variable (which
I agree with, but didn't comment on here). I'm talking about what
interface is exposed to the user.

I.e. both concerns can be satisfied, but whether my suggestion is
sensible UX is another matter...

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 1/3] notes.c: introduce "--blank-line" option
  2022-11-07 17:22         ` Ævar Arnfjörð Bjarmason
@ 2022-11-07 21:46           ` Taylor Blau
  2022-11-07 22:36             ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 186+ messages in thread
From: Taylor Blau @ 2022-11-07 21:46 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Eric Sunshine, Teng Long, git, tenglong.tl

On Mon, Nov 07, 2022 at 06:22:34PM +0100, Ævar Arnfjörð Bjarmason wrote:
>
> On Mon, Nov 07 2022, Eric Sunshine wrote:
>
> > On Mon, Nov 7, 2022 at 9:56 AM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:
> >> On Mon, Nov 07 2022, Teng Long wrote:
> >> > When appending to a given object which has note and if the appended
> >> > note is not empty too, we will insert a blank line at first. The
> >> > blank line serves as a split line, but sometimes we just want to
> >> > omit it then append on the heels of the target note. So, we add
> >> > a new "OPT_BOOL()" option to determain whether a new blank line
> >> > is insert at first.
> >> >
> >> > Signed-off-by: Teng Long <dyroneteng@gmail.com>
> >> > ---
> >> > diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
> >> > @@ -159,6 +161,11 @@ OPTIONS
> >> > +--blank-line::
> >> > +--no-blank-line::
> >> > +     Controls if a blank line to split paragraphs is inserted
> >> > +     when appending (the default is true).
> >>
> >> Just make this:
> >>
> >>         --no-blank-line:
> >>                 Suppress the insertion of a blank line before the
> >>                 inserted notes.
> >>
> >> Or something, i.e. when adding a "true by default" let's add a "no-..." variant directly.
> >
> > This is the exact opposite of Junio's advice[1], isn't it?
> >
> > [1]: https://lore.kernel.org/git/xmqqsfjsi7eq.fsf@gitster.g/
>
> I read that as him mainly talking about what we name the variable (which
> I agree with, but didn't comment on here). I'm talking about what
> interface is exposed to the user.

Having --blank-line as an option is convenient for scripting, so I'd err
on the side of the original interpretation of Junio's suggestion.

We can clearly support '--[no-]blank-line' and allow latter arguments to
override previous ones. The documentation is fine as-is.

Thanks,
Taylor

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 1/3] notes.c: introduce "--blank-line" option
  2022-11-07 13:57   ` [PATCH v2 1/3] " Teng Long
  2022-11-07 14:45     ` Ævar Arnfjörð Bjarmason
  2022-11-07 15:06     ` Ævar Arnfjörð Bjarmason
@ 2022-11-07 21:47     ` Taylor Blau
  2022-11-08  7:36       ` Teng Long
  2 siblings, 1 reply; 186+ messages in thread
From: Taylor Blau @ 2022-11-07 21:47 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, tenglong.tl

On Mon, Nov 07, 2022 at 09:57:03PM +0800, Teng Long wrote:
> From: Teng Long <dyroneteng@gmail.com>
>
> When appending to a given object which has note and if the appended
> note is not empty too, we will insert a blank line at first. The
> blank line serves as a split line, but sometimes we just want to
> omit it then append on the heels of the target note. So, we add
> a new "OPT_BOOL()" option to determain whether a new blank line
> is insert at first.

The discussion about whether this should support '--blank-line' or just
be '--no-blank-line' aside, I'm curious what motivated this change?

Thanks,
Taylor

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 2/3] notes.c: fixed tip when target and append note are both empty
  2022-11-07 14:40     ` Ævar Arnfjörð Bjarmason
@ 2022-11-07 21:51       ` Taylor Blau
  2022-11-07 22:33         ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 186+ messages in thread
From: Taylor Blau @ 2022-11-07 21:51 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: Teng Long, git, tenglong.tl

On Mon, Nov 07, 2022 at 03:40:03PM +0100, Ævar Arnfjörð Bjarmason wrote:
> > @@ -567,9 +567,15 @@ static int append_edit(int argc, const char **argv, const char *prefix)
> >  	struct notes_tree *t;
> >  	struct object_id object, new_note;
> >  	const struct object_id *note;
> > -	char *logmsg;
> > +	char *logmsg = NULL;
> >  	const char * const *usage;
> > -	struct note_data d = { 0, 0, NULL, STRBUF_INIT };
> > +	struct note_data d = {
> > +		.given = 0,
> > +		.use_editor = 0,
> > +		.edit_path = NULL,
>
> Most of this is an unrelated "use designated init" cleanup, which is
> good, but let's do that in a different commit if we need it. And then
> you can drop all of these...
>
> > +		.buf = STRBUF_INIT
>
> ...and only need to keep this one.

I don't mind seeing the cleanup here, but I don't see why we need to
keep that portion of the hunk at all for this series.

IOW, your "...and only need to keep this one" does not make sense to me.

Changing "char *logmsg" to be initialized to NULL is important, though,
since we now only set it conditionally still free it unconditionally. So
that should be kept.

Thanks,
Taylor

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 2/3] notes.c: fixed tip when target and append note are both empty
  2022-11-07 21:51       ` Taylor Blau
@ 2022-11-07 22:33         ` Ævar Arnfjörð Bjarmason
  2022-11-07 22:45           ` Taylor Blau
  2022-11-08  8:55           ` Teng Long
  0 siblings, 2 replies; 186+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-11-07 22:33 UTC (permalink / raw)
  To: Taylor Blau; +Cc: Teng Long, git, tenglong.tl


On Mon, Nov 07 2022, Taylor Blau wrote:

> On Mon, Nov 07, 2022 at 03:40:03PM +0100, Ævar Arnfjörð Bjarmason wrote:
>> > @@ -567,9 +567,15 @@ static int append_edit(int argc, const char **argv, const char *prefix)
>> >  	struct notes_tree *t;
>> >  	struct object_id object, new_note;
>> >  	const struct object_id *note;
>> > -	char *logmsg;
>> > +	char *logmsg = NULL;
>> >  	const char * const *usage;
>> > -	struct note_data d = { 0, 0, NULL, STRBUF_INIT };
>> > +	struct note_data d = {
>> > +		.given = 0,
>> > +		.use_editor = 0,
>> > +		.edit_path = NULL,
>>
>> Most of this is an unrelated "use designated init" cleanup, which is
>> good, but let's do that in a different commit if we need it. And then
>> you can drop all of these...
>>
>> > +		.buf = STRBUF_INIT
>>
>> ...and only need to keep this one.
>
> I don't mind seeing the cleanup here, but I don't see why we need to
> keep that portion of the hunk at all for this series.
>
> IOW, your "...and only need to keep this one" does not make sense to me.

I was still on my initial read-through, and was assuming that it was a
prep change for the 3/3 adding a new field, before I saw the 3/3...

> Changing "char *logmsg" to be initialized to NULL is important, though,
> since we now only set it conditionally still free it unconditionally. So
> that should be kept.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 1/3] notes.c: introduce "--blank-line" option
  2022-11-07 21:46           ` Taylor Blau
@ 2022-11-07 22:36             ` Ævar Arnfjörð Bjarmason
  2022-11-08  0:32               ` Taylor Blau
  0 siblings, 1 reply; 186+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-11-07 22:36 UTC (permalink / raw)
  To: Taylor Blau; +Cc: Eric Sunshine, Teng Long, git, tenglong.tl


On Mon, Nov 07 2022, Taylor Blau wrote:

> On Mon, Nov 07, 2022 at 06:22:34PM +0100, Ævar Arnfjörð Bjarmason wrote:
>>
>> On Mon, Nov 07 2022, Eric Sunshine wrote:
>>
>> > On Mon, Nov 7, 2022 at 9:56 AM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:
>> >> On Mon, Nov 07 2022, Teng Long wrote:
>> >> > When appending to a given object which has note and if the appended
>> >> > note is not empty too, we will insert a blank line at first. The
>> >> > blank line serves as a split line, but sometimes we just want to
>> >> > omit it then append on the heels of the target note. So, we add
>> >> > a new "OPT_BOOL()" option to determain whether a new blank line
>> >> > is insert at first.
>> >> >
>> >> > Signed-off-by: Teng Long <dyroneteng@gmail.com>
>> >> > ---
>> >> > diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
>> >> > @@ -159,6 +161,11 @@ OPTIONS
>> >> > +--blank-line::
>> >> > +--no-blank-line::
>> >> > +     Controls if a blank line to split paragraphs is inserted
>> >> > +     when appending (the default is true).
>> >>
>> >> Just make this:
>> >>
>> >>         --no-blank-line:
>> >>                 Suppress the insertion of a blank line before the
>> >>                 inserted notes.
>> >>
>> >> Or something, i.e. when adding a "true by default" let's add a "no-..." variant directly.
>> >
>> > This is the exact opposite of Junio's advice[1], isn't it?
>> >
>> > [1]: https://lore.kernel.org/git/xmqqsfjsi7eq.fsf@gitster.g/
>>
>> I read that as him mainly talking about what we name the variable (which
>> I agree with, but didn't comment on here). I'm talking about what
>> interface is exposed to the user.
>
> Having --blank-line as an option is convenient for scripting, so I'd err
> on the side of the original interpretation of Junio's suggestion.

I see from re-reading my reply that I was conflating two things. What I
*meant* to suggest is this:

When an option is not the default, and we provide a way to turn it off
we usually document that as:

	--no-foo:
		Don't do foo.

See e.g. "git commit --no-edit", and "git clone --no-checkout".

But this is orthagonal to what you call the option in the source, and
whether your variable is "inverted". I.e. in both those cases we have a
"--edit" and "--checkout", but when we prepare the options for
parse_options() we do (or the equivalent of):

	int no_checkout = 0;
	OPT_BOOL('n', "no-checkout", &option_no_checkout,

And:

	int edit = 1;
	OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),

So, I'm (now) saying I don't care which form we use in the sources, but
that' it's useful to document things as e.g.:

	--no-checkout::
        	No checkout of HEAD is performed after the clone is complete.

Instead of e.g.:

	--no-checkout:
	--checkout:
 		Do we do a checkout when we clone (doing a checkout is
 		the default).

Because the former convention shows the user at a glance which of the
two is the default.

> We can clearly support '--[no-]blank-line' and allow latter arguments to
> override previous ones.

We'll support both either way, i.e. parse_options() detects that the
name starts with "no-", so the negation of a "no-checkout" isn't
"--no-no-checkout", but a "--checkout".

> The documentation is fine as-is.

I think the above form would make it a bit better, but just my 0.02:

	--no-blank-line::
		Don't add an extra "\n" between the body of the commit
		and the note.

Or something...

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 2/3] notes.c: fixed tip when target and append note are both empty
  2022-11-07 22:33         ` Ævar Arnfjörð Bjarmason
@ 2022-11-07 22:45           ` Taylor Blau
  2022-11-08  8:55           ` Teng Long
  1 sibling, 0 replies; 186+ messages in thread
From: Taylor Blau @ 2022-11-07 22:45 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Taylor Blau, Teng Long, git, tenglong.tl

On Mon, Nov 07, 2022 at 11:33:33PM +0100, Ævar Arnfjörð Bjarmason wrote:
> > I don't mind seeing the cleanup here, but I don't see why we need to
> > keep that portion of the hunk at all for this series.
> >
> > IOW, your "...and only need to keep this one" does not make sense to me.
>
> I was still on my initial read-through, and was assuming that it was a
> prep change for the 3/3 adding a new field, before I saw the 3/3...

Gotcha, makes sense. Thanks.

Thanks,
Taylor

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 1/3] notes.c: introduce "--blank-line" option
  2022-11-07 22:36             ` Ævar Arnfjörð Bjarmason
@ 2022-11-08  0:32               ` Taylor Blau
  0 siblings, 0 replies; 186+ messages in thread
From: Taylor Blau @ 2022-11-08  0:32 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Taylor Blau, Eric Sunshine, Teng Long, git, tenglong.tl

On Mon, Nov 07, 2022 at 11:36:58PM +0100, Ævar Arnfjörð Bjarmason wrote:
> So, I'm (now) saying I don't care which form we use in the sources, but
> that' it's useful to document things as e.g.:
>
> 	--no-checkout::
>         	No checkout of HEAD is performed after the clone is complete.
>
> Instead of e.g.:
>
> 	--no-checkout:
> 	--checkout:
>  		Do we do a checkout when we clone (doing a checkout is
>  		the default).
>
> Because the former convention shows the user at a glance which of the
> two is the default.

Thanks for clarifying. For what it's worth, I slightly prefer
documenting both options as long as we clearly specify which is the
default behavior (and thus implied).

But I definitely do not feel strongly about it.

Thanks,
Taylor

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 1/3] notes.c: introduce "--blank-line" option
  2022-11-07 14:45     ` Ævar Arnfjörð Bjarmason
  2022-11-07 15:45       ` Eric Sunshine
@ 2022-11-08  3:45       ` Teng Long
  2022-11-08 13:06       ` Teng Long
  2 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2022-11-08  3:45 UTC (permalink / raw)
  To: avarab; +Cc: dyroneteng, git, tenglong.tl

"Ævar Arnfjörð Bjarmason" <avarab@gmail.com> writes:

> Just make this:
>
> 	--no-blank-line:
> 		Suppress the insertion of a blank line before the
> 		inserted notes.
>
> Or something, i.e. when adding a "true by default" let's add a "no-..." variant
> directly.
>
> ...

Yes, that's what I propose to do originally, but I can accept either NEG or POS
way. So, I will hang this up for a while and try to hear more inputs.

> > -		if (d.buf.len && prev_buf && size)
> > +		if (blankline && d.buf.len && prev_buf && size)
> >  			strbuf_insertstr(&d.buf, 0, "\n");
>
> Maybe this needs to be elaborated in the docs? I.e. it sounds as if
> we'll insert a \n unconditionally, which this shows isn't the case.

The current doc add the content about this circumstance corresponing to this
"if", which describes as:

>  append::
>  	Append to the notes of an existing object (defaults to HEAD).
> -	Creates a new notes object if needed.
> +	Creates a new notes object if needed. If the note of the given
> +	object and the note to be appended are not empty, a blank line
> +	will be inserted between them.

... so you mean we should add more detailed information here?

> This should be a test_when_finished "", for the previous test, otherwise
> this one will presumably fail if you use the "wrong" --run="" arguments
> to skip the last test.

Yes, I agree, will fix (although if we replace it by "test_when_finished,
because of the dependency of the previous repo status, execute
`sh t3301-notes.sh --run=60` also failed).

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 1/3] notes.c: introduce "--blank-line" option
  2022-11-07 15:06     ` Ævar Arnfjörð Bjarmason
@ 2022-11-08  6:32       ` Teng Long
  0 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2022-11-08  6:32 UTC (permalink / raw)
  To: avarab; +Cc: dyroneteng, git, tenglong.tl

"Ævar Arnfjörð Bjarmason" <avarab@gmail.com> writes:

> I think the right thin to do is to just drop the strbuf_grow() here
> altogether, since strbuf_insert() will handle it for us, but that would
> make sense before this change, so maybe in pre-cleanup?

Sorry for ignoring this line of code. I think you are right, and I will
make a separate pre-cleanup commit for this in next round.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 1/3] notes.c: introduce "--blank-line" option
  2022-11-07 21:47     ` Taylor Blau
@ 2022-11-08  7:36       ` Teng Long
  0 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2022-11-08  7:36 UTC (permalink / raw)
  To: me; +Cc: avarab, dyroneteng, git, tenglong.tl

Taylor Blau <me@ttaylorr.com> writes:

* Actually that's my suggestion, because I found when I use "git notes appends"
to append new content to an existing note. It will always insert a blank line
between the existing and the new. Does "append" represent "a blank line first
then the content"? If the user does not want it, there may be no way to
circumvent this on "append" at present. For example:

--------example start-------

Here is a commit with notes (from a openstack community repo), the notes is as:

commit ...(HEAD -> master, origin/master, origin/HEAD)
Author: ...
Date:   ...

    .....
    Change-Id: ....

Notes (review):
    Code-Review+2: yatin <ykarel@redhat.com>
    Code-Review+2: chandan kumar <chkumar@redhat.com>
    Workflow+1: chandan kumar <chkumar@redhat.com>
    Verified+1: RDO Third Party CI <dmsimard+rdothirdparty@redhat.com>
    Verified+2: Zuul
    Submitted-by: Zuul
    Submitted-at: Wed, 28 Sep 2022 12:58:46 +0000
    Reviewed-on: https://review.opendev.org/c/openstack/tripleo-quickstart/+/859516
    Project: openstack/tripleo-quickstart
    Branch: refs/heads/master

So, if user wants to append this notes, actually will add a blank line, maybe a
surprise. But I may not represent most users, so I added this compatible option,
maybe better to let the use choose, then send this as a RFC patch.

--------example finished-------

* Another reason is, I found that if I append nothing to a commit (here on my case
is HEAD) which doesn't exist notes on it, the output "Removing note for
object" shows something a little intesting to me, because there is no note to
remove, that's what 2/3 does. The following are specific examples:

--------example start-------

➜  git-notes-test git:(tl/test) ✗ git --no-pager log HEAD -n 1
commit d5a0127568e89239bb02f78d44dfa0427e726103 (HEAD -> tl/test)
Author: tenglong.tl <tenglong.tl@alibaba-inc.com>
Date:   Thu Oct 20 18:09:16 2022 +0800

    111
➜  git-notes-test git:(tl/test) ✗ git notes append -m ""
Removing note for object d5a0127568e89239bb02f78d44dfa0427e726103

--------example finished-------

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 2/3] notes.c: fixed tip when target and append note are both empty
  2022-11-07 22:33         ` Ævar Arnfjörð Bjarmason
  2022-11-07 22:45           ` Taylor Blau
@ 2022-11-08  8:55           ` Teng Long
  1 sibling, 0 replies; 186+ messages in thread
From: Teng Long @ 2022-11-08  8:55 UTC (permalink / raw)
  To: avarab; +Cc: dyroneteng, git, me, tenglong.tl

"Ævar Arnfjörð Bjarmason" <avarab@gmail.com> writes:

>  I was still on my initial read-through, and was assuming that it was a
> prep change for the 3/3 adding a new field, before I saw the 3/3...

Yes, you are right, that previous patch add a "copy" of "d", but now it's
unnecessary, so I think make a pre-cleanup commit which includes the "char
*logmsg = NULL" and the "designated init" of "struct note_data d" looks
nice.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 1/3] notes.c: introduce "--blank-line" option
  2022-11-07 14:45     ` Ævar Arnfjörð Bjarmason
  2022-11-07 15:45       ` Eric Sunshine
  2022-11-08  3:45       ` Teng Long
@ 2022-11-08 13:06       ` Teng Long
  2022-11-08 13:22         ` Ævar Arnfjörð Bjarmason
  2 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2022-11-08 13:06 UTC (permalink / raw)
  To: avarab; +Cc: dyroneteng, git, tenglong.tl

"Ævar Arnfjörð Bjarmason" <avarab@gmail.com> writes:

> >  	int allow_empty = 0;
> > +	int blankline = 1;
>
> So keep this...
>
> >  	const char *object_ref;
> >G  	struct notes_tree *t;
> >  	struct object_id object, new_note;
> > @@ -584,6 +585,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_BOOL(0, "blank-line", &blankline,
>
> ...and just make this "no-blank-line", and parse_options() will do the
> right thing. I.e. PARSE_OPT_NONEG is implied.

Sorry for another question. By the explanation of "api-parse-options.txt" :

`OPT_BOOL(short, long, &int_var, description)`::
	Introduce a boolean option. `int_var` is set to one with
	`--option` and set to zero with `--no-option`

I think it means the same as "parse_options() will do right thing" as you said
to me , but...after the modification the effect is opposite:

diff --git a/builtin/notes.c b/builtin/notes.c
index a6273781fb8..73427ea8dff 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -562,6 +562,7 @@ static int copy(int argc, const char **argv, const char *prefix)
 static int append_edit(int argc, const char **argv, const char *prefix)
 {
        int allow_empty = 0;
+       int blankline = 1;
        const char *object_ref;
        struct notes_tree *t;
        struct object_id object, new_note;
@@ -584,6 +585,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_BOOL(0, "no-blank-line", &blankline,
+                       N_("insert paragraph break before appending to an existing note")),
                OPT_END()
        };
        int edit = !strcmp(argv[0], "edit");
@@ -618,7 +621,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
                enum object_type type;
                char *prev_buf = read_object_file(note, &type, &size);

-               if (d.buf.len && prev_buf && size)
+               if (blankline && d.buf.len && prev_buf && size)
                        strbuf_insertstr(&d.buf, 0, "\n");
                if (prev_buf && size)
                        strbuf_insert(&d.buf, 0, prev_buf, size);

----
So, I am a bit confused and I guess maybe somewhere I misunderstood or didn't
notice some details.

Thanks.

^ permalink raw reply related	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 1/3] notes.c: introduce "--blank-line" option
  2022-11-08 13:06       ` Teng Long
@ 2022-11-08 13:22         ` Ævar Arnfjörð Bjarmason
  2022-11-09  6:35           ` Teng Long
  0 siblings, 1 reply; 186+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-11-08 13:22 UTC (permalink / raw)
  To: Teng Long; +Cc: git, tenglong.tl


On Tue, Nov 08 2022, Teng Long wrote:

> "Ævar Arnfjörð Bjarmason" <avarab@gmail.com> writes:
>
>> >  	int allow_empty = 0;
>> > +	int blankline = 1;
>>
>> So keep this...
>>
>> >  	const char *object_ref;
>> >G  	struct notes_tree *t;
>> >  	struct object_id object, new_note;
>> > @@ -584,6 +585,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_BOOL(0, "blank-line", &blankline,
>>
>> ...and just make this "no-blank-line", and parse_options() will do the
>> right thing. I.e. PARSE_OPT_NONEG is implied.
>
> Sorry for another question. By the explanation of "api-parse-options.txt" :
>
> `OPT_BOOL(short, long, &int_var, description)`::
> 	Introduce a boolean option. `int_var` is set to one with
> 	`--option` and set to zero with `--no-option`
>
> I think it means the same as "parse_options() will do right thing" as you said
> to me , but...after the modification the effect is opposite:
>
> diff --git a/builtin/notes.c b/builtin/notes.c
> index a6273781fb8..73427ea8dff 100644
> --- a/builtin/notes.c
> +++ b/builtin/notes.c
> @@ -562,6 +562,7 @@ static int copy(int argc, const char **argv, const char *prefix)
>  static int append_edit(int argc, const char **argv, const char *prefix)
>  {
>         int allow_empty = 0;
> +       int blankline = 1;
>         const char *object_ref;
>         struct notes_tree *t;
>         struct object_id object, new_note;
> @@ -584,6 +585,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_BOOL(0, "no-blank-line", &blankline,
> +                       N_("insert paragraph break before appending to an existing note")),
>                 OPT_END()
>         };
>         int edit = !strcmp(argv[0], "edit");
> @@ -618,7 +621,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
>                 enum object_type type;
>                 char *prev_buf = read_object_file(note, &type, &size);
>
> -               if (d.buf.len && prev_buf && size)
> +               if (blankline && d.buf.len && prev_buf && size)
>                         strbuf_insertstr(&d.buf, 0, "\n");
>                 if (prev_buf && size)
>                         strbuf_insert(&d.buf, 0, prev_buf, size);
>
> ----
> So, I am a bit confused and I guess maybe somewhere I misunderstood or didn't
> notice some details.
>
> Thanks.

Sorry, I meant that in both cases it will expose the same options to the
user: --blank-line and --no-blank-line. I.e. if you create options
named:

	"x" "x-y"

Their negations are: --no-x and --no-x-y. But if their names are:

	"x" "no-x"

The negations are:

	--no-x and --x

But as your example shows that's unrelated to whether the *variable in
the code* is negated.

So however you structure the code, which would be:

	int blankline = 1:
        [...]
	OPT_BOOL(0, "blankline", &blankline, [...]);

Or:

	int no_blankline = 0:
        [...]
	OPT_BOOL(0, "no-blankline", &no_blankline, [...]);

The documentation could in both cases say:

	--no-blankline:
		describe the non-default[...]

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 1/3] notes.c: introduce "--blank-line" option
  2022-11-08 13:22         ` Ævar Arnfjörð Bjarmason
@ 2022-11-09  6:35           ` Teng Long
  0 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2022-11-09  6:35 UTC (permalink / raw)
  To: avarab; +Cc: dyroneteng, git, tenglong.tl

"Ævar Arnfjörð Bjarmason" <avarab@gmail.com> writes:

> Sorry, I meant that in both cases it will expose the same options to the
> user: --blank-line and --no-blank-line. I.e. if you create options
> named:
>
> 	"x" "x-y"
>
> Their negations are: --no-x and --no-x-y. But if their names are:
>
> 	"x" "no-x"
>
> The negations are:
>
> 	--no-x and --x
>
> But as your example shows that's unrelated to whether the *variable in
> the code* is negated.
>
> So however you structure the code, which would be:
>
> 	int blankline = 1:
>         [...]
> 	OPT_BOOL(0, "blankline", &blankline, [...]);
>
> Or:
>
> 	int no_blankline = 0:
>         [...]
> 	OPT_BOOL(0, "no-blankline", &no_blankline, [...]);
>
> The documentation could in both cases say:
>
> 	--no-blankline:
> 		describe the non-default[...]

Thank you for the detailed explanation, now it's clear for me.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 0/3] notes.c: introduce "--blank-line" option
  2022-11-07 14:57   ` [PATCH v2 0/3] notes.c: introduce "--blank-line" option Ævar Arnfjörð Bjarmason
@ 2022-11-09  7:05     ` Teng Long
  2022-11-09  7:06     ` Teng Long
  1 sibling, 0 replies; 186+ messages in thread
From: Teng Long @ 2022-11-09  7:05 UTC (permalink / raw)
  To: avarab; +Cc: dyroneteng, git, tenglong.tl

"Ævar Arnfjörð Bjarmason" <avarab@gmail.com> writes:

> > From: Teng Long <dyroneteng@gmail.com>
> > [...]
> > * drop unreachable code in "append_edit()". Ævar found that some code has been
> > unreachable in patch v1. I think it's because, after the commit "notes.c: fixed
> > tip when target and append note are both empty", for example in this patch, the
> > situation of "removing an existing note" should be impossible unless a BUG when
> > trying to do append. The tests are passed, but I'm not sure I fully understand
> > the original design.
>
> I suggested squashing that BUG() in 3/3 into 2/3, but reading this again
> I think it should come first.
>
> I.e. this seems to me like the code in cd067d3bf4e (Builtin-ify
> git-notes, 2010-02-13) might have just been blindly carried forward to
> both "create" and "edit" in 52694cdabbf (builtin/notes: split
> create_note() to clarify add vs. remove logic, 2014-11-12).
>
> But it would be good to have confirmation, e.g. if you check out
> 52694cdabbf and remove that "Removing note" branch from add() does it
> fail tests at the time, but not in the case of append_edit()?

Thanks for mention that. I look back to 52694cdabbf and remove "Removing note"
will make the test fail, because the notes operation "append" is different with
"add", the latter supports to overwrite the existing note then let the
"removing" happen (e.g. execute `git notes add -f -F /dev/null` on an existing
note), but the former will not because it only does "appends" but not doing
"overwrites".

So, I think may just remove the "Removing note" code in append_edit() is OK.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v2 0/3] notes.c: introduce "--blank-line" option
  2022-11-07 14:57   ` [PATCH v2 0/3] notes.c: introduce "--blank-line" option Ævar Arnfjörð Bjarmason
  2022-11-09  7:05     ` Teng Long
@ 2022-11-09  7:06     ` Teng Long
  1 sibling, 0 replies; 186+ messages in thread
From: Teng Long @ 2022-11-09  7:06 UTC (permalink / raw)
  To: avarab; +Cc: dyroneteng, git, tenglong.tl

"Ævar Arnfjörð Bjarmason" <avarab@gmail.com> writes:

> > From: Teng Long <dyroneteng@gmail.com>
> > [...]
> > * drop unreachable code in "append_edit()". Ævar found that some code has been
> > unreachable in patch v1. I think it's because, after the commit "notes.c: fixed
> > tip when target and append note are both empty", for example in this patch, the
> > situation of "removing an existing note" should be impossible unless a BUG when
> > trying to do append. The tests are passed, but I'm not sure I fully understand
> > the original design.
>
> I suggested squashing that BUG() in 3/3 into 2/3, but reading this again
> I think it should come first.
>
> I.e. this seems to me like the code in cd067d3bf4e (Builtin-ify
> git-notes, 2010-02-13) might have just been blindly carried forward to
> both "create" and "edit" in 52694cdabbf (builtin/notes: split
> create_note() to clarify add vs. remove logic, 2014-11-12).
>
> But it would be good to have confirmation, e.g. if you check out
> 52694cdabbf and remove that "Removing note" branch from add() does it
> fail tests at the time, but not in the case of append_edit()?

Thanks for mention that. I look back to 52694cdabbf and remove "Removing note"
will make the test fail, because the notes operation "append" is different with
"add", the latter supports to overwrite the existing note then let the
"removing" happen (e.g. execute `git notes add -f -F /dev/null` on an existing
note), but the former will not because it only does "appends" but not doing
"overwrites".

So, I think may just remove the "Removing note" code in append_edit() is OK.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option
  2022-11-07 13:57 ` [PATCH v2 0/3] notes.c: introduce "--blank-line" option Teng Long
                     ` (3 preceding siblings ...)
  2022-11-07 14:57   ` [PATCH v2 0/3] notes.c: introduce "--blank-line" option Ævar Arnfjörð Bjarmason
@ 2022-11-09  9:06   ` Teng Long
  2022-11-09  9:06     ` [PATCH v3 1/5] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
                       ` (7 more replies)
  4 siblings, 8 replies; 186+ messages in thread
From: Teng Long @ 2022-11-09  9:06 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, tenglong.tl

From: Teng Long <dyroneteng@gmail.com>

Diff since v2:

* [5/5] make "--no-blank-line" in doc.
* [1/5] [2/5][3/5]do some cleanups and split to serval independent commit.
* [3/5] futher explain in commit-msg why we can drop it in "append" but not "add".
* [4/5] [4/5] use "test_when_finished" to cleanup notes in tests.

Thanks.

Teng Long (5):
  notes.c: cleanup 'strbuf_grow' call in 'append_edit'
  notes.c: cleanup for "designated init" and "char ptr init"
  notes.c: drop unreachable code in 'append_edit()'
  notes.c: provide tips when target and append note are both empty
  notes.c: introduce "--no-blank-line" option

 Documentation/git-notes.txt | 10 ++++++++--
 builtin/notes.c             | 20 ++++++++++----------
 t/t3301-notes.sh            | 18 ++++++++++++++++--
 3 files changed, 34 insertions(+), 14 deletions(-)

Range-diff against v2:
-:  ---------- > 1:  8ae58934a1 notes.c: cleanup 'strbuf_grow' call in 'append_edit'
-:  ---------- > 2:  a53576ea88 notes.c: cleanup for "designated init" and "char ptr init"
-:  ---------- > 3:  62a952ba3e notes.c: drop unreachable code in 'append_edit()'
-:  ---------- > 4:  0d8ce0b14b notes.c: provide tips when target and append note are both empty
1:  2381947abd ! 5:  196e80358e notes.c: introduce "--blank-line" option
    @@ Metadata
     Author: Teng Long <dyroneteng@gmail.com>
     
      ## Commit message ##
    -    notes.c: introduce "--blank-line" option
    +    notes.c: introduce "--no-blank-line" option
     
         When appending to a given object which has note and if the appended
         note is not empty too, we will insert a blank line at first. The
    @@ Documentation/git-notes.txt: SYNOPSIS
      'git notes' add [-f] [--allow-empty] [-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] [--blank-line] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
    ++'git notes' append [--allow-empty] [--no-blank-line] [-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.
      
    -+--blank-line::
     +--no-blank-line::
    -+	Controls if a blank line to split paragraphs is inserted
    -+	when appending (the default is true).
    ++	Do not insert a blank line before the inserted notes (insert
    ++	a blank line is the default).
     +
      --ref <ref>::
      	Manipulate the notes tree in <ref>.  This overrides
    @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char
      	};
      	int edit = !strcmp(argv[0], "edit");
     @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char *prefix)
    + 		enum object_type type;
      		char *prev_buf = read_object_file(note, &type, &size);
      
    - 		strbuf_grow(&d.buf, size + 1);
     -		if (d.buf.len && prev_buf && size)
     +		if (blankline && d.buf.len && prev_buf && size)
      			strbuf_insertstr(&d.buf, 0, "\n");
    @@ t/t3301-notes.sh: test_expect_success 'listing non-existing notes fails' '
      '
      
     +test_expect_success 'append to existing note without a beginning blank line' '
    ++	test_when_finished git notes remove HEAD &&
     +	cat >expect <<-\EOF &&
     +		Initial set of notes
     +		Appended notes
    @@ t/t3301-notes.sh: test_expect_success 'listing non-existing notes fails' '
      
      		More notes appended with git notes append
      	EOF
    -+	git notes remove HEAD &&
    ++
      	git notes add -m "Initial set of notes" &&
      	git notes append -m "More notes appended with git notes append" &&
      	git notes show >actual &&
2:  5dbe014a09 < -:  ---------- notes.c: fixed tip when target and append note are both empty
3:  2475ea0c04 < -:  ---------- notes.c: drop unreachable code in "append_edit()"
-- 
2.38.1.386.g196e80358e8


^ permalink raw reply	[flat|nested] 186+ messages in thread

* [PATCH v3 1/5] notes.c: cleanup 'strbuf_grow' call in 'append_edit'
  2022-11-09  9:06   ` [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option Teng Long
@ 2022-11-09  9:06     ` Teng Long
  2022-11-09  9:06     ` [PATCH v3 2/5] notes.c: cleanup for "designated init" and "char ptr init" Teng Long
                       ` (6 subsequent siblings)
  7 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2022-11-09  9:06 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, tenglong.tl

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.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/notes.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index be51f69225..f4bab3b2f2 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -618,7 +618,6 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		enum object_type type;
 		char *prev_buf = read_object_file(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.38.1.386.g196e80358e8


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v3 2/5] notes.c: cleanup for "designated init" and "char ptr init"
  2022-11-09  9:06   ` [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option Teng Long
  2022-11-09  9:06     ` [PATCH v3 1/5] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
@ 2022-11-09  9:06     ` Teng Long
  2022-11-09  9:06     ` [PATCH v3 3/5] notes.c: drop unreachable code in 'append_edit()' Teng Long
                       ` (5 subsequent siblings)
  7 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2022-11-09  9:06 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, tenglong.tl

From: Teng Long <dyroneteng@gmail.com>

Let's do some cleanup for the following two places in "append_edit()".

The first place is "char *logmsg;" need to be initialized with NULL.
The second place is "struct note_data d = { 0, 0, NULL, STRBUF_INIT };"
could be replaced with designated init format.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/notes.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index f4bab3b2f2..485439c901 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -566,9 +566,9 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 	struct notes_tree *t;
 	struct object_id object, new_note;
 	const struct object_id *note;
-	char *logmsg;
+	char *logmsg = NULL;
 	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.38.1.386.g196e80358e8


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v3 3/5] notes.c: drop unreachable code in 'append_edit()'
  2022-11-09  9:06   ` [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option Teng Long
  2022-11-09  9:06     ` [PATCH v3 1/5] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
  2022-11-09  9:06     ` [PATCH v3 2/5] notes.c: cleanup for "designated init" and "char ptr init" Teng Long
@ 2022-11-09  9:06     ` Teng Long
  2022-11-09  9:06     ` [PATCH v3 4/5] notes.c: provide tips when target and append note are both empty Teng Long
                       ` (4 subsequent siblings)
  7 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2022-11-09  9:06 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, tenglong.tl

From: Teng Long <dyroneteng@gmail.com>

Situation of note "removing" shouldn't happen in 'append_edit()',
unless it's a bug. So, let's drop the unreachable "else" code
in "append_edit()".

The notes operation "append" is different with "add", the latter
supports to overwrite the existing note then let the "removing"
happen (e.g. execute `git notes add -f -F /dev/null` on an existing
note), but the former will not because it only does "appends" but
not doing "overwrites".

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/notes.c | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index 485439c901..8c3a1cd913 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -630,13 +630,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		if (add_note(t, &object, &new_note, combine_notes_overwrite))
 			BUG("combine_notes_overwrite failed");
 		logmsg = xstrfmt("Notes added by 'git notes %s'", argv[0]);
-	} else {
-		fprintf(stderr, _("Removing note for object %s\n"),
-			oid_to_hex(&object));
-		remove_note(t, object.hash);
-		logmsg = xstrfmt("Notes removed by 'git notes %s'", argv[0]);
+		commit_notes(the_repository, t, logmsg);
 	}
-	commit_notes(the_repository, t, logmsg);
 
 	free(logmsg);
 	free_note_data(&d);
-- 
2.38.1.386.g196e80358e8


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v3 4/5] notes.c: provide tips when target and append note are both empty
  2022-11-09  9:06   ` [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option Teng Long
                       ` (2 preceding siblings ...)
  2022-11-09  9:06     ` [PATCH v3 3/5] notes.c: drop unreachable code in 'append_edit()' Teng Long
@ 2022-11-09  9:06     ` Teng Long
  2022-11-09  9:06     ` [PATCH v3 5/5] notes.c: introduce "--no-blank-line" option Teng Long
                       ` (3 subsequent siblings)
  7 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2022-11-09  9:06 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, tenglong.tl

From: Teng Long <dyroneteng@gmail.com>

When "git notes append <object>" is executed, if there is no note in
the given object and the appended note is empty too, we could print
the exact tips to end-user.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/notes.c  | 5 ++++-
 t/t3301-notes.sh | 5 +++--
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index 8c3a1cd913..a6273781fb 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -631,7 +631,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 			BUG("combine_notes_overwrite failed");
 		logmsg = xstrfmt("Notes added by 'git notes %s'", argv[0]);
 		commit_notes(the_repository, t, logmsg);
-	}
+	} else if (!d.buf.len && !note)
+		fprintf(stderr,
+			_("Both original and appended notes are empty in %s, do nothing\n"),
+			oid_to_hex(&object));
 
 	free(logmsg);
 	free_note_data(&d);
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 3288aaec7d..e7807e052a 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -552,6 +552,7 @@ test_expect_success 'appending empty string does not change existing note' '
 '
 
 test_expect_success 'git notes append == add when there is no existing note' '
+	test_when_finished git notes remove HEAD &&
 	git notes remove HEAD &&
 	test_must_fail git notes list HEAD &&
 	git notes append -m "Initial set of notes${LF}${LF}More notes appended with git notes append" &&
@@ -560,9 +561,9 @@ test_expect_success 'git notes append == add when there is no existing note' '
 '
 
 test_expect_success 'appending empty string to non-existing note does not create note' '
-	git notes remove HEAD &&
 	test_must_fail git notes list HEAD &&
-	git notes append -m "" &&
+	git notes append -m "" >output 2>&1 &&
+	grep "Both original and appended notes are empty" output &&
 	test_must_fail git notes list HEAD
 '
 
-- 
2.38.1.386.g196e80358e8


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v3 5/5] notes.c: introduce "--no-blank-line" option
  2022-11-09  9:06   ` [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option Teng Long
                       ` (3 preceding siblings ...)
  2022-11-09  9:06     ` [PATCH v3 4/5] notes.c: provide tips when target and append note are both empty Teng Long
@ 2022-11-09  9:06     ` Teng Long
  2022-11-28 14:20     ` [PATCH v3 0/5] " Teng Long
                       ` (2 subsequent siblings)
  7 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2022-11-09  9:06 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, tenglong.tl

From: Teng Long <dyroneteng@gmail.com>

When appending to a given object which has note and if the appended
note is not empty too, we will insert a blank line at first. The
blank line serves as a split line, but sometimes we just want to
omit it then append on the heels of the target note. So, we add
a new "OPT_BOOL()" option to determain whether a new blank line
is insert at first.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-notes.txt | 10 ++++++++--
 builtin/notes.c             |  5 ++++-
 t/t3301-notes.sh            | 13 +++++++++++++
 3 files changed, 25 insertions(+), 3 deletions(-)

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index efbc10f0f5..50b198c2b2 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -11,7 +11,7 @@ SYNOPSIS
 'git notes' [list [<object>]]
 'git notes' add [-f] [--allow-empty] [-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-blank-line] [-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>
@@ -86,7 +86,9 @@ 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.
+	Creates a new notes object if needed. If the note of the given
+	object and the note to be appended are not empty, a blank line
+	will be inserted between them.
 
 edit::
 	Edit the notes for a given object (defaults to HEAD).
@@ -159,6 +161,10 @@ OPTIONS
 	Allow an empty note object to be stored. The default behavior is
 	to automatically remove empty notes.
 
+--no-blank-line::
+	Do not insert a blank line before the inserted notes (insert
+	a blank line is the default).
+
 --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 a6273781fb..f38e6e8b04 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -562,6 +562,7 @@ static int copy(int argc, const char **argv, const char *prefix)
 static int append_edit(int argc, const char **argv, const char *prefix)
 {
 	int allow_empty = 0;
+	int blankline = 1;
 	const char *object_ref;
 	struct notes_tree *t;
 	struct object_id object, new_note;
@@ -584,6 +585,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_BOOL(0, "blank-line", &blankline,
+			N_("insert paragraph break before appending to an existing note")),
 		OPT_END()
 	};
 	int edit = !strcmp(argv[0], "edit");
@@ -618,7 +621,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		enum object_type type;
 		char *prev_buf = read_object_file(note, &type, &size);
 
-		if (d.buf.len && prev_buf && size)
+		if (blankline && d.buf.len && prev_buf && size)
 			strbuf_insertstr(&d.buf, 0, "\n");
 		if (prev_buf && size)
 			strbuf_insert(&d.buf, 0, prev_buf, size);
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index e7807e052a..dedad93a2f 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -521,12 +521,25 @@ test_expect_success 'listing non-existing notes fails' '
 	test_must_be_empty actual
 '
 
+test_expect_success 'append to existing note without a beginning blank line' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect <<-\EOF &&
+		Initial set of notes
+		Appended notes
+	EOF
+	git notes add -m "Initial set of notes" &&
+	git notes append --no-blank-line -m "Appended notes" &&
+	git notes show >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'append to existing note with "git notes append"' '
 	cat >expect <<-EOF &&
 		Initial set of notes
 
 		More notes appended with git notes append
 	EOF
+
 	git notes add -m "Initial set of notes" &&
 	git notes append -m "More notes appended with git notes append" &&
 	git notes show >actual &&
-- 
2.38.1.386.g196e80358e8


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option
  2022-11-09  9:06   ` [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option Teng Long
                       ` (4 preceding siblings ...)
  2022-11-09  9:06     ` [PATCH v3 5/5] notes.c: introduce "--no-blank-line" option Teng Long
@ 2022-11-28 14:20     ` Teng Long
  2022-11-29  1:10       ` Junio C Hamano
  2022-11-29 12:57     ` Teng Long
  2023-01-12  2:48     ` [PATCH v4 0/5] notes.c: introduce "--separator" optio Teng Long
  7 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2022-11-28 14:20 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, tenglong.tl

> Teng Long (5):
>   notes.c: cleanup 'strbuf_grow' call in 'append_edit'
>   notes.c: cleanup for "designated init" and "char ptr init"
>   notes.c: drop unreachable code in 'append_edit()'
>   notes.c: provide tips when target and append note are both empty
>   notes.c: introduce "--no-blank-line" option

I'm not sure if this patch series should continue, and if there are no
updated comments it will be temporarily suspended.

Thanks for the reviews on the past patches.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option
  2022-11-28 14:20     ` [PATCH v3 0/5] " Teng Long
@ 2022-11-29  1:10       ` Junio C Hamano
  2022-11-29 22:53         ` Taylor Blau
  0 siblings, 1 reply; 186+ messages in thread
From: Junio C Hamano @ 2022-11-29  1:10 UTC (permalink / raw)
  To: Taylor Blau; +Cc: Teng Long, avarab, git, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

>> Teng Long (5):
>>   notes.c: cleanup 'strbuf_grow' call in 'append_edit'
>>   notes.c: cleanup for "designated init" and "char ptr init"
>>   notes.c: drop unreachable code in 'append_edit()'
>>   notes.c: provide tips when target and append note are both empty
>>   notes.c: introduce "--no-blank-line" option
>
> I'm not sure if this patch series should continue, and if there are no
> updated comments it will be temporarily suspended.
>
> Thanks for the reviews on the past patches.

Taylor, This topic was marked to "expect" a reroll in the second
issue of November "What's cooking" report you did.  Do you recall
what remaining works there were?

I personally do not have much opinion on this topic, other than that
"--no-blank-link" would be a horrible name (i.e. uses concrete words
to pretend that it clearly describes what it does, but is utterly
unclear where these blank lines are etc.) for the feature to help
end-users discover it.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option
  2022-11-09  9:06   ` [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option Teng Long
                       ` (5 preceding siblings ...)
  2022-11-28 14:20     ` [PATCH v3 0/5] " Teng Long
@ 2022-11-29 12:57     ` Teng Long
  2022-11-29 13:19       ` Junio C Hamano
  2023-01-12  2:48     ` [PATCH v4 0/5] notes.c: introduce "--separator" optio Teng Long
  7 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2022-11-29 12:57 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, tenglong.tl

Junio C Hamano <gitster@pobox.com> writes:

> I personally do not have much opinion on this topic, other than that
> "--no-blank-link" would be a horrible name (i.e. uses concrete words
> to pretend that it clearly describes what it does, but is utterly
> unclear where these blank lines are etc.) for the feature to help
> end-users discover it.

I have some a candidates might like '--newline' and '--no-newline', or
'splitted-newline' or 'no-splitted-newline', but I think the latter is
a little long.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option
  2022-11-29 12:57     ` Teng Long
@ 2022-11-29 13:19       ` Junio C Hamano
  2022-12-15 12:48         ` Teng Long
  0 siblings, 1 reply; 186+ messages in thread
From: Junio C Hamano @ 2022-11-29 13:19 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> Junio C Hamano <gitster@pobox.com> writes:
>
>> I personally do not have much opinion on this topic, other than that
>> "--no-blank-link" would be a horrible name (i.e. uses concrete words
>> to pretend that it clearly describes what it does, but is utterly
>> unclear where these blank lines are etc.) for the feature to help
>> end-users discover it.
>
> I have some a candidates might like '--newline' and '--no-newline', or
> 'splitted-newline' or 'no-splitted-newline', but I think the latter is
> a little long.
>
> Thanks.

I do not care much between blank and newline.  

Both alone are equally horrible, in that the option would have no
effect when "git notes edit" is used and spawns an editor, or "git
notes append -m one -m two" is used and the command adds the second
paragraph whose text is "two".

Just like "git commit", the argument to each "-m" option becomes a
separate paragraph by default.  I personally feel that those who
want to make them separate lines deserve to have an option like this
one, so that they can do

 $ git notes add -m foo HEAD
 $ git notes append \
	--each-message-is-line-not-paragraph -m bar -m baz
 $ git notes show HEAD
 foo
 bar
 baz

but the point is "blank-line" or "newline" does not say which
newline in the resulting notes object you are mucking with.  It is
not like in this example:

 $ git notes add -m "title of the note"
 $ git notes append --no-blank-line -m "body of the note

 that span multiple

 lines" HEAD

you are removving the blank lines embedded in the body of the
message, but from the option name, it is hard to guess that.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option
  2022-11-29  1:10       ` Junio C Hamano
@ 2022-11-29 22:53         ` Taylor Blau
  0 siblings, 0 replies; 186+ messages in thread
From: Taylor Blau @ 2022-11-29 22:53 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Teng Long, avarab, git, tenglong.tl

On Tue, Nov 29, 2022 at 10:10:52AM +0900, Junio C Hamano wrote:
> Teng Long <dyroneteng@gmail.com> writes:
>
> >> Teng Long (5):
> >>   notes.c: cleanup 'strbuf_grow' call in 'append_edit'
> >>   notes.c: cleanup for "designated init" and "char ptr init"
> >>   notes.c: drop unreachable code in 'append_edit()'
> >>   notes.c: provide tips when target and append note are both empty
> >>   notes.c: introduce "--no-blank-line" option
> >
> > I'm not sure if this patch series should continue, and if there are no
> > updated comments it will be temporarily suspended.
> >
> > Thanks for the reviews on the past patches.
>
> Taylor, This topic was marked to "expect" a reroll in the second
> issue of November "What's cooking" report you did.  Do you recall
> what remaining works there were?

Looking at the dates, I sent that WC on 2022-11-08, which means that I
most likely was referencing the discussion between the author and Ævar.

The following WC on 2022-11-14 likely should have changed the status to
"Waiting for review".

Thanks,
Taylor

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option
  2022-11-29 13:19       ` Junio C Hamano
@ 2022-12-15 12:48         ` Teng Long
  2022-12-19  3:03           ` Eric Sunshine
  0 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2022-12-15 12:48 UTC (permalink / raw)
  To: gitster; +Cc: avarab, dyroneteng, git, tenglong.tl

Junio C Hamano <gitster@pobox.com> writes:

> but the point is "blank-line" or "newline" does not say which
> newline in the resulting notes object you are mucking with.  It is
> not like in this example:
>
>  $ git notes add -m "title of the note"
>  $ git notes append --no-blank-line -m "body of the note
>
>  that span multiple
>
>  lines" HEAD
>
> you are removving the blank lines embedded in the body of the
> message, but from the option name, it is hard to guess that.

Fine, I see. Maybe "insert-starting-newline" or "insert-initial-newline". If we
could not find a suitable naming for this, it's ok for me to hang up [5/5] (a
little struggle for me to find a better name for this now in fact (⊙ˍ⊙).),
because except it, the [4/5] still an active bugfix in my opinion.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option
  2022-12-15 12:48         ` Teng Long
@ 2022-12-19  3:03           ` Eric Sunshine
  2022-12-21  9:16             ` Teng Long
  2022-12-22  9:30             ` Teng Long
  0 siblings, 2 replies; 186+ messages in thread
From: Eric Sunshine @ 2022-12-19  3:03 UTC (permalink / raw)
  To: Teng Long; +Cc: gitster, avarab, git, tenglong.tl

On Thu, Dec 15, 2022 at 7:58 AM Teng Long <dyroneteng@gmail.com> wrote:
> Junio C Hamano <gitster@pobox.com> writes:
>
> > but the point is "blank-line" or "newline" does not say which
> > newline in the resulting notes object you are mucking with.  It is
> > not like in this example:
> > [...]
> > you are removving the blank lines embedded in the body of the
> > message, but from the option name, it is hard to guess that.
>
> Fine, I see. Maybe "insert-starting-newline" or "insert-initial-newline". If we
> could not find a suitable naming for this, it's ok for me to hang up [5/5] (a
> little struggle for me to find a better name for this now in fact (⊙ˍ⊙).),
> because except it, the [4/5] still an active bugfix in my opinion.

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.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option
  2022-12-19  3:03           ` Eric Sunshine
@ 2022-12-21  9:16             ` Teng Long
  2022-12-21 11:35               ` Junio C Hamano
  2022-12-22  9:30             ` Teng Long
  1 sibling, 1 reply; 186+ messages in thread
From: Teng Long @ 2022-12-21  9:16 UTC (permalink / raw)
  To: sunshine; +Cc: avarab, dyroneteng, git, gitster, tenglong.tl

Eric Sunshine <sunshine@sunshineco.com> writes:

> 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.

Your idea is an enhancement of the original one of me. I think it's a suitable
name and I could implement it, maybe we could hear about Junio's advice of the
naming?

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option
  2022-12-21  9:16             ` Teng Long
@ 2022-12-21 11:35               ` Junio C Hamano
  0 siblings, 0 replies; 186+ messages in thread
From: Junio C Hamano @ 2022-12-21 11:35 UTC (permalink / raw)
  To: Teng Long; +Cc: sunshine, avarab, git, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> Eric Sunshine <sunshine@sunshineco.com> writes:
>
>> 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.
>
> Your idea is an enhancement of the original one of me. I think it's a suitable
> name and I could implement it, maybe we could hear about Junio's advice of the
> naming?

Yeah, saying "separator" clarifies what that empty line is meant to
be (i.e. it is an inter-paragraph separator), and is much better
than "newline" or "blankline", I would think.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option
  2022-12-19  3:03           ` Eric Sunshine
  2022-12-21  9:16             ` Teng Long
@ 2022-12-22  9:30             ` Teng Long
  2022-12-23  1:36               ` Eric Sunshine
  1 sibling, 1 reply; 186+ messages in thread
From: Teng Long @ 2022-12-22  9:30 UTC (permalink / raw)
  To: sunshine; +Cc: avarab, dyroneteng, git, gitster, tenglong.tl

Eric Sunshine <sunshine@sunshineco.com> writes:

> 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.

There is another question for me, if the separator we passed contains "\n"
string , the argument the cmd receives will need to tranfer to '\n' character
instead to make sure it's a linebreak but not a "\n" instead.

So maybe like:

diff --git a/builtin/notes.c b/builtin/notes.c
index f38e6e8b04..64ee64eff7 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -559,15 +559,52 @@ static int copy(int argc, const char **argv, const char *prefix)
        return retval;
 }

+static void insert_separator(struct strbuf *message, const char *separator)
+{
+       struct strbuf transfered = STRBUF_INIT;
+
+       if (!separator)
+               strbuf_insertstr(message, 0, "\n");
+       else if (!strcmp("", separator))
+               return;
+       else {
+               while (*separator) {
+                       if (*separator == '\\'){
+                               switch (separator[1]) {
+                                       case 'n':
+                                               strbuf_addstr(&transfered, "\n");
+                                               separator++;
+                                               break;
+                                       case 'r':
+                                               strbuf_addstr(&transfered, "\r");
+                                               separator++;
+                                               break;
+                                       case 't':
+                                               strbuf_addstr(&transfered, "\t");
+                                               separator++;
+                                               break;
+                                       default:
+                                               strbuf_addch(&transfered, *separator);
+                               }
+                       } else {
+                               strbuf_addch(&transfered, *separator);
+                       }
+                       separator++;
+               }
+               strbuf_insertstr(message, 0, transfered.buf);
+               strbuf_release(&transfered);
+       }
+}
+
 static int append_edit(int argc, const char **argv, const char *prefix)
 {
        int allow_empty = 0;
-       int blankline = 1;
        const char *object_ref;
        struct notes_tree *t;
        struct object_id object, new_note;
        const struct object_id *note;
        char *logmsg = NULL;
+       const char *separator = NULL;
        const char * const *usage;
        struct note_data d = { .buf = STRBUF_INIT };
        struct option options[] = {
@@ -585,8 +622,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_BOOL(0, "blank-line", &blankline,
-                       N_("insert paragraph break before appending to an existing note")),
+               OPT_STRING(0, "separator", &separator, N_("text"),
+                       N_("insert <text> as separator before appending to an existing note")),
                OPT_END()
        };
        int edit = !strcmp(argv[0], "edit");
@@ -621,8 +658,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)
                enum object_type type;
                char *prev_buf = read_object_file(note, &type, &size);

-               if (blankline && d.buf.len && prev_buf && size)
-                       strbuf_insertstr(&d.buf, 0, "\n");
+               if (d.buf.len && prev_buf && size)
+                       insert_separator(&d.buf, separator);
                if (prev_buf && size)
                        strbuf_insert(&d.buf, 0, prev_buf, size);
                free(prev_buf);
--

If the above is understood correctly, is there an api that handles escape
characters already in the existing code (I haven't found one so far, so I need
to confirm and replace it if there is one). In addition, the insert_separator
function above handles three special characters \t\n\r. Do we need more?

Thanks

^ permalink raw reply related	[flat|nested] 186+ messages in thread

* Re: [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option
  2022-12-22  9:30             ` Teng Long
@ 2022-12-23  1:36               ` Eric Sunshine
  0 siblings, 0 replies; 186+ messages in thread
From: Eric Sunshine @ 2022-12-23  1:36 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, gitster, tenglong.tl

On Thu, Dec 22, 2022 at 4:30 AM Teng Long <dyroneteng@gmail.com> wrote:
> Eric Sunshine <sunshine@sunshineco.com> writes:
> > 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.
>
> There is another question for me, if the separator we passed contains "\n"
> string , the argument the cmd receives will need to tranfer to '\n' character
> instead to make sure it's a linebreak but not a "\n" instead.
>
> So maybe like:
> +static void insert_separator(struct strbuf *message, const char *separator)
> +{
> +               while (*separator) {
> +                       if (*separator == '\\'){
> +                               switch (separator[1]) {
> +                                       case 'n':
> +                                               strbuf_addstr(&transfered, "\n");
> +                                               separator++;
> +                                               break;
> + [...]
> +                       separator++;
> +               }
> +}
>
> If the above is understood correctly, is there an api that handles escape
> characters already in the existing code (I haven't found one so far, so I need
> to confirm and replace it if there is one). In addition, the insert_separator
> function above handles three special characters \t\n\r. Do we need more?

You could probably use unquote_c_style() from quote.[hc]; something like this:

    struct strbuf orig = STRBUF_INIT;
    struct strbuf unquoted = STRBUF_INIT;
    strbuf_addf(&orig, "\"%s\"", separator);
    if (unquote_c_style(&unquoted, orig.buf, NULL) < 0) {
        strbuf_release(&unquoted);
        strbuf_release(&orig);
        die(_("some suitable error message"));
    }
    /* unquote succeeded -- use "unquoted" here */

However, I suspect that this is overkill, and you should explore
simpler ideas first.

For instance, it is perfectly acceptable to embed newlines directly in
shell strings, so this would work just fine without having to write
any extra string-unquoting code:

    % git notes add --separator='---
    ' <object>

But, I think you can make this even friendlier without having to do
any extra coding to support string-unquoting. In particular, use this
heuristic:

    - if the separator is zero-length, use it as-is
    - otherwise, if the separator ends with a newline, use it as-is
    - otherwise add a newline to the separator

In other words:

    if (!separator)
        separator = "\n"; /* default is one blank line */
    if (*separator == '\0')
        /* separator is empty; use as-is (no blank line) */
    else if (separator[strlen(separator) - 1] == '\n')
        /* user supplied newline; use as-is */
    else
        /* separator lacks newline; add it ourselves */

With the above logic, this defaults to a blank line between notes:

    % git notes add ...

this has no blank line between notes:

    % git notes add --separator='' ...

this uses a "---" + "\n" as separator:

    % git notes add --separator='---' ...

as does this:

    % git notes add --separator='---
    ' ...

and this places two blank lines between notes:

    % git notes add --separator='

    ' ...

^ permalink raw reply	[flat|nested] 186+ messages in thread

* [PATCH v4 0/5] notes.c: introduce "--separator" optio
  2022-11-09  9:06   ` [PATCH v3 0/5] notes.c: introduce "--no-blank-line" option Teng Long
                       ` (6 preceding siblings ...)
  2022-11-29 12:57     ` Teng Long
@ 2023-01-12  2:48     ` Teng Long
  2023-01-12  2:48       ` [PATCH v4 1/5] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
                         ` (5 more replies)
  7 siblings, 6 replies; 186+ messages in thread
From: Teng Long @ 2023-01-12  2:48 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, tenglong.tl, sunshine

From: Teng Long <dyroneteng@gmail.com>

Sorry for the late patch, my family just recovered from COVID-19.

Diff from v3:

     * rename option name from '--blank-line' to '--separator'

Thanks.

Teng Long (5):
  notes.c: cleanup 'strbuf_grow' call in 'append_edit'
  notes.c: cleanup for "designated init" and "char ptr init"
  notes.c: drop unreachable code in 'append_edit()'
  notes.c: provide tips when target and append note are both empty
  notes.c: introduce "--separator" option

 Documentation/git-notes.txt | 18 +++++++++--
 builtin/notes.c             | 64 +++++++++++++++++++++++++++++--------
 t/t3301-notes.sh            | 64 +++++++++++++++++++++++++++++++++++--
 3 files changed, 129 insertions(+), 17 deletions(-)

Range-diff against v3:
1:  8ae58934a1 = 1:  f00a759658 notes.c: cleanup 'strbuf_grow' call in 'append_edit'
2:  a53576ea88 = 2:  29f7703b06 notes.c: cleanup for "designated init" and "char ptr init"
3:  62a952ba3e = 3:  7b756b4c60 notes.c: drop unreachable code in 'append_edit()'
4:  0d8ce0b14b = 4:  d41ba14050 notes.c: provide tips when target and append note are both empty
5:  196e80358e < -:  ---------- notes.c: introduce "--no-blank-line" option
-:  ---------- > 5:  f7edbd0e50 notes.c: introduce "--separator" option
-- 
2.38.1.386.g6952793f2d9.dirty


^ permalink raw reply	[flat|nested] 186+ messages in thread

* [PATCH v4 1/5] notes.c: cleanup 'strbuf_grow' call in 'append_edit'
  2023-01-12  2:48     ` [PATCH v4 0/5] notes.c: introduce "--separator" optio Teng Long
@ 2023-01-12  2:48       ` Teng Long
  2023-01-15  4:53         ` Eric Sunshine
  2023-01-12  2:48       ` [PATCH v4 2/5] notes.c: cleanup for "designated init" and "char ptr init" Teng Long
                         ` (4 subsequent siblings)
  5 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-01-12  2:48 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, tenglong.tl, sunshine

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.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/notes.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index 80d9dfd25c..e57f024824 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -618,7 +618,6 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		enum object_type type;
 		char *prev_buf = read_object_file(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.38.1.386.g6952793f2d9.dirty


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v4 2/5] notes.c: cleanup for "designated init" and "char ptr init"
  2023-01-12  2:48     ` [PATCH v4 0/5] notes.c: introduce "--separator" optio Teng Long
  2023-01-12  2:48       ` [PATCH v4 1/5] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
@ 2023-01-12  2:48       ` Teng Long
  2023-01-12  9:51         ` Ævar Arnfjörð Bjarmason
  2023-01-12  2:48       ` [PATCH v4 3/5] notes.c: drop unreachable code in 'append_edit()' Teng Long
                         ` (3 subsequent siblings)
  5 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-01-12  2:48 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, tenglong.tl, sunshine

From: Teng Long <dyroneteng@gmail.com>

Let's do some cleanup for the following two places in "append_edit()".

The first place is "char *logmsg;" need to be initialized with NULL.
The second place is "struct note_data d = { 0, 0, NULL, STRBUF_INIT };"
could be replaced with designated init format.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/notes.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index e57f024824..8ca55ec83e 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -566,9 +566,9 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 	struct notes_tree *t;
 	struct object_id object, new_note;
 	const struct object_id *note;
-	char *logmsg;
+	char *logmsg = NULL;
 	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.38.1.386.g6952793f2d9.dirty


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v4 3/5] notes.c: drop unreachable code in 'append_edit()'
  2023-01-12  2:48     ` [PATCH v4 0/5] notes.c: introduce "--separator" optio Teng Long
  2023-01-12  2:48       ` [PATCH v4 1/5] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
  2023-01-12  2:48       ` [PATCH v4 2/5] notes.c: cleanup for "designated init" and "char ptr init" Teng Long
@ 2023-01-12  2:48       ` Teng Long
  2023-01-15 20:59         ` Eric Sunshine
  2023-01-12  2:48       ` [PATCH v4 4/5] notes.c: provide tips when target and append note are both empty Teng Long
                         ` (2 subsequent siblings)
  5 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-01-12  2:48 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, tenglong.tl, sunshine

From: Teng Long <dyroneteng@gmail.com>

Situation of note "removing" shouldn't happen in 'append_edit()',
unless it's a bug. So, let's drop the unreachable "else" code
in "append_edit()".

The notes operation "append" is different with "add", the latter
supports to overwrite the existing note then let the "removing"
happen (e.g. execute `git notes add -f -F /dev/null` on an existing
note), but the former will not because it only does "appends" but
not doing "overwrites".

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/notes.c | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index 8ca55ec83e..b71a81bff1 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -630,13 +630,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		if (add_note(t, &object, &new_note, combine_notes_overwrite))
 			BUG("combine_notes_overwrite failed");
 		logmsg = xstrfmt("Notes added by 'git notes %s'", argv[0]);
-	} else {
-		fprintf(stderr, _("Removing note for object %s\n"),
-			oid_to_hex(&object));
-		remove_note(t, object.hash);
-		logmsg = xstrfmt("Notes removed by 'git notes %s'", argv[0]);
+		commit_notes(the_repository, t, logmsg);
 	}
-	commit_notes(the_repository, t, logmsg);
 
 	free(logmsg);
 	free_note_data(&d);
-- 
2.38.1.386.g6952793f2d9.dirty


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v4 4/5] notes.c: provide tips when target and append note are both empty
  2023-01-12  2:48     ` [PATCH v4 0/5] notes.c: introduce "--separator" optio Teng Long
                         ` (2 preceding siblings ...)
  2023-01-12  2:48       ` [PATCH v4 3/5] notes.c: drop unreachable code in 'append_edit()' Teng Long
@ 2023-01-12  2:48       ` Teng Long
  2023-01-12  9:52         ` Ævar Arnfjörð Bjarmason
  2023-01-15 21:28         ` Eric Sunshine
  2023-01-12  2:48       ` [PATCH v4 5/5] notes.c: introduce "--separator" option Teng Long
  2023-02-16 13:05       ` [PATCH v5 0/3] " Teng Long
  5 siblings, 2 replies; 186+ messages in thread
From: Teng Long @ 2023-01-12  2:48 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, tenglong.tl, sunshine

From: Teng Long <dyroneteng@gmail.com>

When "git notes append <object>" is executed, if there is no note in
the given object and the appended note is empty too, we could print
the exact tips to end-user.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/notes.c  | 5 ++++-
 t/t3301-notes.sh | 5 +++--
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index b71a81bff1..f2efb3736c 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -631,7 +631,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 			BUG("combine_notes_overwrite failed");
 		logmsg = xstrfmt("Notes added by 'git notes %s'", argv[0]);
 		commit_notes(the_repository, t, logmsg);
-	}
+	} else if (!d.buf.len && !note)
+		fprintf(stderr,
+			_("Both original and appended notes are empty in %s, do nothing\n"),
+			oid_to_hex(&object));
 
 	free(logmsg);
 	free_note_data(&d);
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 3288aaec7d..e7807e052a 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -552,6 +552,7 @@ test_expect_success 'appending empty string does not change existing note' '
 '
 
 test_expect_success 'git notes append == add when there is no existing note' '
+	test_when_finished git notes remove HEAD &&
 	git notes remove HEAD &&
 	test_must_fail git notes list HEAD &&
 	git notes append -m "Initial set of notes${LF}${LF}More notes appended with git notes append" &&
@@ -560,9 +561,9 @@ test_expect_success 'git notes append == add when there is no existing note' '
 '
 
 test_expect_success 'appending empty string to non-existing note does not create note' '
-	git notes remove HEAD &&
 	test_must_fail git notes list HEAD &&
-	git notes append -m "" &&
+	git notes append -m "" >output 2>&1 &&
+	grep "Both original and appended notes are empty" output &&
 	test_must_fail git notes list HEAD
 '
 
-- 
2.38.1.386.g6952793f2d9.dirty


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v4 5/5] notes.c: introduce "--separator" option
  2023-01-12  2:48     ` [PATCH v4 0/5] notes.c: introduce "--separator" optio Teng Long
                         ` (3 preceding siblings ...)
  2023-01-12  2:48       ` [PATCH v4 4/5] notes.c: provide tips when target and append note are both empty Teng Long
@ 2023-01-12  2:48       ` Teng Long
  2023-01-12  9:53         ` Ævar Arnfjörð Bjarmason
  2023-01-15 22:15         ` Eric Sunshine
  2023-02-16 13:05       ` [PATCH v5 0/3] " Teng Long
  5 siblings, 2 replies; 186+ messages in thread
From: Teng Long @ 2023-01-12  2:48 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, tenglong.tl, sunshine

From: Teng Long <dyroneteng@gmail.com>

When appending to a given notes object and the appended note is not
empty too, we will insert a blank line at first which separates the
existing note and the appended one, which as the separator.

Sometimes, we want to use a specified <text> as the separator. For
example, if we specify as:

    * --separator='------': we will insert "------\n" as the separator,
    because user do not provide the line break char at last, we will add
    the trailing '\n' compatibly.

    * --separator='------\n': we will insert as-is because it contains
    the line break at last.

    * --separator='': we specify an empty separator which means will
    append the message directly without inserting any separator at
    first.

    * not specified --separator option: will use '\n' as the separator
    when do appending and this is the default behavour.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-notes.txt | 18 +++++++++--
 builtin/notes.c             | 49 +++++++++++++++++++++++++++---
 t/t3301-notes.sh            | 59 +++++++++++++++++++++++++++++++++++++
 3 files changed, 120 insertions(+), 6 deletions(-)

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index efbc10f0f5..227fa88317 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -11,7 +11,7 @@ SYNOPSIS
 'git notes' [list [<object>]]
 'git notes' add [-f] [--allow-empty] [-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] [-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>
@@ -86,7 +86,11 @@ 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.
+	Creates a new notes object if needed. If the note of the given
+	object and the note to be appended are not empty, a blank line
+	will be inserted between them as the separator ("blank line" is
+	the default behavior, `--separator` option supports to specify
+	a customized one).
 
 edit::
 	Edit the notes for a given object (defaults to HEAD).
@@ -159,6 +163,16 @@ OPTIONS
 	Allow an empty note object to be stored. The default behavior is
 	to automatically remove empty notes.
 
+--separator <text>::
+	Specify the <text> to be inserted between existing note and appended
+	message, the <text> acts as a separator.
+	If <text> is empty (`--separator=''`), will append the message to
+	existing note directly without insert any separator.
+	If <text> is nonempty, will use as-is. One thing to notice is if
+	the <text> lacks newline charactor, will add the newline automatically.
+	If not specify this option, a blank line will be inserted as the
+	separator.
+
 --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 f2efb3736c..6746ad3232 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -24,6 +24,8 @@
 #include "notes-utils.h"
 #include "worktree.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>]"),
@@ -209,7 +211,7 @@ static void write_note_data(struct note_data *d, struct object_id *oid)
 	}
 }
 
-static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
+static int parse_msg_arg_add(const struct option *opt, const char *arg, int unset)
 {
 	struct note_data *d = opt->value;
 
@@ -225,6 +227,43 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
 	return 0;
 }
 
+static void insert_separator(struct strbuf *message)
+{
+	const char *insert;
+
+	if (!separator)
+		separator = "\n";
+	if (*separator == '\0')
+		/* separator is empty; use as-is (no blank line) */
+		return;
+	else if (separator[strlen(separator) - 1] == '\n')
+		/* user supplied newline; use as-is */
+		insert = separator;
+	else
+		/* separator lacks newline; add it ourselves */
+		insert = xstrfmt("%s%s", separator,"\n");
+	strbuf_insertstr(message, 0, insert);
+}
+
+static int parse_msg_arg_append(const struct option *opt, const char *arg, int unset)
+{
+	struct note_data *d = opt->value;
+	struct strbuf append = STRBUF_INIT;
+
+	BUG_ON_OPT_NEG(unset);
+
+	strbuf_addstr(&append, arg);
+	if (d->buf.len){
+		insert_separator(&append);
+	}
+	strbuf_addbuf(&d->buf, &append);
+	strbuf_stripspace(&d->buf, 0);
+
+	d->given = 1;
+	strbuf_release(&append);
+	return 0;
+}
+
 static int parse_file_arg(const struct option *opt, const char *arg, int unset)
 {
 	struct note_data *d = opt->value;
@@ -406,7 +445,7 @@ static int add(int argc, const char **argv, const char *prefix)
 	struct option options[] = {
 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
 			N_("note contents as a string"), PARSE_OPT_NONEG,
-			parse_msg_arg),
+			parse_msg_arg_add),
 		OPT_CALLBACK_F('F', "file", &d, N_("file"),
 			N_("note contents in a file"), PARSE_OPT_NONEG,
 			parse_file_arg),
@@ -572,7 +611,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 	struct option options[] = {
 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
 			N_("note contents as a string"), PARSE_OPT_NONEG,
-			parse_msg_arg),
+			parse_msg_arg_append),
 		OPT_CALLBACK_F('F', "file", &d, N_("file"),
 			N_("note contents in a file"), PARSE_OPT_NONEG,
 			parse_file_arg),
@@ -584,6 +623,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_("text"),
+			N_("insert <text> as separator before appending to an existing note")),
 		OPT_END()
 	};
 	int edit = !strcmp(argv[0], "edit");
@@ -619,7 +660,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		char *prev_buf = read_object_file(note, &type, &size);
 
 		if (d.buf.len && prev_buf && size)
-			strbuf_insertstr(&d.buf, 0, "\n");
+			insert_separator(&d.buf);
 		if (prev_buf && size)
 			strbuf_insert(&d.buf, 0, prev_buf, size);
 		free(prev_buf);
git format-patch a38d39a4c50d1275833aba54c4dbdfce9e2e9ca1  --thread -v 4 --output-directory=outgoing/git-notes-append/v4 --cover-letter  --range-diff 196e80358ediff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index e7807e052a..e8bc9934ed 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -521,6 +521,65 @@ 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 separatoro with line break' '
+	test_when_finished git notes remove HEAD &&
+	cat >expect <<-\EOF &&
+		notes-1
+		-------
+		notes-2
+	EOF
+
+	git notes add -m "notes-1" &&
+	separator=$(printf "%s\n" "-------") &&
+	git notes append --separator="$separator" -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 to existing note with "git notes append"' '
 	cat >expect <<-EOF &&
 		Initial set of notes
-- 
2.38.1.386.g6952793f2d9.dirty


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* Re: [PATCH v4 2/5] notes.c: cleanup for "designated init" and "char ptr init"
  2023-01-12  2:48       ` [PATCH v4 2/5] notes.c: cleanup for "designated init" and "char ptr init" Teng Long
@ 2023-01-12  9:51         ` Ævar Arnfjörð Bjarmason
  2023-01-28 11:33           ` Teng Long
  0 siblings, 1 reply; 186+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2023-01-12  9:51 UTC (permalink / raw)
  To: Teng Long; +Cc: git, tenglong.tl, sunshine


On Thu, Jan 12 2023, Teng Long wrote:

> From: Teng Long <dyroneteng@gmail.com>
>
> Let's do some cleanup for the following two places in "append_edit()".
>
> The first place is "char *logmsg;" need to be initialized with NULL.
> The second place is "struct note_data d = { 0, 0, NULL, STRBUF_INIT };"
> could be replaced with designated init format.
>
> Signed-off-by: Teng Long <dyroneteng@gmail.com>
> ---
>  builtin/notes.c | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/builtin/notes.c b/builtin/notes.c
> index e57f024824..8ca55ec83e 100644
> --- a/builtin/notes.c
> +++ b/builtin/notes.c
> @@ -566,9 +566,9 @@ static int append_edit(int argc, const char **argv, const char *prefix)
>  	struct notes_tree *t;
>  	struct object_id object, new_note;
>  	const struct object_id *note;
> -	char *logmsg;
> +	char *logmsg = NULL;

This change isn't needed, and the compiler will check that we have it
init'd.

It *is* needed when combined with your 3/5, but even then it's the wrong
solution. In that change you move the commit_notes() into that "if"
branch that needs the "logmsg", and it's correct that you need to
NULL-init it to safely pass it to free().

But let's just declare the "logmsg" in that if block then, and move the
free() over there, that reduces the scope it's in.

>  	const char * const *usage;
> -	struct note_data d = { 0, 0, NULL, STRBUF_INIT };
> +	struct note_data d = { .buf = STRBUF_INIT };

This change is good, but then let's change the other such case in this
file to a designated init too, and make the commit message etc. be about
using designated init here.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v4 4/5] notes.c: provide tips when target and append note are both empty
  2023-01-12  2:48       ` [PATCH v4 4/5] notes.c: provide tips when target and append note are both empty Teng Long
@ 2023-01-12  9:52         ` Ævar Arnfjörð Bjarmason
  2023-01-15 21:28         ` Eric Sunshine
  1 sibling, 0 replies; 186+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2023-01-12  9:52 UTC (permalink / raw)
  To: Teng Long; +Cc: git, tenglong.tl, sunshine


On Thu, Jan 12 2023, Teng Long wrote:

> From: Teng Long <dyroneteng@gmail.com>
>
> When "git notes append <object>" is executed, if there is no note in
> the given object and the appended note is empty too, we could print
> the exact tips to end-user.
>
> Signed-off-by: Teng Long <dyroneteng@gmail.com>
> ---
>  builtin/notes.c  | 5 ++++-
>  t/t3301-notes.sh | 5 +++--
>  2 files changed, 7 insertions(+), 3 deletions(-)
>
> diff --git a/builtin/notes.c b/builtin/notes.c
> index b71a81bff1..f2efb3736c 100644
> --- a/builtin/notes.c
> +++ b/builtin/notes.c
> @@ -631,7 +631,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)
>  			BUG("combine_notes_overwrite failed");
>  		logmsg = xstrfmt("Notes added by 'git notes %s'", argv[0]);
>  		commit_notes(the_repository, t, logmsg);
> -	}
> +	} else if (!d.buf.len && !note)
> +		fprintf(stderr,
> +			_("Both original and appended notes are empty in %s, do nothing\n"),
> +			oid_to_hex(&object));

Style: This arm should have {}-braces too, see the CodingGuidelines.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v4 5/5] notes.c: introduce "--separator" option
  2023-01-12  2:48       ` [PATCH v4 5/5] notes.c: introduce "--separator" option Teng Long
@ 2023-01-12  9:53         ` Ævar Arnfjörð Bjarmason
  2023-01-15 22:04           ` Eric Sunshine
  2023-01-15 22:15         ` Eric Sunshine
  1 sibling, 1 reply; 186+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2023-01-12  9:53 UTC (permalink / raw)
  To: Teng Long; +Cc: git, tenglong.tl, sunshine


On Thu, Jan 12 2023, Teng Long wrote:

> From: Teng Long <dyroneteng@gmail.com>
>
> When appending to a given notes object and the appended note is not
> empty too, we will insert a blank line at first which separates the
> existing note and the appended one, which as the separator.
>
> Sometimes, we want to use a specified <text> as the separator. For
> example, if we specify as:
>
>     * --separator='------': we will insert "------\n" as the separator,
>     because user do not provide the line break char at last, we will add
>     the trailing '\n' compatibly.
>
>     * --separator='------\n': we will insert as-is because it contains
>     the line break at last.
>
>     * --separator='': we specify an empty separator which means will
>     append the message directly without inserting any separator at
>     first.
>
>     * not specified --separator option: will use '\n' as the separator
>     when do appending and this is the default behavour.
>
> Signed-off-by: Teng Long <dyroneteng@gmail.com>
> ---
>  Documentation/git-notes.txt | 18 +++++++++--
>  builtin/notes.c             | 49 +++++++++++++++++++++++++++---
>  t/t3301-notes.sh            | 59 +++++++++++++++++++++++++++++++++++++
>  3 files changed, 120 insertions(+), 6 deletions(-)
>
> diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
> index efbc10f0f5..227fa88317 100644
> --- a/Documentation/git-notes.txt
> +++ b/Documentation/git-notes.txt
> @@ -11,7 +11,7 @@ SYNOPSIS
>  'git notes' [list [<object>]]
>  'git notes' add [-f] [--allow-empty] [-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] [-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>
> @@ -86,7 +86,11 @@ 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.
> +	Creates a new notes object if needed. If the note of the given
> +	object and the note to be appended are not empty, a blank line
> +	will be inserted between them as the separator ("blank line" is
> +	the default behavior, `--separator` option supports to specify
> +	a customized one).

I think this should change to:

	[...]will be inserted between them. Use the `--separator` option
	to insert other delimiters.

I.e. part of that's fixes for odd grammar, but mainly just offloading
the explanation to the --separator discussion below.


>  
>  edit::
>  	Edit the notes for a given object (defaults to HEAD).
> @@ -159,6 +163,16 @@ OPTIONS
>  	Allow an empty note object to be stored. The default behavior is
>  	to automatically remove empty notes.
>  
> +--separator <text>::
> +	Specify the <text> to be inserted between existing note and appended
> +	message, the <text> acts as a separator.

Maybe let's use '<string>' or '<separator>' here instead? e.g.:

	Specifies the <string> ...

Maybe "<text>" just looks odd to me.

More generally, let's say something like:

	When invoking "git notes append", specify the...

I.e. this is only for "append", but nothing here says so.

> +	If <text> is empty (`--separator=''`), will append the message to
> +	existing note directly without insert any separator.
> +	If <text> is nonempty, will use as-is. One thing to notice is if
> +	the <text> lacks newline charactor, will add the newline automatically.
> +	If not specify this option, a blank line will be inserted as the
> +	separator.


We're spending a lot of text here on a pretty simple concept if I
understand it correctly, I.e. just (pseudocode):

	int sep_extra_nl = 0;
	const char *sep = opt_sep ? opt_sep : "\n";
	if (!strstr(sep, '\n'))
		sep_extra_nl = 1;
	[...]

Except that was written after I read your explanation, but looking at
the code it's incorrect, it's whether the "*last*" character contains a
newline or not.

So all in all, I think we should just say:

	--separator <separator>:
		The '<separator>' inserted between the note and message
		by 'append', "\n" by default. A custom separator can be
		provided, if it doesn't end in a "\n" one will be added
		implicitly .

> +
>  --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 f2efb3736c..6746ad3232 100644
> --- a/builtin/notes.c
> +++ b/builtin/notes.c
> @@ -24,6 +24,8 @@
>  #include "notes-utils.h"
>  #include "worktree.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>]"),
> @@ -209,7 +211,7 @@ static void write_note_data(struct note_data *d, struct object_id *oid)
>  	}
>  }
>  
> -static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
> +static int parse_msg_arg_add(const struct option *opt, const char *arg, int unset)
>  {
>  	struct note_data *d = opt->value;
>  
> @@ -225,6 +227,43 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
>  	return 0;
>  }
>  
> +static void insert_separator(struct strbuf *message)
> +{
> +	const char *insert;
> +
> +	if (!separator)
> +		separator = "\n";
> +	if (*separator == '\0')

Style: Don't compare to 0, NULL, '\0' etc. Just use !*separator.

> +		/* separator is empty; use as-is (no blank line) */
> +		return;
> +	else if (separator[strlen(separator) - 1] == '\n')
> +		/* user supplied newline; use as-is */
> +		insert = separator;
> +	else
> +		/* separator lacks newline; add it ourselves */
> +		insert = xstrfmt("%s%s", separator,"\n");

We're leaking memor here, and making it hard to fix that by conflating a
const "insert" with this allocated version.

I haven't read the whole context, but this seems really complex per the
doc feedback above. Why can't we just keep track of if we're using the
default value or not? I.e. just have the "--separator" option default to
NULL, if it's not set y ou don't need to do this "\n" check, and just
use the default, otherwise append etc.

> +	strbuf_insertstr(message, 0, insert);

Maybe you were trying to get around using a more complex strbuf_splice()
here, but let's just avoid teh xstrfmt() and splice() that "\n" in, if
needed?

> +}
> +
> +static int parse_msg_arg_append(const struct option *opt, const char *arg, int unset)
> +{
> +	struct note_data *d = opt->value;
> +	struct strbuf append = STRBUF_INIT;
> +
> +	BUG_ON_OPT_NEG(unset);
> +
> +	strbuf_addstr(&append, arg);
> +	if (d->buf.len){
> +		insert_separator(&append);
> +	}

Drop the {} here.

> +	strbuf_addbuf(&d->buf, &append);
> +	strbuf_stripspace(&d->buf, 0);
> +
> +	d->given = 1;
> +	strbuf_release(&append);

Why do we need this other variable, canet'w just append to d.buf
directly?

Do we mean to strbuf_stripspace() here over the whole buffer, or just
what we're appending?

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v4 1/5] notes.c: cleanup 'strbuf_grow' call in 'append_edit'
  2023-01-12  2:48       ` [PATCH v4 1/5] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
@ 2023-01-15  4:53         ` Eric Sunshine
  2023-01-28 11:22           ` Teng Long
  0 siblings, 1 reply; 186+ messages in thread
From: Eric Sunshine @ 2023-01-15  4:53 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, tenglong.tl

On Wed, Jan 11, 2023 at 9:48 PM Teng Long <dyroneteng@gmail.com> wrote:
> 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.
>
> Signed-off-by: Teng Long <dyroneteng@gmail.com>
> ---
> diff --git a/builtin/notes.c b/builtin/notes.c
> @@ -618,7 +618,6 @@ static int append_edit(int argc, const char **argv, const char *prefix)
>                 char *prev_buf = read_object_file(note, &type, &size);
>
> -               strbuf_grow(&d.buf, size + 1);
>                 if (d.buf.len && prev_buf && size)
>                         strbuf_insertstr(&d.buf, 0, "\n");

Indeed, it's not clear why that was there in the first place. Digging
through history doesn't shed any light on it. It was introduced by
2347fae50b (builtin-notes: Add "append" subcommand for appending to
note objects, 2010-02-13)[1], but there's no explanation as to why it
was coded that way. Best guess may be that the author originally
inserted "\n" manually by direct manipulation of the strbuf rather
than employing a strbuf function, but then switched to strbuf_insert()
before submitting the series and forgot to remove the now-unnecessary
strbuf_grow().

[1]: https://lore.kernel.org/git/1266096518-2104-26-git-send-email-johan@herland.net/

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v4 3/5] notes.c: drop unreachable code in 'append_edit()'
  2023-01-12  2:48       ` [PATCH v4 3/5] notes.c: drop unreachable code in 'append_edit()' Teng Long
@ 2023-01-15 20:59         ` Eric Sunshine
  2023-01-15 21:10           ` Eric Sunshine
  2023-01-28 11:50           ` Teng Long
  0 siblings, 2 replies; 186+ messages in thread
From: Eric Sunshine @ 2023-01-15 20:59 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, tenglong.tl

On Wed, Jan 11, 2023 at 9:48 PM Teng Long <dyroneteng@gmail.com> wrote:
> Situation of note "removing" shouldn't happen in 'append_edit()',
> unless it's a bug. So, let's drop the unreachable "else" code
> in "append_edit()".
>
> The notes operation "append" is different with "add", the latter
> supports to overwrite the existing note then let the "removing"
> happen (e.g. execute `git notes add -f -F /dev/null` on an existing
> note), but the former will not because it only does "appends" but
> not doing "overwrites".
>
> Signed-off-by: Teng Long <dyroneteng@gmail.com>
> ---
> diff --git a/builtin/notes.c b/builtin/notes.c
> @@ -630,13 +630,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)
>                 if (add_note(t, &object, &new_note, combine_notes_overwrite))
>                         BUG("combine_notes_overwrite failed");
>                 logmsg = xstrfmt("Notes added by 'git notes %s'", argv[0]);
> -       } else {
> -               fprintf(stderr, _("Removing note for object %s\n"),
> -                       oid_to_hex(&object));
> -               remove_note(t, object.hash);
> -               logmsg = xstrfmt("Notes removed by 'git notes %s'", argv[0]);
> +               commit_notes(the_repository, t, logmsg);
>         }
> -       commit_notes(the_repository, t, logmsg);

This change breaks removal of notes using "git notes edit". Prior to
this change, if you delete the content of a note using "git notes
edit", then the note is removed. Following this change, the note
remains, which contradicts documented[1] behavior.

[1]: Unfortunately, perhaps, this behavior is documented under the
"remove" subcommand rather than the "edit" subcommand, but it is
nevertheless documented and has worked this way for ages, so this
patch causes a regression.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v4 3/5] notes.c: drop unreachable code in 'append_edit()'
  2023-01-15 20:59         ` Eric Sunshine
@ 2023-01-15 21:10           ` Eric Sunshine
  2023-01-28 11:50           ` Teng Long
  1 sibling, 0 replies; 186+ messages in thread
From: Eric Sunshine @ 2023-01-15 21:10 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, tenglong.tl

On Sun, Jan 15, 2023 at 3:59 PM Eric Sunshine <sunshine@sunshineco.com> wrote:
> On Wed, Jan 11, 2023 at 9:48 PM Teng Long <dyroneteng@gmail.com> wrote:
> > -       } else {
> > -               fprintf(stderr, _("Removing note for object %s\n"),
> > -                       oid_to_hex(&object));
> > -               remove_note(t, object.hash);
> > -               logmsg = xstrfmt("Notes removed by 'git notes %s'", argv[0]);
> > +               commit_notes(the_repository, t, logmsg);
> >         }
> > -       commit_notes(the_repository, t, logmsg);
>
> This change breaks removal of notes using "git notes edit". Prior to
> this change, if you delete the content of a note using "git notes
> edit", then the note is removed. Following this change, the note
> remains, which contradicts documented[1] behavior.

Aside from suggesting that this patch ought to be dropped, implicit in
the above comment is that test coverage is not as thorough as it
should be, otherwise this regression would have been caught early.
This suggests that the test coverage, at least for "git notes edit",
needs some beefing up, though doing so is probably outside the scope
of this series; it's something which could/should be done separately,
and it doesn't necessarily need to be done by you.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v4 4/5] notes.c: provide tips when target and append note are both empty
  2023-01-12  2:48       ` [PATCH v4 4/5] notes.c: provide tips when target and append note are both empty Teng Long
  2023-01-12  9:52         ` Ævar Arnfjörð Bjarmason
@ 2023-01-15 21:28         ` Eric Sunshine
  1 sibling, 0 replies; 186+ messages in thread
From: Eric Sunshine @ 2023-01-15 21:28 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, tenglong.tl

On Wed, Jan 11, 2023 at 9:48 PM Teng Long <dyroneteng@gmail.com> wrote:
> When "git notes append <object>" is executed, if there is no note in
> the given object and the appended note is empty too, we could print
> the exact tips to end-user.
>
> Signed-off-by: Teng Long <dyroneteng@gmail.com>
> ---
> diff --git a/builtin/notes.c b/builtin/notes.c
> @@ -631,7 +631,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)
> +       } else if (!d.buf.len && !note)
> +               fprintf(stderr,
> +                       _("Both original and appended notes are empty in %s, do nothing\n"),
> +                       oid_to_hex(&object));

My knee-jerk reaction is between "meh" and "thumbs down". The commit
message says we "can do this" but doesn't explain "why we should do
this". Is this condition important enough to break the Unix maxim of
"Rule of Silence" (or "Silence is Golden")?

I also wonder if this change is going to cause problems (or at least
annoyance) for automated tooling. At present, tooling doesn't have to
worry whether or not the existing or new note is empty; everything
just works as expected without complaint. However, following this
change, such tooling may now be greeted with an unexpected diagnostic
message.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v4 5/5] notes.c: introduce "--separator" option
  2023-01-12  9:53         ` Ævar Arnfjörð Bjarmason
@ 2023-01-15 22:04           ` Eric Sunshine
  0 siblings, 0 replies; 186+ messages in thread
From: Eric Sunshine @ 2023-01-15 22:04 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: Teng Long, git, tenglong.tl

On Thu, Jan 12, 2023 at 5:07 AM Ævar Arnfjörð Bjarmason
<avarab@gmail.com> wrote:
> On Thu, Jan 12 2023, Teng Long wrote:
> > When appending to a given notes object and the appended note is not
> > empty too, we will insert a blank line at first which separates the
> > existing note and the appended one, which as the separator.
> > [...]
> > Signed-off-by: Teng Long <dyroneteng@gmail.com>
> > ---
> > diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
> > @@ -159,6 +163,16 @@ OPTIONS
> > +--separator <text>::
> > +     Specify the <text> to be inserted between existing note and appended
> > +     message, the <text> acts as a separator.
>
> Maybe let's use '<string>' or '<separator>' here instead? e.g.:
>         Specifies the <string> ...
> Maybe "<text>" just looks odd to me.
>
> More generally, let's say something like:
>         When invoking "git notes append", specify the...
> I.e. this is only for "append", but nothing here says so.

Agreed on these points.

> > +     If <text> is empty (`--separator=''`), will append the message to
> > +     existing note directly without insert any separator.
> > +     If <text> is nonempty, will use as-is. One thing to notice is if
> > +     the <text> lacks newline charactor, will add the newline automatically.
> > +     If not specify this option, a blank line will be inserted as the
> > +     separator.
>
> We're spending a lot of text here on a pretty simple concept if I
> understand it correctly, I.e. just (pseudocode):
>
>         int sep_extra_nl = 0;
>         const char *sep = opt_sep ? opt_sep : "\n";
>         if (!strstr(sep, '\n'))
>                 sep_extra_nl = 1;
>         [...]
>
> Except that was written after I read your explanation, but looking at
> the code it's incorrect, it's whether the "*last*" character contains a
> newline or not.
>
> So all in all, I think we should just say:
>
>         --separator <separator>:
>                 The '<separator>' inserted between the note and message
>                 by 'append', "\n" by default. A custom separator can be
>                 provided, if it doesn't end in a "\n" one will be added
>                 implicitly .

Unfortunately, this misses the point. The original reason Teng Long
started on this patch series was to be able to _suppress_ the blank
line added unconditionally between notes. In the original submission,
this was done via a --no-blankline option, but that met with
resistance from some reviewers as being potentially confusing and too
specialized. (The commit message of this patch should probably do a
better job of explaining that one purpose of this change is to support
the case of no-separator.)

A generalized --separator= option was suggested[1] as a possibly more
palatable alternative, with which an empty string (meaning "no
separator") would cover the case for which the original --no-blankline
was meant to handle. So, at the very least, the documentation needs to
call out the empty string as being a special case for which automatic
appending of "\n" does not occur.

> > diff --git a/builtin/notes.c b/builtin/notes.c
> > +static void insert_separator(struct strbuf *message)
> > +{
> > +     const char *insert;
> > +
> > +     if (!separator)
> > +             separator = "\n";
> > +     if (*separator == '\0')
>
> Style: Don't compare to 0, NULL, '\0' etc. Just use !*separator.

My fault[2]. Your suggestion is indeed more appropriate in this codebase.

> > +             /* separator is empty; use as-is (no blank line) */
> > +             return;
> > +     else if (separator[strlen(separator) - 1] == '\n')
> > +             /* user supplied newline; use as-is */
> > +             insert = separator;
> > +     else
> > +             /* separator lacks newline; add it ourselves */
> > +             insert = xstrfmt("%s%s", separator,"\n");
>
> We're leaking memor here, and making it hard to fix that by conflating a
> const "insert" with this allocated version.
>
> I haven't read the whole context, but this seems really complex per the
> doc feedback above. Why can't we just keep track of if we're using the
> default value or not? I.e. just have the "--separator" option default to
> NULL, if it's not set y ou don't need to do this "\n" check, and just
> use the default, otherwise append etc.

That wouldn't work for the reason given above. The idea outlined in
[2] is that an empty separator is treated specially as meaning
"nothing-between-notes, not even a blank line".

> > +     strbuf_insertstr(message, 0, insert);
>
> Maybe you were trying to get around using a more complex strbuf_splice()
> here, but let's just avoid teh xstrfmt() and splice() that "\n" in, if
> needed?

The code example I gave in [2] was meant to illustrate the suggested
behavior as clearly as possible, not necessarily to be copied
verbatim. Being able to do this without leaking memory should
certainly be possible.

> Do we mean to strbuf_stripspace() here over the whole buffer, or just
> what we're appending?

That's a very good question. The note being appended might indeed have
leading whitespace gunk which ought to be removed before the append
operation. (Plus, it's a reasonable assumption that the existing note
text has already been "stripspaced".)

[1]: https://lore.kernel.org/git/CAPig+cRcezSp4Rqt1Y9bD-FT6+7b0g9qHfbGRx65AOnw2FQXKg@mail.gmail.com/
[2]: https://lore.kernel.org/git/CAPig+cTFBVAL2gd3LqQEzS--cXqJXR+1OVerii-D6JqFvJwXqQ@mail.gmail.com/

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v4 5/5] notes.c: introduce "--separator" option
  2023-01-12  2:48       ` [PATCH v4 5/5] notes.c: introduce "--separator" option Teng Long
  2023-01-12  9:53         ` Ævar Arnfjörð Bjarmason
@ 2023-01-15 22:15         ` Eric Sunshine
  1 sibling, 0 replies; 186+ messages in thread
From: Eric Sunshine @ 2023-01-15 22:15 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, tenglong.tl

On Wed, Jan 11, 2023 at 9:48 PM Teng Long <dyroneteng@gmail.com> wrote:
> When appending to a given notes object and the appended note is not
> empty too, we will insert a blank line at first which separates the
> existing note and the appended one, which as the separator.
> [...]
> Signed-off-by: Teng Long <dyroneteng@gmail.com>
> ---
> diff --git a/builtin/notes.c b/builtin/notes.c
> @@ -24,6 +24,8 @@
> +static char *separator = "\n";

If you are initializing `separator` to "\n"...

> @@ -225,6 +227,43 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
> +static void insert_separator(struct strbuf *message)
> +{
> +       if (!separator)
> +               separator = "\n";

... then these lines are not needed (and are effectively dead-code).

> +       if (*separator == '\0')
> +               /* separator is empty; use as-is (no blank line) */
> +               return;
> +       else if (separator[strlen(separator) - 1] == '\n')
> +               /* user supplied newline; use as-is */
> +               insert = separator;
> +       else
> +               /* separator lacks newline; add it ourselves */
> +               insert = xstrfmt("%s%s", separator,"\n");
> +       strbuf_insertstr(message, 0, insert);
> +}

> git format-patch a38d39a4c50d1275833aba54c4dbdfce9e2e9ca1  --thread -v 4 --output-directory=outgoing/git-notes-append/v4 --cover-letter  --range-diff 196e80358ediff --git a/t/t3301-notes.sh b/t/t3301-notes.sh

There's some weird malformation going on here... this should just be a
"diff --git ..." line.

> @@ -521,6 +521,65 @@ test_expect_success 'listing non-existing notes fails' '
> +test_expect_success 'append: specify an empty separator' '
> +       test_when_finished git notes remove HEAD &&
> +       cat >expect <<-\EOF &&
> +               notes-1
> +               notes-2
> +       EOF

Style nit: We don't normally give extra indentation to the body of the
here-doc. Instead:

    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
> +
> +'

Style nit: drop the unnecessary blank line before the closing quote.

> +test_expect_success 'append: specify separatoro with line break' '

s/separatoro/separator/

> +       test_when_finished git notes remove HEAD &&
> +       cat >expect <<-\EOF &&
> +               notes-1
> +               -------
> +               notes-2
> +       EOF
> +
> +       git notes add -m "notes-1" &&
> +       separator=$(printf "%s\n" "-------") &&
> +       git notes append --separator="$separator" -m "notes-2" &&

It might be easier to drop the `separator` variable and write this simply as:

    git notes append --separator="-------$LF" -m "notes-2" &&

LF is defined in t/test-lib.sh as "\n".

> +       git notes show >actual &&
> +       test_cmp expect actual
> +'

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v4 1/5] notes.c: cleanup 'strbuf_grow' call in 'append_edit'
  2023-01-15  4:53         ` Eric Sunshine
@ 2023-01-28 11:22           ` Teng Long
  0 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-01-28 11:22 UTC (permalink / raw)
  To: sunshine; +Cc: avarab, dyroneteng, git, tenglong.tl

Eric Sunshine <sunshine@sunshineco.com> writes:

> On Wed, Jan 11, 2023 at 9:48 PM Teng Long <dyroneteng@gmail.com> wrote:
> > 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.
> >
> > Signed-off-by: Teng Long <dyroneteng@gmail.com>
> > ---
> > diff --git a/builtin/notes.c b/builtin/notes.c
> > @@ -618,7 +618,6 @@ static int append_edit(int argc, const char **argv, const char *prefix)
> >                 char *prev_buf = read_object_file(note, &type, &size);
> >
> > -               strbuf_grow(&d.buf, size + 1);
> >                 if (d.buf.len && prev_buf && size)
> >                         strbuf_insertstr(&d.buf, 0, "\n");
>
> Indeed, it's not clear why that was there in the first place. Digging
> through history doesn't shed any light on it. It was introduced by
> 2347fae50b (builtin-notes: Add "append" subcommand for appending to
> note objects, 2010-02-13)[1], but there's no explanation as to why it
> was coded that way. Best guess may be that the author originally
> inserted "\n" manually by direct manipulation of the strbuf rather
> than employing a strbuf function, but then switched to strbuf_insert()
> before submitting the series and forgot to remove the now-unnecessary
> strbuf_grow().

Yes, I have the same opinion with you, maybe original idea to savesome array
creation overhead? I'm not sure, but I think the deletion here is OK.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v4 2/5] notes.c: cleanup for "designated init" and "char ptr init"
  2023-01-12  9:51         ` Ævar Arnfjörð Bjarmason
@ 2023-01-28 11:33           ` Teng Long
  0 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-01-28 11:33 UTC (permalink / raw)
  To: avarab; +Cc: dyroneteng, git, sunshine, tenglong.tl

Ævar Arnfjörð Bjarmason writes:

> > From: Teng Long <dyroneteng@gmail.com>
> >
> > Let's do some cleanup for the following two places in "append_edit()".
> >
> > The first place is "char *logmsg;" need to be initialized with NULL.
> > The second place is "struct note_data d = { 0, 0, NULL, STRBUF_INIT };"
> > could be replaced with designated init format.
> >
> > Signed-off-by: Teng Long <dyroneteng@gmail.com>
> > ---
> >  builtin/notes.c | 4 ++--
> >  1 file changed, 2 insertions(+), 2 deletions(-)
> >
> > diff --git a/builtin/notes.c b/builtin/notes.c
> > index e57f024824..8ca55ec83e 100644
> > --- a/builtin/notes.c
> > +++ b/builtin/notes.c
> > @@ -566,9 +566,9 @@ static int append_edit(int argc, const char **argv, const char *prefix)
> >  	struct notes_tree *t;
> >  	struct object_id object, new_note;
> >  	const struct object_id *note;
> > -	char *logmsg;
> > +	char *logmsg = NULL;
>
> This change isn't needed, and the compiler will check that we have it
> init'd.
>
> It *is* needed when combined with your 3/5, but even then it's the wrong
> solution. In that change you move the commit_notes() into that "if"
> branch that needs the "logmsg", and it's correct that you need to
> NULL-init it to safely pass it to free().

You are correct, will fix.

> But let's just declare the "logmsg" in that if block then, and move the
> free() over there, that reduces the scope it's in.
>
> >  	const char * const *usage;
> > -	struct note_data d = { 0, 0, NULL, STRBUF_INIT };
> > +	struct note_data d = { .buf = STRBUF_INIT };
>
> This change is good, but then let's change the other such case in this
> file to a designated init too, and make the commit message etc. be about
> using designated init here.

OK.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v4 3/5] notes.c: drop unreachable code in 'append_edit()'
  2023-01-15 20:59         ` Eric Sunshine
  2023-01-15 21:10           ` Eric Sunshine
@ 2023-01-28 11:50           ` Teng Long
  2023-01-30  5:38             ` Eric Sunshine
  1 sibling, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-01-28 11:50 UTC (permalink / raw)
  To: sunshine; +Cc: avarab, dyroneteng, git, tenglong.tl

Eric Sunshine writes:

> > Situation of note "removing" shouldn't happen in 'append_edit()',
> > unless it's a bug. So, let's drop the unreachable "else" code
> > in "append_edit()".
> >
> > The notes operation "append" is different with "add", the latter
> > supports to overwrite the existing note then let the "removing"
> > happen (e.g. execute `git notes add -f -F /dev/null` on an existing
> > note), but the former will not because it only does "appends" but
> > not doing "overwrites".
> >
> > Signed-off-by: Teng Long <dyroneteng@gmail.com>
> > ---
> > diff --git a/builtin/notes.c b/builtin/notes.c
> > @@ -630,13 +630,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)
> >                 if (add_note(t, &object, &new_note, combine_notes_overwrite))
> >                         BUG("combine_notes_overwrite failed");
> >                 logmsg = xstrfmt("Notes added by 'git notes %s'", argv[0]);
> > -       } else {
> > -               fprintf(stderr, _("Removing note for object %s\n"),
> > -                       oid_to_hex(&object));
> > -               remove_note(t, object.hash);
> > -               logmsg = xstrfmt("Notes removed by 'git notes %s'", argv[0]);
> > +               commit_notes(the_repository, t, logmsg);
> >         }
> > -       commit_notes(the_repository, t, logmsg);
>
> This change breaks removal of notes using "git notes edit". Prior to
> this change, if you delete the content of a note using "git notes
> edit", then the note is removed. Following this change, the note
> remains, which contradicts documented[1] behavior.
>
> [1]: Unfortunately, perhaps, this behavior is documented under the
> "remove" subcommand rather than the "edit" subcommand, but it is
> nevertheless documented and has worked this way for ages, so this
> patch causes a regression.

As the commit msg describes, the subcommands I understand should have clear
responsibilities as possible (documentaion may have some effect in my mind). So,
the removal opertion under "append subcommand" here is little wired to me, but
your suggestion makes sense, this may have compatibility issues. Although I
think it's weird that someone would use this in the presence of the remove
subcommand, and my feeling is that this code is actually copied from the add
method (introduced by 52694cdabbf68f19c8289416e7bb3bbef41d8d27), but I'm not
sure.

So, it's ok for me to drop this one.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v4 3/5] notes.c: drop unreachable code in 'append_edit()'
  2023-01-28 11:50           ` Teng Long
@ 2023-01-30  5:38             ` Eric Sunshine
  2023-02-01  8:08               ` Teng Long
  0 siblings, 1 reply; 186+ messages in thread
From: Eric Sunshine @ 2023-01-30  5:38 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, tenglong.tl

On Sat, Jan 28, 2023 at 6:50 AM Teng Long <dyroneteng@gmail.com> wrote:
> Eric Sunshine writes:
> > > Situation of note "removing" shouldn't happen in 'append_edit()',
> > > unless it's a bug. So, let's drop the unreachable "else" code
> > > in "append_edit()".
> > >
> > > Signed-off-by: Teng Long <dyroneteng@gmail.com>
> > > ---
> > > -       } else {
> > > -               fprintf(stderr, _("Removing note for object %s\n"),
> > > -                       oid_to_hex(&object));
> > > -               remove_note(t, object.hash);
> > > -               logmsg = xstrfmt("Notes removed by 'git notes %s'", argv[0]);
> > > +               commit_notes(the_repository, t, logmsg);
> > >         }
> > > -       commit_notes(the_repository, t, logmsg);
> >
> > This change breaks removal of notes using "git notes edit". Prior to
> > this change, if you delete the content of a note using "git notes
> > edit", then the note is removed. Following this change, the note
> > remains, which contradicts documented[1] behavior.
>
> As the commit msg describes, the subcommands I understand should have clear
> responsibilities as possible (documentaion may have some effect in my mind). So,
> the removal opertion under "append subcommand" here is little wired to me, but
> your suggestion makes sense, this may have compatibility issues. Although I
> think it's weird that someone would use this in the presence of the remove
> subcommand, and my feeling is that this code is actually copied from the add
> method (introduced by 52694cdabbf68f19c8289416e7bb3bbef41d8d27), but I'm not
> sure.
>
> So, it's ok for me to drop this one.

It's unfortunate, perhaps, that the git-notes command-set is not
orthogonal, but it's another case of historic accretion. According to
the history, as originally implemented, git-notes only supported two
commands, "edit" and "show", and "edit" was responsible for _all_
mutation operations, including deletion. git-notes only grew a more
complete set of commands a couple years later.

At any rate, even though the original behaviors may not make as much
sense these days now that git-notes has a more complete set of
commands, we still need to be careful not to break existing workflows
and scripts (tooling). That's why it would be nice to beef up the
tests so that the existing behaviors don't get broken by changes such
as this patch wants to make. But, as mentioned earlier, the additional
tests probably shouldn't be part of this series, but rather can be
done as a separate series (by someone; it doesn't have to be you).

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v4 3/5] notes.c: drop unreachable code in 'append_edit()'
  2023-01-30  5:38             ` Eric Sunshine
@ 2023-02-01  8:08               ` Teng Long
  0 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-02-01  8:08 UTC (permalink / raw)
  To: sunshine; +Cc: avarab, dyroneteng, git, tenglong.tl

On Mon, 30 Jan 2023 00:38:02 -0500 Eric Sunshine <sunshine@sunshineco.com>
wrote:

> It's unfortunate, perhaps, that the git-notes command-set is not
> orthogonal, but it's another case of historic accretion. According to
> the history, as originally implemented, git-notes only supported two
> commands, "edit" and "show", and "edit" was responsible for _all_
> mutation operations, including deletion. git-notes only grew a more
> complete set of commands a couple years later.
>
> At any rate, even though the original behaviors may not make as much
> sense these days now that git-notes has a more complete set of
> commands, we still need to be careful not to break existing workflows
> and scripts (tooling). That's why it would be nice to beef up the
> tests so that the existing behaviors don't get broken by changes such
> as this patch wants to make. But, as mentioned earlier, the additional
> tests probably shouldn't be part of this series, but rather can be
> done as a separate series (by someone; it doesn't have to be you).

Make sense by your detailed explanation.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* [PATCH v5 0/3] notes.c: introduce "--separator" option
  2023-01-12  2:48     ` [PATCH v4 0/5] notes.c: introduce "--separator" optio Teng Long
                         ` (4 preceding siblings ...)
  2023-01-12  2:48       ` [PATCH v4 5/5] notes.c: introduce "--separator" option Teng Long
@ 2023-02-16 13:05       ` Teng Long
  2023-02-16 13:05         ` [PATCH v5 1/3] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
                           ` (3 more replies)
  5 siblings, 4 replies; 186+ messages in thread
From: Teng Long @ 2023-02-16 13:05 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, sunshine, tenglong.tl

Diff since v4:

* Remove commits that may have compatibility issues
* Fix the problems that mentioned in v4
* Use OPT_STRING_LIST instead of OPT_CALLBACK_F to parse multiple "-m"

Thanks.

Teng Long (3):
  notes.c: cleanup 'strbuf_grow' call in 'append_edit'
  notes.c: cleanup for "designated init"
  notes.c: introduce "--separator" option

 Documentation/git-notes.txt | 12 ++++++--
 builtin/notes.c             | 37 ++++++++++++++++++-----
 t/t3301-notes.sh            | 58 +++++++++++++++++++++++++++++++++++++
 3 files changed, 97 insertions(+), 10 deletions(-)

Range-diff against v4:
1:  f00a7596 ! 1:  9a450669 notes.c: cleanup 'strbuf_grow' call in 'append_edit'
    @@ Commit message
         needed, but actually when inserting, "strbuf_insertstr(&d.buf, 0,
         "\n");" will do the "grow" for us.
     
    +    Best guess may be that the author originally inserted "\n" manually by
    +    direct manipulation of the strbuf rather than employing a strbuf
    +    function, but then switched to strbuf_insert() before submitting the
    +    series and forgot to remove the now-unnecessary strbuf_grow().
    +
         Signed-off-by: Teng Long <dyroneteng@gmail.com>
    +    Helped-by: Eric Sunshine <sunshine@sunshineco.com>
     
      ## builtin/notes.c ##
    +@@ builtin/notes.c: 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);
     @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char *prefix)
      		enum object_type type;
      		char *prev_buf = read_object_file(note, &type, &size);
2:  29f7703b < -:  -------- notes.c: cleanup for "designated init" and "char ptr init"
3:  7b756b4c < -:  -------- notes.c: drop unreachable code in 'append_edit()'
4:  d41ba140 < -:  -------- notes.c: provide tips when target and append note are both empty
5:  f7edbd0e < -:  -------- notes.c: introduce "--separator" option
-:  -------- > 2:  e7bc6060 notes.c: cleanup for "designated init"
-:  -------- > 3:  a74c96d6 notes.c: introduce "--separator" option
-- 
2.39.2.459.g31d98a8e.dirty


^ permalink raw reply	[flat|nested] 186+ messages in thread

* [PATCH v5 1/3] notes.c: cleanup 'strbuf_grow' call in 'append_edit'
  2023-02-16 13:05       ` [PATCH v5 0/3] " Teng Long
@ 2023-02-16 13:05         ` Teng Long
  2023-02-16 18:39           ` Junio C Hamano
  2023-02-16 13:05         ` [PATCH v5 2/3] notes.c: cleanup for "designated init" Teng Long
                           ` (2 subsequent siblings)
  3 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-02-16 13:05 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, sunshine, tenglong.tl

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.

Best guess may be that the author originally inserted "\n" manually by
direct manipulation of the strbuf rather than employing a strbuf
function, but then switched to strbuf_insert() before submitting the
series and forgot to remove the now-unnecessary strbuf_grow().

Signed-off-by: Teng Long <dyroneteng@gmail.com>
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
---
 builtin/notes.c | 2 --
 1 file changed, 2 deletions(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index 80d9dfd2..23cb6f0d 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -215,7 +215,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);
@@ -618,7 +617,6 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		enum object_type type;
 		char *prev_buf = read_object_file(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.39.2.459.g31d98a8e.dirty


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v5 2/3] notes.c: cleanup for "designated init"
  2023-02-16 13:05       ` [PATCH v5 0/3] " Teng Long
  2023-02-16 13:05         ` [PATCH v5 1/3] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
@ 2023-02-16 13:05         ` Teng Long
  2023-02-16 18:39           ` Junio C Hamano
  2023-02-16 13:05         ` [PATCH v5 3/3] notes.c: introduce "--separator" option Teng Long
  2023-02-23  7:29         ` [PATCH v6 0/3] " Teng Long
  3 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-02-16 13:05 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, sunshine, tenglong.tl

The "struct note_data d = { 0, 0, NULL, STRBUF_INIT };" style could be
replaced with designated init format.
---
 builtin/notes.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index 23cb6f0d..553ae2bd 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -401,7 +401,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,
@@ -567,7 +567,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.39.2.459.g31d98a8e.dirty


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v5 3/3] notes.c: introduce "--separator" option
  2023-02-16 13:05       ` [PATCH v5 0/3] " Teng Long
  2023-02-16 13:05         ` [PATCH v5 1/3] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
  2023-02-16 13:05         ` [PATCH v5 2/3] notes.c: cleanup for "designated init" Teng Long
@ 2023-02-16 13:05         ` Teng Long
  2023-02-16 23:22           ` Junio C Hamano
  2023-02-23  7:29         ` [PATCH v6 0/3] " Teng Long
  3 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-02-16 13:05 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, sunshine, tenglong.tl

When appending to a given notes object and the appended note is not
empty too, we will insert a blank line at first which separates the
existing note and the appended one, which as the separator.

Sometimes, we want to use a specified <separator> as the separator. For
example, if we specify as:

    * --separator='------': we will insert "------\n" as the separator,
    because user do not provide the line break char at last, we will add
    the trailing '\n' compatibly.

    * --separator='------\n': we will insert as-is because it contains
    the line break at last.

    * not specified --separator option: will use '\n' as the separator
    when do appending and this is the default behavour.

    * --separator='': we specify an empty separator which has the same
    behavour with --separator='\n' and or not specified the option.

In addition, if a user specifies multple "-m" with "--separator", the
separator should be inserted between the messages too, so we use
OPT_STRING_LIST instead of OPT_CALLBACK_F to parse "-m" option, make
sure the option value of "--separator" been parsed already when we need
it.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-notes.txt | 12 ++++++--
 builtin/notes.c             | 31 +++++++++++++++++---
 t/t3301-notes.sh            | 58 +++++++++++++++++++++++++++++++++++++
 3 files changed, 95 insertions(+), 6 deletions(-)

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index efbc10f0..5abe6092 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -11,7 +11,7 @@ SYNOPSIS
 'git notes' [list [<object>]]
 'git notes' add [-f] [--allow-empty] [-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] [-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>
@@ -86,7 +86,9 @@ 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.
+	Creates a new notes object if needed. If the note and the
+	message are not empty, "\n" will be inserted between them.
+	Use the `--separator` option to insert other delimiters.
 
 edit::
 	Edit the notes for a given object (defaults to HEAD).
@@ -159,6 +161,12 @@ OPTIONS
 	Allow an empty note object to be stored. The default behavior is
 	to automatically remove empty notes.
 
+--separator <separator>::
+	The '<separator>' inserted between the note and message
+	by 'append', "\n" by default. A custom separator can be
+	provided, if it doesn't end in a "\n", one will be added
+	implicitly .
+
 --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 553ae2bd..524976fe 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -24,6 +24,7 @@
 #include "notes-utils.h"
 #include "worktree.h"
 
+static char *separator = NULL;
 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>]"),
@@ -209,6 +210,16 @@ 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)
+		strbuf_insertstr(message, pos, "\n");
+	else if (separator[strlen(separator) - 1] == '\n')
+		strbuf_insertstr(message, pos, separator);
+	else
+		strbuf_insertf(message, pos, "%s%s", separator, "\n");
+}
+
 static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
 {
 	struct note_data *d = opt->value;
@@ -567,11 +578,12 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 	const struct object_id *note;
 	char *logmsg;
 	const char * const *usage;
+	size_t message_idx;
 	struct note_data d = { .buf = STRBUF_INIT };
+	struct string_list message = STRING_LIST_INIT_DUP;
 	struct option options[] = {
-		OPT_CALLBACK_F('m', "message", &d, N_("message"),
-			N_("note contents as a string"), PARSE_OPT_NONEG,
-			parse_msg_arg),
+		OPT_STRING_LIST('m', "message", &message, N_("message"),
+			N_("note contents as a string")),
 		OPT_CALLBACK_F('F', "file", &d, N_("file"),
 			N_("note contents in a file"), PARSE_OPT_NONEG,
 			parse_file_arg),
@@ -583,6 +595,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 <separator> as separator before appending a message")),
 		OPT_END()
 	};
 	int edit = !strcmp(argv[0], "edit");
@@ -596,6 +610,15 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		usage_with_options(usage, options);
 	}
 
+	for (message_idx = 0; message_idx < message.nr; message_idx++) {
+		if (d.buf.len)
+			insert_separator(&d.buf, d.buf.len);
+		strbuf_insertstr(&d.buf, d.buf.len,
+				 message.items[message_idx].string);
+		strbuf_stripspace(&d.buf, 0);
+		d.given = 1;
+	}
+
 	if (d.given && edit)
 		fprintf(stderr, _("The -m/-F/-c/-C options have been deprecated "
 			"for the 'edit' subcommand.\n"
@@ -618,7 +641,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		char *prev_buf = read_object_file(note, &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..fe00497b 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -521,6 +521,64 @@ 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 separatoro 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 to existing note with "git notes append"' '
 	cat >expect <<-EOF &&
 		Initial set of notes
-- 
2.39.2.459.g31d98a8e.dirty


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* Re: [PATCH v5 1/3] notes.c: cleanup 'strbuf_grow' call in 'append_edit'
  2023-02-16 13:05         ` [PATCH v5 1/3] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
@ 2023-02-16 18:39           ` Junio C Hamano
  2023-02-20  3:34             ` Teng Long
  0 siblings, 1 reply; 186+ messages in thread
From: Junio C Hamano @ 2023-02-16 18:39 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> 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.

Correct.  I think the code ends up trying to exactly size the strbuf
as multiple "-m message" options are encountered on the command
line, which is probably pointless (see below).

> Best guess may be that the author originally inserted "\n" manually by
> direct manipulation of the strbuf rather than employing a strbuf
> function, but then switched to strbuf_insert() before submitting the
> series and forgot to remove the now-unnecessary strbuf_grow().

Please do not speculate when you do not have to.  "git blame" is
your friend.

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>
> ---
>  builtin/notes.c | 2 --
>  1 file changed, 2 deletions(-)
>
> diff --git a/builtin/notes.c b/builtin/notes.c
> index 80d9dfd2..23cb6f0d 100644
> --- a/builtin/notes.c
> +++ b/builtin/notes.c
> @@ -215,7 +215,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);
> @@ -618,7 +617,6 @@ static int append_edit(int argc, const char **argv, const char *prefix)
>  		enum object_type type;
>  		char *prev_buf = read_object_file(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)

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v5 2/3] notes.c: cleanup for "designated init"
  2023-02-16 13:05         ` [PATCH v5 2/3] notes.c: cleanup for "designated init" Teng Long
@ 2023-02-16 18:39           ` Junio C Hamano
  0 siblings, 0 replies; 186+ messages in thread
From: Junio C Hamano @ 2023-02-16 18:39 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> The "struct note_data d = { 0, 0, NULL, STRBUF_INIT };" style could be
> replaced with designated init format.
> ---

Missing sign-off.

The change itself with the explanation does make sense.

Thanks.

>  builtin/notes.c | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/builtin/notes.c b/builtin/notes.c
> index 23cb6f0d..553ae2bd 100644
> --- a/builtin/notes.c
> +++ b/builtin/notes.c
> @@ -401,7 +401,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,
> @@ -567,7 +567,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,

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v5 3/3] notes.c: introduce "--separator" option
  2023-02-16 13:05         ` [PATCH v5 3/3] notes.c: introduce "--separator" option Teng Long
@ 2023-02-16 23:22           ` Junio C Hamano
  2023-02-20 14:00             ` Teng Long
  0 siblings, 1 reply; 186+ messages in thread
From: Junio C Hamano @ 2023-02-16 23:22 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> When appending to a given notes object and the appended note is not
> empty too, we will insert a blank line at first which separates the
> existing note and the appended one, which as the separator.

", which as the separator" does not sound grammatically correct.
Without that part, the above is a perfectly readable description of
the current behaviour.

> Sometimes, we want to use a specified <separator> as the separator. For
> example, if we specify as:
>
>     * --separator='------': we will insert "------\n" as the separator,
>     because user do not provide the line break char at last, we will add
>     the trailing '\n' compatibly.

In a way compatible to what?  I think s/compatibly// would be even
easier to read.  I think you are doing that for convenience.

>     * --separator='------\n': we will insert as-is because it contains
>     the line break at last.

If this behaviour gets spelled out like this, it needs to be
justified.

After seeing that "------" gives the user these dashes on its own
single line, wouldn't a natural expectation by the user be to see a
line with dashes, followed by a blank line, if you give "------\n"?
How do you justify removal of that newline in a way that is easy to
understand to readers?

I am not saying that you should allow --separator="---\n\n\n" to
give three blank lines between paragraphs. I think it makes sense to
keep the stripspace in the code after a paragraph gets added. I just
prefer to see it done as our design choice, not "because there is
stripspace that removes them", i.e. what the code happens to do. 

>     * not specified --separator option: will use '\n' as the separator
>     when do appending and this is the default behavour.

s/not specified --separator option/no --separator option/; the way
you phrased can be misread for

	git notes --separator -m foo -m bar

i.e. any additional specifics is not given but --separator is still
on the command line.  But I do not think you meant that---rather
this entry is what happens by default, i.e. a blank line separates
each paragraph.

>     * --separator='': we specify an empty separator which has the same
>     behavour with --separator='\n' and or not specified the option.

I do not quite see why it is necessary to spell this out.  Isn't
this a natural consequence of the first one (i.e. "six dashes
without any terminating LF gets a line with dashes plus LF"
naturally extends to "0 dashes without any terminating LF gets a
blank line")?

> In addition, if a user specifies multple "-m" with "--separator", the
> separator should be inserted between the messages too, so we use
> OPT_STRING_LIST instead of OPT_CALLBACK_F to parse "-m" option, make
> sure the option value of "--separator" been parsed already when we need
> it.

This is hard to grok.  Is it an instruction to whoever is
implementing this new feature, or is it an instruct to end-users
telling that they need to give --separator before they start giving
-m <msg>, -F <file>, -c <object>, etc.?

> diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
> index efbc10f0..5abe6092 100644
> --- a/Documentation/git-notes.txt
> +++ b/Documentation/git-notes.txt
> @@ -11,7 +11,7 @@ SYNOPSIS
>  'git notes' [list [<object>]]
>  'git notes' add [-f] [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]

Doesn't add allow you to say

	$ git notes add -m foo -m bar

Shouldn't it also honor --separator to specify an alternate
paragraph break?

> -'git notes' append [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
> +'git notes' append [--allow-empty] [--separator] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]

"--separator" -> "--separator=<paragraph-break>" or something?

> @@ -86,7 +86,9 @@ 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.
> +	Creates a new notes object if needed. If the note and the
> +	message are not empty, "\n" will be inserted between them.
> +	Use the `--separator` option to insert other delimiters.

"\n" is so, ... programmer lingo?  "A blank line" is inserted
between these paragraphs.

> +--separator <separator>::
> +	The '<separator>' inserted between the note and message
> +	by 'append', "\n" by default. A custom separator can be

I see no reason to single out 'append'; "add -m <msg> -m <msg>"
follows exactly the same paragraph concatenation logic in the
current code, no?  If we allow customized paragraph separators,
we should use the same logic there as well.

It probably is simpler to explain if you treat the "current note in
'append'" as if the text were just "earlier paragraphs", to which
more paragraphs taken from each -m <msg> and -F <file> are
concatenated, with paragraph break before each of them.  In the case
of 'add', there happens to be zero "earlier paragraphs", but
everything else gets concatenated the same way, no?

> +	provided, if it doesn't end in a "\n", one will be added
> +	implicitly .

Funny punctuation.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v5 1/3] notes.c: cleanup 'strbuf_grow' call in 'append_edit'
  2023-02-16 18:39           ` Junio C Hamano
@ 2023-02-20  3:34             ` Teng Long
  0 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-02-20  3:34 UTC (permalink / raw)
  To: gitster; +Cc: avarab, dyroneteng, git, sunshine, tenglong.tl

Junio C Hamano <gitster@pobox.com> wrote on Thu, 16 Feb 2023 10:39:05 -0800:

> > Best guess may be that the author originally inserted "\n" manually by
> > direct manipulation of the strbuf rather than employing a strbuf
> > function, but then switched to strbuf_insert() before submitting the
> > series and forgot to remove the now-unnecessary strbuf_grow().
>
> Please do not speculate when you do not have to.  "git blame" is
> your friend.

My bad, I checked with "git blame" but did not describe in detail.

> 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.

Will apply by your content, thanks very much.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v5 3/3] notes.c: introduce "--separator" option
  2023-02-16 23:22           ` Junio C Hamano
@ 2023-02-20 14:00             ` Teng Long
  2023-02-21 21:31               ` Junio C Hamano
  0 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-02-20 14:00 UTC (permalink / raw)
  To: gitster; +Cc: avarab, dyroneteng, git, sunshine, tenglong.tl

Junio C Hamano <gitster@pobox.com> wrote on Thu, 16 Feb 2023 15:22:16 -0800:

> > When appending to a given notes object and the appended note is not
> > empty too, we will insert a blank line at first which separates the
> > existing note and the appended one, which as the separator.
>
> ", which as the separator" does not sound grammatically correct.
> Without that part, the above is a perfectly readable description of
> the current behaviour.

OK, I will remove ", which as the separator".

> > Sometimes, we want to use a specified <separator> as the separator. For
> > example, if we specify as:
> >
> >     * --separator='------': we will insert "------\n" as the separator,
> >     because user do not provide the line break char at last, we will add
> >     the trailing '\n' compatibly.
>
> In a way compatible to what?  I think s/compatibly// would be even
> easier to read.  I think you are doing that for convenience.

My bad, I think I use a wrong word, maybe s/compatibly/automatically
and I look back and think the representation of "------\n" is not
correct, because "\n" is two characters here and will not treat
as a newline. So, after modification, maybe like:

   * --separator='------': we will insert "------" and a trailing
   newline character, because if no newline specified at the end,
   one will be added automatically.

> >     * --separator='------\n': we will insert as-is because it contains
> >     the line break at last.
>
> If this behaviour gets spelled out like this, it needs to be
> justified.
>
> After seeing that "------" gives the user these dashes on its own
> single line, wouldn't a natural expectation by the user be to see a
> line with dashes, followed by a blank line, if you give "------\n"?
> How do you justify removal of that newline in a way that is easy to
> understand to readers?
>
> I am not saying that you should allow --separator="---\n\n\n" to
> give three blank lines between paragraphs. I think it makes sense to
> keep the stripspace in the code after a paragraph gets added. I just
> prefer to see it done as our design choice, not "because there is
> stripspace that removes them", i.e. what the code happens to do.

Firstly, like the problem I talked above, please let me figure out that
"------\n" is not represent as "------" and a blank line, but only
as "------\n" verbatim because "\n" will be treated as two characters
but not a blank line while parsing. If the user wants to specify a
blank line in the value of the option, they can pass "CTRL+v CTRL+j".

Then, I think I did get some interference from old logic. Returning
to the user scenario, I think about what the user needs and what is
my original idea is: consistent behavior

   * behavior 1: What the user enters is used as the delimiter itself
   without any special processing.

   * behavior 2: No matter what the user enters, we always add a
   newline at the end or other logic like stripspace, etc.

I prefer the first one, sometimes users want to be next to the previous
note, then:

   git notes append -m foo -m bar --separator=""

and they can also choose to add as many line breaks as they want, then:

    export LF="
    "
    git notes append -m foo -m bar --separator="$LF$LF$LF"

We didn't help users make choices but I have to say it may be a little
inconvenient to enter a newline character in the terminal, but I still
prefer this way.

> >     * not specified --separator option: will use '\n' as the separator
> >     when do appending and this is the default behavour.
>
> s/not specified --separator option/no --separator option/; the way
> you phrased can be misread for
>
Will apply as:

        * no --separator option: will use '\n' as the separator when
        do appending and this is the default behavour.

> 	git notes --separator -m foo -m bar
>
> i.e. any additional specifics is not given but --separator is still
> on the command line.  But I do not think you meant that---rather
> this entry is what happens by default, i.e. a blank line separates
> each paragraph.

Yes, only separate the messages(-m), but not each paragraph in it.

> >     * --separator='': we specify an empty separator which has the same
> >     behavour with --separator='\n' and or not specified the option.
>
> I do not quite see why it is necessary to spell this out.  Isn't
> this a natural consequence of the first one (i.e. "six dashes
> without any terminating LF gets a line with dashes plus LF"
> naturally extends to "0 dashes without any terminating LF gets a
> blank line")?

Agree.. will remove.

> > In addition, if a user specifies multple "-m" with "--separator", the
> > separator should be inserted between the messages too, so we use
> > OPT_STRING_LIST instead of OPT_CALLBACK_F to parse "-m" option, make
> > sure the option value of "--separator" been parsed already when we need
> > it.
>
> This is hard to grok.  Is it an instruction to whoever is
> implementing this new feature, or is it an instruct to end-users
> telling that they need to give --separator before they start giving
> -m <msg>, -F <file>, -c <object>, etc.?

No, it's not the order of the user give, but the backend we deal.

We use "parse_msg_arg" as a callback when parsing "-m " by OPT_CALLBACK_F,
so if we have to read the separator before we parse it, so we could insert
it correctly between the messages, So I use OPT_STRING_LIST instead.


> > diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
> > index efbc10f0..5abe6092 100644
> > --- a/Documentation/git-notes.txt
> > +++ b/Documentation/git-notes.txt
> > @@ -11,7 +11,7 @@ SYNOPSIS
> >  'git notes' [list [<object>]]
> >  'git notes' add [-f] [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
>
> Doesn't add allow you to say
>
> 	$ git notes add -m foo -m bar
>
> Shouldn't it also honor --separator to specify an alternate
> paragraph break?

Agree, you also mentioned me that, does git-notes-add need to be implies by
this option too? I think it needs too.

> > @@ -86,7 +86,9 @@ 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.
> > +	Creates a new notes object if needed. If the note and the
> > +	message are not empty, "\n" will be inserted between them.
> > +	Use the `--separator` option to insert other delimiters.
>
> "\n" is so, ... programmer lingo?  "A blank line" is inserted
> between these paragraphs.

Will apply.

> > +--separator <separator>::
> > +	The '<separator>' inserted between the note and message
> > +	by 'append', "\n" by default. A custom separator can be
>
> I see no reason to single out 'append'; "add -m <msg> -m <msg>"
> follows exactly the same paragraph concatenation logic in the
> current code, no?  If we allow customized paragraph separators,
> we should use the same logic there as well.

Agree, as I replied above, I think it will be done in next patch.

> It probably is simpler to explain if you treat the "current note in
> 'append'" as if the text were just "earlier paragraphs", to which
> more paragraphs taken from each -m <msg> and -F <file> are
> concatenated, with paragraph break before each of them.  In the case
> of 'add', there happens to be zero "earlier paragraphs", but
> everything else gets concatenated the same way, no?

Yes, they are the same way, you are right, so we should let add and append
both be implied by the option.

> > +	provided, if it doesn't end in a "\n", one will be added
> > +	implicitly .
>
> Funny punctuation.

My bad, will fix.

Thanks very much for your detailed review.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v5 3/3] notes.c: introduce "--separator" option
  2023-02-20 14:00             ` Teng Long
@ 2023-02-21 21:31               ` Junio C Hamano
  2023-02-22  8:17                 ` Teng Long
  0 siblings, 1 reply; 186+ messages in thread
From: Junio C Hamano @ 2023-02-21 21:31 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

>> > In addition, if a user specifies multple "-m" with "--separator", the
>> > separator should be inserted between the messages too, so we use
>> > OPT_STRING_LIST instead of OPT_CALLBACK_F to parse "-m" option, make
>> > sure the option value of "--separator" been parsed already when we need
>> > it.
>>
>> This is hard to grok.  Is it an instruction to whoever is
>> implementing this new feature, or is it an instruct to end-users
>> telling that they need to give --separator before they start giving
>> -m <msg>, -F <file>, -c <object>, etc.?
>
> No, it's not the order of the user give, but the backend we deal.
>
> We use "parse_msg_arg" as a callback when parsing "-m " by OPT_CALLBACK_F,
> so if we have to read the separator before we parse it, so we could insert
> it correctly between the messages, So I use OPT_STRING_LIST instead.

That is an implementation detail of how you chose to implement the
feature, and not an inherent limitation, is it?  It makes a lame
excuse to give users a hard-to-use UI.

For example, we could parse all the command line parameters without
making any action other than recording the strings given to -m and
contents of files given via -F in the order they appeared on the
command line, all in a single string list, while remembering the
last value of --separator you got, and then at the end concatenate
these strings using the final value of the separator, no?

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v5 3/3] notes.c: introduce "--separator" option
  2023-02-21 21:31               ` Junio C Hamano
@ 2023-02-22  8:17                 ` Teng Long
  2023-02-22 23:15                   ` Junio C Hamano
  0 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-02-22  8:17 UTC (permalink / raw)
  To: gitster; +Cc: avarab, dyroneteng, git, sunshine, tenglong.tl

Junio C Hamano <gitster@pobox.com> writes:

> > We use "parse_msg_arg" as a callback when parsing "-m " by OPT_CALLBACK_F,
> > so if we have to read the separator before we parse it, so we could insert
> > it correctly between the messages, So I use OPT_STRING_LIST instead.
>
> That is an implementation detail of how you chose to implement the
> feature, and not an inherent limitation, is it?  It makes a lame
> excuse to give users a hard-to-use UI.

> For example, we could parse all the command line parameters without
> making any action other than recording the strings given to -m and
> contents of files given via -F in the order they appeared on the
> command line, all in a single string list, while remembering the
> last value of --separator you got, and then at the end concatenate
> these strings using the final value of the separator, no?

Yes, please let'me clarify it, we shouldn't to give users a hard-to-use UI, so:

     1. order of "-m" and "-F" matters, it determine the order of the paragraphs
     (remain the same as before, which I need to fix in next patch).

     2. order of "-m""-F" and "--separator" doesn't matter.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v5 3/3] notes.c: introduce "--separator" option
  2023-02-22  8:17                 ` Teng Long
@ 2023-02-22 23:15                   ` Junio C Hamano
  0 siblings, 0 replies; 186+ messages in thread
From: Junio C Hamano @ 2023-02-22 23:15 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> Junio C Hamano <gitster@pobox.com> writes:
>
>> > We use "parse_msg_arg" as a callback when parsing "-m " by OPT_CALLBACK_F,
>> > so if we have to read the separator before we parse it, so we could insert
>> > it correctly between the messages, So I use OPT_STRING_LIST instead.
>>
>> That is an implementation detail of how you chose to implement the
>> feature, and not an inherent limitation, is it?  It makes a lame
>> excuse to give users a hard-to-use UI.
>
>> For example, we could parse all the command line parameters without
>> making any action other than recording the strings given to -m and
>> contents of files given via -F in the order they appeared on the
>> command line, all in a single string list, while remembering the
>> last value of --separator you got, and then at the end concatenate
>> these strings using the final value of the separator, no?
>
> Yes, please let'me clarify it, we shouldn't to give users a hard-to-use UI, so:
>
>      1. order of "-m" and "-F" matters, it determine the order of the paragraphs
>      (remain the same as before, which I need to fix in next patch).
>
>      2. order of "-m""-F" and "--separator" doesn't matter.
>
> Thanks.

Sounds good.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* [PATCH v6 0/3] notes.c: introduce "--separator" option
  2023-02-16 13:05       ` [PATCH v5 0/3] " Teng Long
                           ` (2 preceding siblings ...)
  2023-02-16 13:05         ` [PATCH v5 3/3] notes.c: introduce "--separator" option Teng Long
@ 2023-02-23  7:29         ` Teng Long
  2023-02-23  7:29           ` [PATCH v6 1/3] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
                             ` (4 more replies)
  3 siblings, 5 replies; 186+ messages in thread
From: Teng Long @ 2023-02-23  7:29 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, sunshine, tenglong.tl, gitster

From: Teng Long <dyroneteng@gmail.com>

Diff since v5:

1. Optimized and fixed the commit-message problems.
2. Optimized and fixed the related documentation.
3. the order of "-m" and -F should matter, but not, it was newly
introduced problem in v4, now fixed in v5.

By the way, In order to add a separator when parsing '-F and
'-m', I used 'string_list' to save all messages for subsequent
processing instead of processing in callback, because the value
of '--separator' has not been initialized and set at this time and
I have not found a similar "OPT_XXX" API for doing this. I'm not
sure if it's worth adding a similar API to parse the value of an
option in advance and initialize it for this scenario. If so, it
may not be suitable to continue to incubate in the current patch
series, but I will try to contribute it.

Thanks.

Teng Long (3):
  notes.c: cleanup 'strbuf_grow' call in 'append_edit'
  notes.c: cleanup for "designated init"
  notes.c: introduce '--separator=<paragraph-break>' option

 Documentation/git-notes.txt |  20 ++++++--
 builtin/notes.c             |  78 ++++++++++++++++++----------
 t/t3301-notes.sh            | 100 ++++++++++++++++++++++++++++++++++++
 3 files changed, 168 insertions(+), 30 deletions(-)

Range-diff against v5:
1:  9a450669 ! 1:  b029ee0b notes.c: cleanup 'strbuf_grow' call in 'append_edit'
    @@ Commit message
         needed, but actually when inserting, "strbuf_insertstr(&d.buf, 0,
         "\n");" will do the "grow" for us.
     
    -    Best guess may be that the author originally inserted "\n" manually by
    -    direct manipulation of the strbuf rather than employing a strbuf
    -    function, but then switched to strbuf_insert() before submitting the
    -    series and forgot to remove the now-unnecessary strbuf_grow().
    +    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 ##
     @@ builtin/notes.c: static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
2:  e7bc6060 ! 2:  043db631 notes.c: cleanup for "designated init"
    @@ Commit message
         The "struct note_data d = { 0, 0, NULL, STRBUF_INIT };" style could be
         replaced with designated init format.
     
    +    Signed-off-by: Teng Long <dyroneteng@gmail.com>
    +
      ## builtin/notes.c ##
     @@ builtin/notes.c: static int add(int argc, const char **argv, const char *prefix)
      	struct notes_tree *t;
3:  a74c96d6 ! 3:  d5a6c747 notes.c: introduce "--separator" option
    @@ Metadata
     Author: Teng Long <dyroneteng@gmail.com>
     
      ## Commit message ##
    -    notes.c: introduce "--separator" option
    +    notes.c: introduce '--separator=<paragraph-break>' option
     
    -    When appending to a given notes object and the appended note is not
    -    empty too, we will insert a blank line at first which separates the
    -    existing note and the appended one, which as the separator.
    +    When adding new notes or appending to an existing notes, we will
    +    insert a blank line between the paragraphs, like:
     
    -    Sometimes, we want to use a specified <separator> as the separator. For
    -    example, if we specify as:
    +         $ git notes add -m foo -m bar
    +         $ git notes show HEAD | cat
    +         foo
     
    -        * --separator='------': we will insert "------\n" as the separator,
    -        because user do not provide the line break char at last, we will add
    -        the trailing '\n' compatibly.
    +         bar
     
    -        * --separator='------\n': we will insert as-is because it contains
    -        the line break at last.
    +    The default behavour sometimes is not enough, the user may want
    +    to use a custom delimiter between paragraphs, like when
    +    specifiy one or more '-m' or '-F' options. So this commit
    +    introduces a new '--separator' option for 'git-notes-add' and
    +    'git-notes-append', for example when execute:
     
    -        * not specified --separator option: will use '\n' as the separator
    -        when do appending and this is the default behavour.
    +        $ git notes add -m foo -m bar --separator="-"
    +        $ git notes show HEAD | cat
    +        foo
    +        -
    +        bar
     
    -        * --separator='': we specify an empty separator which has the same
    -        behavour with --separator='\n' and or not specified the option.
    +    We will check the option value and if the value doesn't contail
    +    a trailing '\n', will add it automatically, so execute
     
    -    In addition, if a user specifies multple "-m" with "--separator", the
    -    separator should be inserted between the messages too, so we use
    -    OPT_STRING_LIST instead of OPT_CALLBACK_F to parse "-m" option, make
    -    sure the option value of "--separator" been parsed already when we need
    -    it.
    +          $ git notes add -m foo -m bar --separator="-"
    +    and
    +          $ export LF="
    +          "
    +          $ git notes add -m foo -m bar --separator="-$LF"
    +
    +    have the same behavour.
     
         Signed-off-by: Teng Long <dyroneteng@gmail.com>
     
      ## Documentation/git-notes.txt ##
     @@ 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] [-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] [-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: 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
     @@ Documentation/git-notes.txt: 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.
    -+	Creates a new notes object if needed. If the note and the
    -+	message are not empty, "\n" will be inserted between them.
    -+	Use the `--separator` option to insert other delimiters.
    ++	Creates a new notes object if needed. 
    ++	The default delimiter is a blank line, use the `--separator`
    ++	option to insert other delimiters. More specifically, if the
    ++	note and the message are not empty, the delimiter will be
    ++	inserted between them. If you specify multiple `-m` and `-F`
    ++	options, the delimiter will be inserted between the messages
    ++	too.
      
      edit::
      	Edit the notes for a given object (defaults to HEAD).
    @@ Documentation/git-notes.txt: OPTIONS
      	Allow an empty note object to be stored. The default behavior is
      	to automatically remove empty notes.
      
    -+--separator <separator>::
    -+	The '<separator>' inserted between the note and message
    -+	by 'append', "\n" by default. A custom separator can be
    -+	provided, if it doesn't end in a "\n", one will be added
    -+	implicitly .
    ++--separator <paragraph-break>::
    ++	The '<paragraph-break>' inserted between paragraphs.
    ++	A blank line by default.
     +
      --ref <ref>::
      	Manipulate the notes tree in <ref>.  This overrides
    @@ builtin/notes.c
     +static char *separator = NULL;
      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] [-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>"),
     @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_id *oid)
      	}
      }
    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_
     +	else
     +		strbuf_insertf(message, pos, "%s%s", separator, "\n");
     +}
    ++
    ++static void parse_messages(struct string_list *messages, struct note_data *d)
    ++{
    ++	size_t i;
    ++	for (i = 0; i < messages->nr; i++) {
    ++		if (d->buf.len)
    ++			insert_separator(&d->buf, d->buf.len);
    ++		strbuf_insertstr(&d->buf, d->buf.len,
    ++				 messages->items[i].string);
    ++		strbuf_stripspace(&d->buf, 0);
    ++		d->given = 1;
    ++	}
    ++}
     +
      static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
      {
    - 	struct note_data *d = opt->value;
    -@@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char *prefix)
    +-	struct note_data *d = opt->value;
    ++	struct string_list *msg = opt->value;
    + 
    + 	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;
    ++	string_list_append(msg, arg);
    + 	return 0;
    + }
    + 
    ++
    + static int parse_file_arg(const struct option *opt, const char *arg, int unset)
    + {
    +-	struct note_data *d = opt->value;
    ++	struct string_list *msg = opt->value;
    ++	struct strbuf buf = STRBUF_INIT;
    + 
    + 	BUG_ON_OPT_NEG(unset);
    + 
    +-	if (d->buf.len)
    +-		strbuf_addch(&d->buf, '\n');
    + 	if (!strcmp(arg, "-")) {
    +-		if (strbuf_read(&d->buf, 0, 1024) < 0)
    ++		if (strbuf_read(&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(&buf, arg, 1024) < 0)
    + 		die_errno(_("could not open or read '%s'"), arg);
    +-	strbuf_stripspace(&d->buf, 0);
    + 
    +-	d->given = 1;
    ++	string_list_append(msg, buf.buf);
    ++	strbuf_release(&buf);
    + 	return 0;
    + }
    + 
    +@@ 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;
    + 	struct note_data d = { .buf = STRBUF_INIT };
    ++	struct string_list messages = STRING_LIST_INIT_DUP;
    + 	struct option options[] = {
    +-		OPT_CALLBACK_F('m', "message", &d, N_("message"),
    ++		OPT_CALLBACK_F('m', "message", &messages, N_("message"),
    + 			N_("note contents as a string"), PARSE_OPT_NONEG,
    + 			parse_msg_arg),
    +-		OPT_CALLBACK_F('F', "file", &d, N_("file"),
    ++		OPT_CALLBACK_F('F', "file", &messages, N_("file"),
    + 			N_("note contents in a file"), PARSE_OPT_NONEG,
    + 			parse_file_arg),
    + 		OPT_CALLBACK_F('c', "reedit-message", &d, N_("object"),
    +@@ 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_END()
    + 	};
    + 
    +@@ builtin/notes.c: static int add(int argc, const char **argv, const char *prefix)
    + 		usage_with_options(git_notes_add_usage, options);
    + 	}
    + 
    ++	parse_messages(&messages, &d);
    + 	object_ref = argc > 1 ? argv[1] : "HEAD";
    + 
    + 	if (get_oid(object_ref, &object))
    +@@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char *prefix)
      	char *logmsg;
      	const char * const *usage;
    -+	size_t message_idx;
      	struct note_data d = { .buf = STRBUF_INIT };
    -+	struct string_list message = STRING_LIST_INIT_DUP;
    ++	struct string_list messages = STRING_LIST_INIT_DUP;
      	struct option options[] = {
     -		OPT_CALLBACK_F('m', "message", &d, N_("message"),
     -			N_("note contents as a string"), PARSE_OPT_NONEG,
    --			parse_msg_arg),
    -+		OPT_STRING_LIST('m', "message", &message, N_("message"),
    -+			N_("note contents as a string")),
    - 		OPT_CALLBACK_F('F', "file", &d, N_("file"),
    ++		OPT_CALLBACK_F('m', "message", &messages, N_("message"),
    ++			N_("note contents as a string"), PARSE_OPT_NONEG, 
    + 			parse_msg_arg),
    +-		OPT_CALLBACK_F('F', "file", &d, N_("file"),
    ++		OPT_CALLBACK_F('F', "file", &messages, N_("file"),
      			N_("note contents in a file"), PARSE_OPT_NONEG,
      			parse_file_arg),
    + 		OPT_CALLBACK_F('c', "reedit-message", &d, N_("object"),
     @@ builtin/notes.c: 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 <separator> as separator before appending a message")),
    ++			N_("insert <paragraph-break> between paragraphs")),
      		OPT_END()
      	};
      	int edit = !strcmp(argv[0], "edit");
    @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char
      		usage_with_options(usage, options);
      	}
      
    -+	for (message_idx = 0; message_idx < message.nr; message_idx++) {
    -+		if (d.buf.len)
    -+			insert_separator(&d.buf, d.buf.len);
    -+		strbuf_insertstr(&d.buf, d.buf.len,
    -+				 message.items[message_idx].string);
    -+		strbuf_stripspace(&d.buf, 0);
    -+		d.given = 1;
    -+	}
    ++	parse_messages(&messages, &d);
     +
      	if (d.given && edit)
      		fprintf(stderr, _("The -m/-F/-c/-C options have been deprecated "
    @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char
      		free(prev_buf);
     
      ## t/t3301-notes.sh ##
    +@@ t/t3301-notes.sh: 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
    + 
    +@@ t/t3301-notes.sh: 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 &&
     @@ t/t3301-notes.sh: test_expect_success 'listing non-existing notes fails' '
      	test_must_be_empty actual
      '
    @@ t/t3301-notes.sh: test_expect_success 'listing non-existing notes fails' '
     +	test_cmp expect actual
     +'
     +
    -+test_expect_success 'append: specify separatoro with line break' '
    ++test_expect_success 'append: specify separator with line break' '
     +	test_when_finished git notes remove HEAD &&
     +	cat >expect <<-\EOF &&
     +	notes-1
    @@ t/t3301-notes.sh: test_expect_success 'listing non-existing notes fails' '
     +	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 &&
-- 
2.39.2.459.gd5a6c747


^ permalink raw reply	[flat|nested] 186+ messages in thread

* [PATCH v6 1/3] notes.c: cleanup 'strbuf_grow' call in 'append_edit'
  2023-02-23  7:29         ` [PATCH v6 0/3] " Teng Long
@ 2023-02-23  7:29           ` Teng Long
  2023-02-23  7:29           ` [PATCH v6 2/3] notes.c: cleanup for "designated init" Teng Long
                             ` (3 subsequent siblings)
  4 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-02-23  7:29 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, sunshine, tenglong.tl, gitster

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(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index 80d9dfd2..23cb6f0d 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -215,7 +215,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);
@@ -618,7 +617,6 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		enum object_type type;
 		char *prev_buf = read_object_file(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.39.2.459.gd5a6c747


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v6 2/3] notes.c: cleanup for "designated init"
  2023-02-23  7:29         ` [PATCH v6 0/3] " Teng Long
  2023-02-23  7:29           ` [PATCH v6 1/3] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
@ 2023-02-23  7:29           ` Teng Long
  2023-02-23  7:29           ` [PATCH v6 3/3] notes.c: introduce '--separator=<paragraph-break>' option Teng Long
                             ` (2 subsequent siblings)
  4 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-02-23  7:29 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, sunshine, tenglong.tl, gitster

From: Teng Long <dyroneteng@gmail.com>

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

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/notes.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index 23cb6f0d..553ae2bd 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -401,7 +401,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,
@@ -567,7 +567,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.39.2.459.gd5a6c747


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v6 3/3] notes.c: introduce '--separator=<paragraph-break>' option
  2023-02-23  7:29         ` [PATCH v6 0/3] " Teng Long
  2023-02-23  7:29           ` [PATCH v6 1/3] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
  2023-02-23  7:29           ` [PATCH v6 2/3] notes.c: cleanup for "designated init" Teng Long
@ 2023-02-23  7:29           ` Teng Long
  2023-02-23 18:21             ` Junio C Hamano
  2023-02-25 21:30             ` Junio C Hamano
  2023-03-27 13:13           ` [PATCH v6 0/3] notes.c: introduce "--separator" option Teng Long
  2023-03-28 14:28           ` [PATCH v7 0/4] " Teng Long
  4 siblings, 2 replies; 186+ messages in thread
From: Teng Long @ 2023-02-23  7:29 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, sunshine, tenglong.tl, gitster

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 | cat
     foo

     bar

The default behavour sometimes is not enough, the user may want
to use a custom delimiter between paragraphs, like when
specifiy one or more '-m' or '-F' options. So this commit
introduces a new '--separator' option for 'git-notes-add' and
'git-notes-append', for example when execute:

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

We will check the option value and if the value doesn't contail
a trailing '\n', will add it automatically, so execute

      $ git notes add -m foo -m bar --separator="-"
and
      $ export LF="
      "
      $ git notes add -m foo -m bar --separator="-$LF"

have the same behavour.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-notes.txt |  20 ++++++--
 builtin/notes.c             |  72 ++++++++++++++++++--------
 t/t3301-notes.sh            | 100 ++++++++++++++++++++++++++++++++++++
 3 files changed, 166 insertions(+), 26 deletions(-)

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index efbc10f0..53d63888 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
@@ -86,7 +88,13 @@ 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.
+	Creates a new notes object if needed. 
+	The default delimiter is a blank line, use the `--separator`
+	option to insert other delimiters. More specifically, if the
+	note and the message are not empty, the delimiter will be
+	inserted between them. If you specify multiple `-m` and `-F`
+	options, the delimiter will be inserted between the messages
+	too.
 
 edit::
 	Edit the notes for a given object (defaults to HEAD).
@@ -159,6 +167,10 @@ OPTIONS
 	Allow an empty note object to be stored. The default behavior is
 	to automatically remove empty notes.
 
+--separator <paragraph-break>::
+	The '<paragraph-break>' inserted between paragraphs.
+	A blank line by default.
+
 --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 553ae2bd..e0ada862 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -24,11 +24,12 @@
 #include "notes-utils.h"
 #include "worktree.h"
 
+static char *separator = NULL;
 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>"),
@@ -209,37 +210,55 @@ 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)
+		strbuf_insertstr(message, pos, "\n");
+	else if (separator[strlen(separator) - 1] == '\n')
+		strbuf_insertstr(message, pos, separator);
+	else
+		strbuf_insertf(message, pos, "%s%s", separator, "\n");
+}
+
+static void parse_messages(struct string_list *messages, struct note_data *d)
+{
+	size_t i;
+	for (i = 0; i < messages->nr; i++) {
+		if (d->buf.len)
+			insert_separator(&d->buf, d->buf.len);
+		strbuf_insertstr(&d->buf, d->buf.len,
+				 messages->items[i].string);
+		strbuf_stripspace(&d->buf, 0);
+		d->given = 1;
+	}
+}
+
 static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
 {
-	struct note_data *d = opt->value;
+	struct string_list *msg = opt->value;
 
 	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;
+	string_list_append(msg, arg);
 	return 0;
 }
 
+
 static int parse_file_arg(const struct option *opt, const char *arg, int unset)
 {
-	struct note_data *d = opt->value;
+	struct string_list *msg = opt->value;
+	struct strbuf buf = STRBUF_INIT;
 
 	BUG_ON_OPT_NEG(unset);
 
-	if (d->buf.len)
-		strbuf_addch(&d->buf, '\n');
 	if (!strcmp(arg, "-")) {
-		if (strbuf_read(&d->buf, 0, 1024) < 0)
+		if (strbuf_read(&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(&buf, arg, 1024) < 0)
 		die_errno(_("could not open or read '%s'"), arg);
-	strbuf_stripspace(&d->buf, 0);
 
-	d->given = 1;
+	string_list_append(msg, buf.buf);
+	strbuf_release(&buf);
 	return 0;
 }
 
@@ -402,11 +421,12 @@ 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 string_list messages = STRING_LIST_INIT_DUP;
 	struct option options[] = {
-		OPT_CALLBACK_F('m', "message", &d, N_("message"),
+		OPT_CALLBACK_F('m', "message", &messages, N_("message"),
 			N_("note contents as a string"), PARSE_OPT_NONEG,
 			parse_msg_arg),
-		OPT_CALLBACK_F('F', "file", &d, N_("file"),
+		OPT_CALLBACK_F('F', "file", &messages, N_("file"),
 			N_("note contents in a file"), PARSE_OPT_NONEG,
 			parse_file_arg),
 		OPT_CALLBACK_F('c', "reedit-message", &d, N_("object"),
@@ -418,6 +438,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()
 	};
 
@@ -429,6 +451,7 @@ static int add(int argc, const char **argv, const char *prefix)
 		usage_with_options(git_notes_add_usage, options);
 	}
 
+	parse_messages(&messages, &d);
 	object_ref = argc > 1 ? argv[1] : "HEAD";
 
 	if (get_oid(object_ref, &object))
@@ -568,11 +591,12 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 	char *logmsg;
 	const char * const *usage;
 	struct note_data d = { .buf = STRBUF_INIT };
+	struct string_list messages = STRING_LIST_INIT_DUP;
 	struct option options[] = {
-		OPT_CALLBACK_F('m', "message", &d, N_("message"),
-			N_("note contents as a string"), PARSE_OPT_NONEG,
+		OPT_CALLBACK_F('m', "message", &messages, N_("message"),
+			N_("note contents as a string"), PARSE_OPT_NONEG, 
 			parse_msg_arg),
-		OPT_CALLBACK_F('F', "file", &d, N_("file"),
+		OPT_CALLBACK_F('F', "file", &messages, N_("file"),
 			N_("note contents in a file"), PARSE_OPT_NONEG,
 			parse_file_arg),
 		OPT_CALLBACK_F('c', "reedit-message", &d, N_("object"),
@@ -583,6 +607,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");
@@ -596,6 +622,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		usage_with_options(usage, options);
 	}
 
+	parse_messages(&messages, &d);
+
 	if (d.given && edit)
 		fprintf(stderr, _("The -m/-F/-c/-C options have been deprecated "
 			"for the 'edit' subcommand.\n"
@@ -618,7 +646,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		char *prev_buf = read_object_file(note, &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..c2c09f32 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,26 @@ 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 +542,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
-- 
2.39.2.459.gd5a6c747


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* Re: [PATCH v6 3/3] notes.c: introduce '--separator=<paragraph-break>' option
  2023-02-23  7:29           ` [PATCH v6 3/3] notes.c: introduce '--separator=<paragraph-break>' option Teng Long
@ 2023-02-23 18:21             ` Junio C Hamano
  2023-02-28 14:11               ` Teng Long
  2023-02-25 21:30             ` Junio C Hamano
  1 sibling, 1 reply; 186+ messages in thread
From: Junio C Hamano @ 2023-02-23 18:21 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> So this commit
> introduces a new '--separator' option for 'git-notes-add' and
> 'git-notes-append', for example when execute:

    Introduce a new '--separator' option for 'git notes add' and
    'git notes append'.

> We will check the option value and if the value doesn't contail
> a trailing '\n', will add it automatically,

contail?

	A newline is added to the value given to --separator if it
	does not end with one already.

> so execute
>       $ git notes add -m foo -m bar --separator="-"
> and
>       $ export LF="
>       "
>       $ git notes add -m foo -m bar --separator="-$LF"
>
> have the same behavour.

	Running A and B produces the same result.

> +	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. 

Concise and readable.  Nice.

> @@ -86,7 +88,13 @@ 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.
> +	Creates a new notes object if needed. 
> +	The default delimiter is a blank line, use the `--separator`

Re-read the above three lines, pretending that it is already 6
months and you've forgotten about adding the feature yourself.

Notice something?

A reader with fresh mind would read and think along the description
but

 - OK, append command is used to append to the note to an existing
   object;

 - OK, if the object does not have a note yet, we will create one;

 - The default delimiter?  Delimiter for what?  I am puzzled.

at the third step, gets puzzled.  The command takes the existing
note's contents, adds a delimiter and then appends the new material
given by the user, but because that is not clear after reading the
first two lines, the sudden appearance of "delimiter" would confuse
readers.

> +	option to insert other delimiters. More specifically, if the
> +	note and the message are not empty, the delimiter will be
> +	inserted between them. If you specify multiple `-m` and `-F`

Again, the order of presentation is somewhat backwards and that is
why we need to say "More specifically" here.

> +	options, the delimiter will be inserted between the messages
> +	too.

	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.

or something along that line, perhaps?

> +--separator <paragraph-break>::
> +	The '<paragraph-break>' inserted between paragraphs.
> +	A blank line by default.

Here is where you need to say about promoting incomplete line
separator more than the proposed log message.

	Specify a string used as a custom inter-paragraph separator
	(a newline is added at the end as needed).  Defaults to a
	blank line.

> diff --git a/builtin/notes.c b/builtin/notes.c
> index 553ae2bd..e0ada862 100644
> --- a/builtin/notes.c
> +++ b/builtin/notes.c
> @@ -24,11 +24,12 @@
> ...
> +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);
> +	else
> +		strbuf_insertf(message, pos, "%s%s", separator, "\n");
> +}

It looks like you are very fond of "insert", but aren't we always
appending with the latest control flow?  In other words, is it worth
carrying 'pos' around?

> +static void parse_messages(struct string_list *messages, struct note_data *d)
> +{
> +	size_t i;
> +	for (i = 0; i < messages->nr; i++) {
> +		if (d->buf.len)
> +			insert_separator(&d->buf, d->buf.len);
> +		strbuf_insertstr(&d->buf, d->buf.len,
> +				 messages->items[i].string);
> +		strbuf_stripspace(&d->buf, 0);

This is not a new problem, but if we get three 100-byte messages, we

 - add the first 100-byte message to d->buf and then run
   stripspace() over that 100-byte.

 - add separator and then the second 100-byte message to d->buf, and
   then run stripspace() over that 200-plus-byte.

 - add separator and then the third 100-byte message to d->buf, and
   then run stripspace() over that 300-plus-byte.

Shouldn't we be doing better?

> +		d->given = 1;

Do we understand what d->given flag represents?  My understanding is
that it becomes true only when any of the -m/-F/-c/-C options are given
to tell the command what message to use, so that we can automatically
open the editor to ask for the message when nothing is given.

So, I suspect that

	d->given = !!messages->nr;

at the beginning of the function, or

	d->given = !!d->buf.len;

may be equivalent[*], instead of setting it once every iteration?

	Side note: The latter is slightly more strict, as giving a
	run of empty strings with the default separator would result
	in an empty d->buf and d->given will become false.

> +	}
> +}

This helper is not parsing, but just processing after the whole
thing was parsed.  "parse_messages" -> "concatenate_messages" or
something, perhaps?

Now d->given is set in parse_reuse_arg() and parse_reedit_arg(),
because -c/-C is another source of a paragraph.  Shouldn't the
paragraph taken by these options go in the message list to be
concatenated together with other messages, or are -c/-C incompatible
with -m/-F and we are OK with two separate and distinct behaviour?

>  static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
>  {
> -	struct note_data *d = opt->value;
> +	struct string_list *msg = opt->value;
>  
>  	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;
> +	string_list_append(msg, arg);
>  	return 0;
>  }

OK, this one now does not concatenate; just accumulates to the "msg"
string list.

>  static int parse_file_arg(const struct option *opt, const char *arg, int unset)
>  {
> -	struct note_data *d = opt->value;
> +	struct string_list *msg = opt->value;
> +	struct strbuf buf = STRBUF_INIT;
>  
>  	BUG_ON_OPT_NEG(unset);
>  
> -	if (d->buf.len)
> -		strbuf_addch(&d->buf, '\n');
>  	if (!strcmp(arg, "-")) {
> -		if (strbuf_read(&d->buf, 0, 1024) < 0)
> +		if (strbuf_read(&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(&buf, arg, 1024) < 0)
>  		die_errno(_("could not open or read '%s'"), arg);
> -	strbuf_stripspace(&d->buf, 0);
>  
> -	d->given = 1;
> +	string_list_append(msg, buf.buf);
> +	strbuf_release(&buf);
>  	return 0;
>  }

Ditto.

> @@ -418,6 +438,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()
>  	};
>  
> @@ -429,6 +451,7 @@ static int add(int argc, const char **argv, const char *prefix)
>  		usage_with_options(git_notes_add_usage, options);
>  	}
>  
> +	parse_messages(&messages, &d);

Yup, this one is "concatenate_messages".  We do not read the
existing one, we accumulated everything the user gave us in
messages, and concatenate them using the separator.  The result
is stored in d->buf.

I wonder why separator is not a parameter into the helper function?


In short, I think handling of -m/-F got vastly better from the
previous rounds in this version, but I am not sure about the only
other remaining "strbuf_addch(&d->buf, '\n')" in parse_reuse_arg().
I am inclined to say that that codepath should behave the same way,
but I didn't think it as deeply as you did, so...?

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v6 3/3] notes.c: introduce '--separator=<paragraph-break>' option
  2023-02-23  7:29           ` [PATCH v6 3/3] notes.c: introduce '--separator=<paragraph-break>' option Teng Long
  2023-02-23 18:21             ` Junio C Hamano
@ 2023-02-25 21:30             ` Junio C Hamano
  2023-02-28 14:14               ` Teng Long
  1 sibling, 1 reply; 186+ messages in thread
From: Junio C Hamano @ 2023-02-25 21:30 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> +static void parse_messages(struct string_list *messages, struct note_data *d)
> +{
> +	size_t i;
> +	for (i = 0; i < messages->nr; i++) {
> +		if (d->buf.len)
> +			insert_separator(&d->buf, d->buf.len);
> +		strbuf_insertstr(&d->buf, d->buf.len,
> +				 messages->items[i].string);
> +		strbuf_stripspace(&d->buf, 0);
> +		d->given = 1;
> +	}
> +}

The two callers of this function prepares the string_list, and have
this function consume it by concatenating its contents to d->buf.
After calling this function, neither of them talks about messages,
which means we are leaking the strings kept in the string_list.

I could eject the topic from today's integration run (because the
topic is not ready to be merged to 'next' as-is; "-C/-c" codepaths
need to be adjusted, at least), but as I took a look already, I'll
queue this fix-up on top of the topic for now.  Feel free to squash
it in (or address the leaks in your own way) when sending in an
update.

Thanks.

 builtin/notes.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git c/builtin/notes.c w/builtin/notes.c
index 97c18fc02f..cd7af76e2f 100644
--- c/builtin/notes.c
+++ w/builtin/notes.c
@@ -220,7 +220,8 @@ static void insert_separator(struct strbuf *message, size_t pos)
 		strbuf_insertf(message, pos, "%s%s", separator, "\n");
 }
 
-static void parse_messages(struct string_list *messages, struct note_data *d)
+/* Consume messages and append them into d->buf */
+static void concat_messages(struct string_list *messages, struct note_data *d)
 {
 	size_t i;
 	for (i = 0; i < messages->nr; i++) {
@@ -231,6 +232,7 @@ static void parse_messages(struct string_list *messages, struct note_data *d)
 		strbuf_stripspace(&d->buf, 0);
 		d->given = 1;
 	}
+	string_list_clear(messages, 0);
 }
 
 static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
@@ -451,7 +453,7 @@ static int add(int argc, const char **argv, const char *prefix)
 		usage_with_options(git_notes_add_usage, options);
 	}
 
-	parse_messages(&messages, &d);
+	concat_messages(&messages, &d);
 	object_ref = argc > 1 ? argv[1] : "HEAD";
 
 	if (get_oid(object_ref, &object))
@@ -622,7 +624,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		usage_with_options(usage, options);
 	}
 
-	parse_messages(&messages, &d);
+	concat_messages(&messages, &d);
 
 	if (d.given && edit)
 		fprintf(stderr, _("The -m/-F/-c/-C options have been deprecated "

^ permalink raw reply related	[flat|nested] 186+ messages in thread

* Re: [PATCH v6 3/3] notes.c: introduce '--separator=<paragraph-break>' option
  2023-02-23 18:21             ` Junio C Hamano
@ 2023-02-28 14:11               ` Teng Long
  0 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-02-28 14:11 UTC (permalink / raw)
  To: gitster; +Cc: avarab, dyroneteng, git, sunshine, tenglong.tl

Junio C Hamano <gitster@pobox.com>:

> > So this commit
> > introduces a new '--separator' option for 'git-notes-add' and
> > 'git-notes-append', for example when execute:
>
>     Introduce a new '--separator' option for 'git notes add' and
>     'git notes append'.

Thanks, I will avoid this like 'git-subcommand' expression in the future.

> > We will check the option value and if the value doesn't contail
> > a trailing '\n', will add it automatically,
>
> contail?

"contain", sorry.

> 	A newline is added to the value given to --separator if it
> 	does not end with one already.

Will apply.

> > so execute
> >       $ git notes add -m foo -m bar --separator="-"
> > and
> >       $ export LF="
> >       "
> >       $ git notes add -m foo -m bar --separator="-$LF"
> >
> > have the same behavour.
>
> 	Running A and B produces the same result.

Will s/Running A and B produces the same result/have the same behavour

>  - The default delimiter?  Delimiter for what?  I am puzzled.
>
> at the third step, gets puzzled.  The command takes the existing
> note's contents, adds a delimiter and then appends the new material
> given by the user, but because that is not clear after reading the
> first two lines, the sudden appearance of "delimiter" would confuse
> readers.
>
> > +	option to insert other delimiters. More specifically, if the
> > +	note and the message are not empty, the delimiter will be
> > +	inserted between them. If you specify multiple `-m` and `-F`
>
> Again, the order of presentation is somewhat backwards and that is
> why we need to say "More specifically" here.
>
> > +	options, the delimiter will be inserted between the messages
> > +	too.
>
> 	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.

Will apply.

> Here is where you need to say about promoting incomplete line
> separator more than the proposed log message.
>
> 	Specify a string used as a custom inter-paragraph separator
> 	(a newline is added at the end as needed).  Defaults to a
> 	blank line.

Will apply.

> > +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);
> > +	else
> > +		strbuf_insertf(message, pos, "%s%s", separator, "\n");
> > +}
>
> It looks like you are very fond of "insert", but aren't we always
> appending with the latest control flow?  In other words, is it worth
> carrying 'pos' around?

Actually I can't find a more suitable verb. I think it worth, but I'm
not sure whether there is a better way, that is, when we add separator which
use to contact messages like multiple '-m' and '-F', the 'pos' is at the end.
When we add separator which use to contact the prev_notes and the new notes
, the 'pos' is at the head.

> > +static void parse_messages(struct string_list *messages, struct note_data *d)
> > +{
> > +	size_t i;
> > +	for (i = 0; i < messages->nr; i++) {
> > +		if (d->buf.len)
> > +			insert_separator(&d->buf, d->buf.len);
> > +		strbuf_insertstr(&d->buf, d->buf.len,
> > +				 messages->items[i].string);
> > +		strbuf_stripspace(&d->buf, 0);
>
> This is not a new problem, but if we get three 100-byte messages, we
>
>  - add the first 100-byte message to d->buf and then run
>    stripspace() over that 100-byte.
>
>  - add separator and then the second 100-byte message to d->buf, and
>    then run stripspace() over that 200-plus-byte.
>
>  - add separator and then the third 100-byte message to d->buf, and
>    then run stripspace() over that 300-plus-byte.
>
> Shouldn't we be doing better?

After I read the comments of 'strbuf_stripspace', it does :

1. remove empty lines from beginning and end.
2. turn multiple empty lines between paragraphs into just one empty line.
3. if the input has only empty lines and spaces, no output will be produced.
4. if the last line doesn't have a newline at the end, one is added.
5. skip every commentted line if skip_comments is enabled.

I think above the five functions in 'strbuf_stripspace', we may just care about
the fourth, the others just like the logic when we edit the commit message when
doing 'git commit' or 'git merge', etc. My opinion is a "commit note" and a
"commit message" maybe not be the same, and sometimes the user might want a
blank line or something at the beginning of the notes. But after I read the
tests I think they are just the same. So, let me just fix this by stripspace
the message individually, thanks.

> Do we understand what d->given flag represents?  My understanding is
> that it becomes true only when any of the -m/-F/-c/-C options are given
> to tell the command what message to use, so that we can automatically
> open the editor to ask for the message when nothing is given.
> 
> So, I suspect that
> 
> 	d->given = !!messages->nr;
> 
> at the beginning of the function, or
> 
> 	d->given = !!d->buf.len;
> 
> may be equivalent[*], instead of setting it once every iteration?
> 
> 	Side note: The latter is slightly more strict, as giving a
> 	run of empty strings with the default separator would result
> 	in an empty d->buf and d->given will become false.

I agree, we should choose the latter and move "d->given = !!d->buf.len;"
behind the place where we concat the messages.


> Now d->given is set in parse_reuse_arg() and parse_reedit_arg(),
> because -c/-C is another source of a paragraph.  Shouldn't the
> paragraph taken by these options go in the message list to be
> concatenated together with other messages, or are -c/-C incompatible
> with -m/-F and we are OK with two separate and distinct behaviour?

Yes, my bad. -c/-C should be considered as well and dealing them with the same
messages-contacting logic, I will fix these two place in next patch.

> I wonder why separator is not a parameter into the helper function?

In my opinion, your proposal is probably similar to the current approach, but
your solution may be a bit more readable. I will consider changing it in the
next patch.

> other remaining "strbuf_addch(&d->buf, '\n')" in parse_reuse_arg().
> I am inclined to say that that codepath should behave the same way,
> but I didn't think it as deeply as you did, so...?

You really have a good eye, I didn't noticed them. There are two place remained,
the first one is "parse_reuse_arg", second one is "prepare_note_data" it's made
for separate the note and the commented lines (note template), I think they
should both be replaced/impacted by "--separator".

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v6 3/3] notes.c: introduce '--separator=<paragraph-break>' option
  2023-02-25 21:30             ` Junio C Hamano
@ 2023-02-28 14:14               ` Teng Long
  0 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-02-28 14:14 UTC (permalink / raw)
  To: gitster; +Cc: avarab, dyroneteng, git, sunshine, tenglong.tl

Junio C Hamano <gitster@pobox.com> writes:

> The two callers of this function prepares the string_list, and have
> this function consume it by concatenating its contents to d->buf.
> After calling this function, neither of them talks about messages,
> which means we are leaking the strings kept in the string_list.
>
> I could eject the topic from today's integration run (because the
> topic is not ready to be merged to 'next' as-is; "-C/-c" codepaths
> need to be adjusted, at least), but as I took a look already, I'll
> queue this fix-up on top of the topic for now.  Feel free to squash
> it in (or address the leaks in your own way) when sending in an
> update.
>
> Thanks.
>
>  builtin/notes.c | 8 +++++---
>  1 file changed, 5 insertions(+), 3 deletions(-)
>
> diff --git c/builtin/notes.c w/builtin/notes.c
> index 97c18fc02f..cd7af76e2f 100644
> --- c/builtin/notes.c
> +++ w/builtin/notes.c
> @@ -220,7 +220,8 @@ static void insert_separator(struct strbuf *message, size_t pos)
>  		strbuf_insertf(message, pos, "%s%s", separator, "\n");
>  }
>
> -static void parse_messages(struct string_list *messages, struct note_data *d)
> +/* Consume messages and append them into d->buf */
> +static void concat_messages(struct string_list *messages, struct note_data *d)
>  {
>  	size_t i;
>  	for (i = 0; i < messages->nr; i++) {
> @@ -231,6 +232,7 @@ static void parse_messages(struct string_list *messages, struct note_data *d)
>  		strbuf_stripspace(&d->buf, 0);
>  		d->given = 1;
>  	}
> +	string_list_clear(messages, 0);
>  }
>
>  static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
> @@ -451,7 +453,7 @@ static int add(int argc, const char **argv, const char *prefix)
>  		usage_with_options(git_notes_add_usage, options);
>  	}
>
> -	parse_messages(&messages, &d);
> +	concat_messages(&messages, &d);
>  	object_ref = argc > 1 ? argv[1] : "HEAD";
>
>  	if (get_oid(object_ref, &object))
> @@ -622,7 +624,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
>  		usage_with_options(usage, options);
>  	}
>
> -	parse_messages(&messages, &d);
> +	concat_messages(&messages, &d);
>
>  	if (d.given && edit)
>  		fprintf(stderr, _("The -m/-F/-c/-C options have been deprecated "

Thanks, will apply in next patch.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* [PATCH v6 0/3] notes.c: introduce "--separator" option
  2023-02-23  7:29         ` [PATCH v6 0/3] " Teng Long
                             ` (2 preceding siblings ...)
  2023-02-23  7:29           ` [PATCH v6 3/3] notes.c: introduce '--separator=<paragraph-break>' option Teng Long
@ 2023-03-27 13:13           ` Teng Long
  2023-03-28 14:28           ` [PATCH v7 0/4] " Teng Long
  4 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-03-27 13:13 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

I'm working on the new patchset recently, and I found there may be a
weired test code writting like this in t3301:


test_expect_success 'refusing to edit notes in refs/remotes/' '
	test_must_fail env MSG=1 GIT_NOTES_REF=refs/heads/bogus git notes edit
'

should it be like:

test_expect_success 'refusing to edit notes in refs/remotes/' '
	test_must_fail env MSG=1 GIT_NOTES_REF=refs/remotes/bogus git notes edit
'

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* [PATCH v7 0/4] notes.c: introduce "--separator" option
  2023-02-23  7:29         ` [PATCH v6 0/3] " Teng Long
                             ` (3 preceding siblings ...)
  2023-03-27 13:13           ` [PATCH v6 0/3] notes.c: introduce "--separator" option Teng Long
@ 2023-03-28 14:28           ` Teng Long
  2023-03-28 14:28             ` [PATCH v7 1/4] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
                               ` (4 more replies)
  4 siblings, 5 replies; 186+ messages in thread
From: Teng Long @ 2023-03-28 14:28 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

From: Teng Long <dyroneteng@gmail.com>

Diff since v6:

1. apply Junio's code[1] in 3/4  
2. apply Junio's code[2] and text in 3/4
3. adjust -c/-C codepath in 3/4
4. add new test cases in 3/4
5. in v6, I used a string_list struct to temporarily save the message
then concat them with the separator. But, I found it will be a problem
when we use -C/-F to save the content of binary file(break the t3307)
because binary file maybe contains the NUL in the middle, so I use
strbuf struct instead to make sure the whole content is saved to the
notes.
6. 4/4 is a new commit, which fix the problem about the binary file
too. The binary file maybe contains multiple consecutive empty lines,
we shouldn't make them into just one empty line, which may corrupt
the file I think.

[1] https://public-inbox.org/git/xmqqk005v3ex.fsf@gitster.g/
[2] https://public-inbox.org/git/xmqqcz608cpk.fsf@gitster.g/

Thanks.

Teng Long (4):
  notes.c: cleanup 'strbuf_grow' call in 'append_edit'
  notes.c: cleanup for "designated init"
  notes.c: introduce '--separator=<paragraph-break>' option
  notes.c: don't do stripespace when parse file arg

 Documentation/git-notes.txt |  21 ++++--
 builtin/notes.c             | 106 +++++++++++++++++++++-------
 t/t3301-notes.sh            | 137 +++++++++++++++++++++++++++++++++++-
 3 files changed, 232 insertions(+), 32 deletions(-)

Range-diff against v6:
1:  b029ee0b = 1:  ea9c199e notes.c: cleanup 'strbuf_grow' call in 'append_edit'
2:  043db631 = 2:  9dc123c8 notes.c: cleanup for "designated init"
3:  d5a6c747 ! 3:  d1febf86 notes.c: introduce '--separator=<paragraph-break>' option
    @@ Commit message
     
         The default behavour sometimes is not enough, the user may want
         to use a custom delimiter between paragraphs, like when
    -    specifiy one or more '-m' or '-F' options. So this commit
    -    introduces a new '--separator' option for 'git-notes-add' and
    -    'git-notes-append', for example when execute:
    +    specifiy '-m', '-F', '-C', '-c' options. So this commit
    +    introduce a new '--separator' option for 'git notes add' and
    +    'git notes append', for example when execute:
     
             $ git notes add -m foo -m bar --separator="-"
             $ git notes show HEAD | cat
    @@ Commit message
             -
             bar
     
    -    We will check the option value and if the value doesn't contail
    -    a trailing '\n', will add it automatically, so execute
    +    A newline is added to the value given to --separator if it
    +    does not end with one already. So execute:
     
               $ git notes add -m foo -m bar --separator="-"
         and
    @@ Commit message
               "
               $ git notes add -m foo -m bar --separator="-$LF"
     
    -    have the same behavour.
    +    Running A and B produces the same result.
     
         Signed-off-by: Teng Long <dyroneteng@gmail.com>
     
    @@ Documentation/git-notes.txt: add::
     -	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. 
    ++	option to insert other delimiters.
      
      copy::
      	Copy the notes for the first object onto the second object (defaults to
    -@@ Documentation/git-notes.txt: the command can read the input given to the `post-rewrite` hook.)
    +@@ Documentation/git-notes.txt: 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).
    +-	Append to the notes of an existing object (defaults to HEAD).
     -	Creates a new notes object if needed.
    -+	Creates a new notes object if needed. 
    -+	The default delimiter is a blank line, use the `--separator`
    -+	option to insert other delimiters. More specifically, if the
    -+	note and the message are not empty, the delimiter will be
    -+	inserted between them. If you specify multiple `-m` and `-F`
    -+	options, the delimiter will be inserted between the messages
    -+	too.
    ++	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).
    @@ Documentation/git-notes.txt: OPTIONS
      	to automatically remove empty notes.
      
     +--separator <paragraph-break>::
    -+	The '<paragraph-break>' inserted between paragraphs.
    -+	A blank line by default.
    ++	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
    @@ builtin/notes.c
      	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: struct note_data {
    + 	int use_editor;
    + 	char *edit_path;
    + 	struct strbuf buf;
    ++	struct strbuf **messages;
    ++	size_t msg_nr;
    ++	size_t msg_alloc;
    + };
    + 
    + static void free_note_data(struct note_data *d)
     @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_id *oid)
      	}
      }
    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_
     +		strbuf_insertf(message, pos, "%s%s", separator, "\n");
     +}
     +
    -+static void parse_messages(struct string_list *messages, struct note_data *d)
    ++/* Consume messages and append them into d->buf, then free them */
    ++static void concat_messages(struct note_data *d)
     +{
    ++	struct strbuf msg = STRBUF_INIT;
    ++
     +	size_t i;
    -+	for (i = 0; i < messages->nr; i++) {
    ++	for (i = 0; i < d->msg_nr ; i++) {
     +		if (d->buf.len)
     +			insert_separator(&d->buf, d->buf.len);
    -+		strbuf_insertstr(&d->buf, d->buf.len,
    -+				 messages->items[i].string);
    -+		strbuf_stripspace(&d->buf, 0);
    -+		d->given = 1;
    ++		strbuf_add(&msg, d->messages[i]->buf, d->messages[i]->len);
    ++		strbuf_addbuf(&d->buf, &msg);
    ++		strbuf_reset(&msg);
    ++		strbuf_release(d->messages[i]);
    ++		free(d->messages[i]);
     +	}
    ++	strbuf_release(&msg);
    ++	free(d->messages);
     +}
     +
      static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
      {
    --	struct note_data *d = opt->value;
    -+	struct string_list *msg = opt->value;
    + 	struct note_data *d = opt->value;
    ++	struct strbuf *buf;
      
      	BUG_ON_OPT_NEG(unset);
      
    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_
     -		strbuf_addch(&d->buf, '\n');
     -	strbuf_addstr(&d->buf, arg);
     -	strbuf_stripspace(&d->buf, 0);
    --
    ++	buf = xmalloc(sizeof(*buf));
    ++	strbuf_init(buf, strlen(arg));
    ++	strbuf_addstr(buf, arg);
    ++	strbuf_stripspace(buf, 0);
    + 
     -	d->given = 1;
    -+	string_list_append(msg, arg);
    ++	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
    ++	d->messages[d->msg_nr - 1] = buf;
      	return 0;
      }
      
    -+
      static int parse_file_arg(const struct option *opt, const char *arg, int unset)
      {
    --	struct note_data *d = opt->value;
    -+	struct string_list *msg = opt->value;
    -+	struct strbuf buf = STRBUF_INIT;
    + 	struct note_data *d = opt->value;
    ++	struct strbuf *buf;
      
      	BUG_ON_OPT_NEG(unset);
      
     -	if (d->buf.len)
     -		strbuf_addch(&d->buf, '\n');
    ++	buf = xmalloc(sizeof(*buf));
    ++	strbuf_init(buf, 0);
    ++
      	if (!strcmp(arg, "-")) {
     -		if (strbuf_read(&d->buf, 0, 1024) < 0)
    -+		if (strbuf_read(&buf, 0, 1024) < 0)
    ++		if (strbuf_read(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(&buf, arg, 1024) < 0)
    ++	} else if (strbuf_read_file(buf, arg, 1024) < 0)
      		die_errno(_("could not open or read '%s'"), arg);
     -	strbuf_stripspace(&d->buf, 0);
    ++	strbuf_stripspace(buf, 0);
    + 
    +-	d->given = 1;
    ++	// we will note stripspace the buf here, because the file maybe
    ++	// is a binary and it maybe contains multiple continuous line breaks.
    ++	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
    ++	d->messages[d->msg_nr - 1] = buf;
    + 	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 strbuf *buf;
    + 	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');
    +-
    ++	buf = xmalloc(sizeof(*buf));
    + 	if (get_oid(arg, &object))
    + 		die(_("failed to resolve '%s' as a valid ref."), arg);
    +-	if (!(buf = read_object_file(&object, &type, &len)))
    ++	if (!(buf->buf = read_object_file(&object, &type, &len)))
    + 		die(_("failed to read object '%s'."), arg);
    + 	if (type != OBJ_BLOB) {
    ++		strbuf_release(buf);
    + 		free(buf);
    + 		die(_("cannot read note data from non-blob object '%s'."), arg);
    + 	}
    +-	strbuf_add(&d->buf, buf, len);
    +-	free(buf);
      
     -	d->given = 1;
    -+	string_list_append(msg, buf.buf);
    -+	strbuf_release(&buf);
    ++	// The reuse object maybe is a binary content which could
    ++	// contains '\0' in the middle, so let's set the correct
    ++	// lenth of strbuf to concat all of content.
    ++	buf->len = len;
    ++	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
    ++	d->messages[d->msg_nr - 1] = buf;
      	return 0;
      }
      
    + static int parse_reedit_arg(const struct option *opt, const char *arg, int unset)
    + {
    + 	struct note_data *d = opt->value;
    ++
    + 	BUG_ON_OPT_NEG(unset);
    + 	d->use_editor = 1;
    + 	return parse_reuse_arg(opt, arg, unset);
     @@ builtin/notes.c: 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 string_list messages = STRING_LIST_INIT_DUP;
    +-	struct note_data d = { .buf = STRBUF_INIT };
    ++	struct note_data d = { .buf = STRBUF_INIT, .messages = NULL };
    ++
      	struct option options[] = {
    --		OPT_CALLBACK_F('m', "message", &d, N_("message"),
    -+		OPT_CALLBACK_F('m', "message", &messages, N_("message"),
    + 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
      			N_("note contents as a string"), PARSE_OPT_NONEG,
    - 			parse_msg_arg),
    --		OPT_CALLBACK_F('F', "file", &d, N_("file"),
    -+		OPT_CALLBACK_F('F', "file", &messages, N_("file"),
    - 			N_("note contents in a file"), PARSE_OPT_NONEG,
    - 			parse_file_arg),
    - 		OPT_CALLBACK_F('c', "reedit-message", &d, N_("object"),
     @@ 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")),
    @@ builtin/notes.c: static int add(int argc, const char **argv, const char *prefix)
      		usage_with_options(git_notes_add_usage, options);
      	}
      
    -+	parse_messages(&messages, &d);
    ++	if (d.msg_nr)
    ++		concat_messages(&d);
    ++	d.given = !!d.buf.len;
    ++
      	object_ref = argc > 1 ? argv[1] : "HEAD";
      
      	if (get_oid(object_ref, &object))
     @@ builtin/notes.c: 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 string_list messages = STRING_LIST_INIT_DUP;
    +-	struct note_data d = { .buf = STRBUF_INIT };
    ++	struct note_data d = { .buf = STRBUF_INIT, .messages = NULL };
    ++
      	struct option options[] = {
    --		OPT_CALLBACK_F('m', "message", &d, N_("message"),
    --			N_("note contents as a string"), PARSE_OPT_NONEG,
    -+		OPT_CALLBACK_F('m', "message", &messages, N_("message"),
    -+			N_("note contents as a string"), PARSE_OPT_NONEG, 
    - 			parse_msg_arg),
    --		OPT_CALLBACK_F('F', "file", &d, N_("file"),
    -+		OPT_CALLBACK_F('F', "file", &messages, N_("file"),
    - 			N_("note contents in a file"), PARSE_OPT_NONEG,
    - 			parse_file_arg),
    - 		OPT_CALLBACK_F('c', "reedit-message", &d, N_("object"),
    + 		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)
      			parse_reuse_arg),
      		OPT_BOOL(0, "allow-empty", &allow_empty,
    @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char
      		usage_with_options(usage, options);
      	}
      
    -+	parse_messages(&messages, &d);
    ++	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 "
    @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char
      		free(prev_buf);
     
      ## t/t3301-notes.sh ##
    +@@
    + 
    + test_description='Test commit notes'
    + 
    ++TEST_PASSES_SANITIZE_LEAK=true
    + . ./test-lib.sh
    + 
    + write_script fake_editor <<\EOF
     @@ t/t3301-notes.sh: test_expect_success 'do not create empty note with -m ""' '
      '
      
    @@ t/t3301-notes.sh: test_expect_success 'create note with combination of -m and -F
     +	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"' '
    @@ t/t3301-notes.sh: test_expect_success 'listing non-existing notes fails' '
      test_expect_success 'append to existing note with "git notes append"' '
      	cat >expect <<-EOF &&
      		Initial set of notes
    +@@ t/t3301-notes.sh: 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) &&
-:  -------- > 4:  b9d12f0c notes.c: don't do stripespace when parse file arg
-- 
2.40.0.rc2.4.gb9d12f0c


^ permalink raw reply	[flat|nested] 186+ messages in thread

* [PATCH v7 1/4] notes.c: cleanup 'strbuf_grow' call in 'append_edit'
  2023-03-28 14:28           ` [PATCH v7 0/4] " Teng Long
@ 2023-03-28 14:28             ` Teng Long
  2023-03-28 14:28             ` [PATCH v7 2/4] notes.c: cleanup for "designated init" Teng Long
                               ` (3 subsequent siblings)
  4 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-03-28 14:28 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

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(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index 80d9dfd2..23cb6f0d 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -215,7 +215,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);
@@ -618,7 +617,6 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		enum object_type type;
 		char *prev_buf = read_object_file(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.rc2.4.gb9d12f0c


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v7 2/4] notes.c: cleanup for "designated init"
  2023-03-28 14:28           ` [PATCH v7 0/4] " Teng Long
  2023-03-28 14:28             ` [PATCH v7 1/4] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
@ 2023-03-28 14:28             ` Teng Long
  2023-03-29 22:17               ` Junio C Hamano
  2023-03-28 14:28             ` [PATCH v7 3/4] notes.c: introduce '--separator=<paragraph-break>' option Teng Long
                               ` (2 subsequent siblings)
  4 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-03-28 14:28 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

From: Teng Long <dyroneteng@gmail.com>

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

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/notes.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index 23cb6f0d..553ae2bd 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -401,7 +401,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,
@@ -567,7 +567,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.rc2.4.gb9d12f0c


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v7 3/4] notes.c: introduce '--separator=<paragraph-break>' option
  2023-03-28 14:28           ` [PATCH v7 0/4] " Teng Long
  2023-03-28 14:28             ` [PATCH v7 1/4] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
  2023-03-28 14:28             ` [PATCH v7 2/4] notes.c: cleanup for "designated init" Teng Long
@ 2023-03-28 14:28             ` Teng Long
  2023-03-28 15:37               ` Junio C Hamano
  2023-03-28 14:28             ` [PATCH v7 4/4] notes.c: don't do stripespace when parse file arg Teng Long
  2023-04-25 13:34             ` [PATCH 0/6] notes.c: introduce "--separator" option Teng Long
  4 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-03-28 14:28 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

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 | cat
     foo

     bar

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

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

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

      $ git notes add -m foo -m bar --separator="-"
and
      $ export LF="
      "
      $ git notes add -m foo -m bar --separator="-$LF"

Running A and B produces the same result.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-notes.txt |  21 ++++--
 builtin/notes.c             | 105 ++++++++++++++++++++++-------
 t/t3301-notes.sh            | 127 ++++++++++++++++++++++++++++++++++++
 3 files changed, 224 insertions(+), 29 deletions(-)

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 553ae2bd..94b69607 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -24,11 +24,12 @@
 #include "notes-utils.h"
 #include "worktree.h"
 
+static char *separator = NULL;
 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>"),
@@ -101,6 +102,9 @@ struct note_data {
 	int use_editor;
 	char *edit_path;
 	struct strbuf buf;
+	struct strbuf **messages;
+	size_t msg_nr;
+	size_t msg_alloc;
 };
 
 static void free_note_data(struct note_data *d)
@@ -209,71 +213,110 @@ 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)
+		strbuf_insertstr(message, pos, "\n");
+	else if (separator[strlen(separator) - 1] == '\n')
+		strbuf_insertstr(message, pos, separator);
+	else
+		strbuf_insertf(message, pos, "%s%s", separator, "\n");
+}
+
+/* Consume messages and append them into d->buf, then free them */
+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, d->messages[i]->len);
+		strbuf_addbuf(&d->buf, &msg);
+		strbuf_reset(&msg);
+		strbuf_release(d->messages[i]);
+		free(d->messages[i]);
+	}
+	strbuf_release(&msg);
+	free(d->messages);
+}
+
 static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
 {
 	struct note_data *d = opt->value;
+	struct strbuf *buf;
 
 	BUG_ON_OPT_NEG(unset);
 
-	if (d->buf.len)
-		strbuf_addch(&d->buf, '\n');
-	strbuf_addstr(&d->buf, arg);
-	strbuf_stripspace(&d->buf, 0);
+	buf = xmalloc(sizeof(*buf));
+	strbuf_init(buf, strlen(arg));
+	strbuf_addstr(buf, arg);
+	strbuf_stripspace(buf, 0);
 
-	d->given = 1;
+	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
+	d->messages[d->msg_nr - 1] = buf;
 	return 0;
 }
 
 static int parse_file_arg(const struct option *opt, const char *arg, int unset)
 {
 	struct note_data *d = opt->value;
+	struct strbuf *buf;
 
 	BUG_ON_OPT_NEG(unset);
 
-	if (d->buf.len)
-		strbuf_addch(&d->buf, '\n');
+	buf = xmalloc(sizeof(*buf));
+	strbuf_init(buf, 0);
+
 	if (!strcmp(arg, "-")) {
-		if (strbuf_read(&d->buf, 0, 1024) < 0)
+		if (strbuf_read(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(buf, arg, 1024) < 0)
 		die_errno(_("could not open or read '%s'"), arg);
-	strbuf_stripspace(&d->buf, 0);
+	strbuf_stripspace(buf, 0);
 
-	d->given = 1;
+	// we will note stripspace the buf here, because the file maybe
+	// is a binary and it maybe contains multiple continuous line breaks.
+	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
+	d->messages[d->msg_nr - 1] = buf;
 	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 strbuf *buf;
 	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');
-
+	buf = xmalloc(sizeof(*buf));
 	if (get_oid(arg, &object))
 		die(_("failed to resolve '%s' as a valid ref."), arg);
-	if (!(buf = read_object_file(&object, &type, &len)))
+	if (!(buf->buf = read_object_file(&object, &type, &len)))
 		die(_("failed to read object '%s'."), arg);
 	if (type != OBJ_BLOB) {
+		strbuf_release(buf);
 		free(buf);
 		die(_("cannot read note data from non-blob object '%s'."), arg);
 	}
-	strbuf_add(&d->buf, buf, len);
-	free(buf);
 
-	d->given = 1;
+	// The reuse object maybe is a binary content which could
+	// contains '\0' in the middle, so let's set the correct
+	// lenth of strbuf to concat all of content.
+	buf->len = len;
+	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
+	d->messages[d->msg_nr - 1] = buf;
 	return 0;
 }
 
 static int parse_reedit_arg(const struct option *opt, const char *arg, int unset)
 {
 	struct note_data *d = opt->value;
+
 	BUG_ON_OPT_NEG(unset);
 	d->use_editor = 1;
 	return parse_reuse_arg(opt, arg, unset);
@@ -401,7 +444,8 @@ 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, .messages = NULL };
+
 	struct option options[] = {
 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
 			N_("note contents as a string"), PARSE_OPT_NONEG,
@@ -418,6 +462,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()
 	};
 
@@ -429,6 +475,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 (get_oid(object_ref, &object))
@@ -567,7 +617,8 @@ 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, .messages = NULL };
+
 	struct option options[] = {
 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
 			N_("note contents as a string"), PARSE_OPT_NONEG,
@@ -583,6 +634,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");
@@ -596,6 +649,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"
@@ -618,7 +675,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
 		char *prev_buf = read_object_file(note, &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..716192b5 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -5,6 +5,7 @@
 
 test_description='Test commit notes'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 write_script fake_editor <<\EOF
@@ -362,6 +363,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 +382,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 +542,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 +918,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.rc2.4.gb9d12f0c


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v7 4/4] notes.c: don't do stripespace when parse file arg
  2023-03-28 14:28           ` [PATCH v7 0/4] " Teng Long
                               ` (2 preceding siblings ...)
  2023-03-28 14:28             ` [PATCH v7 3/4] notes.c: introduce '--separator=<paragraph-break>' option Teng Long
@ 2023-03-28 14:28             ` Teng Long
  2023-03-28 15:54               ` Junio C Hamano
  2023-04-25 13:34             ` [PATCH 0/6] notes.c: introduce "--separator" option Teng Long
  4 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-03-28 14:28 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

From: Teng Long <dyroneteng@gmail.com>

The file maybe is a binary and it could contains multiple line
breaks, if we do the stripespace on it's content, the notes will
be different to the original content.

The binary file as the notes maybe a rare scenario, but there're
some related tests about it in t3307, so let's fix the problem
and add a test in t3301.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/notes.c  |  1 -
 t/t3301-notes.sh | 10 +++++++++-
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index 94b69607..ade8fb73 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -274,7 +274,6 @@ static int parse_file_arg(const struct option *opt, const char *arg, int unset)
 			die_errno(_("cannot read '%s'"), arg);
 	} else if (strbuf_read_file(buf, arg, 1024) < 0)
 		die_errno(_("could not open or read '%s'"), arg);
-	strbuf_stripspace(buf, 0);
 
 	// we will note stripspace the buf here, because the file maybe
 	// is a binary and it maybe contains multiple continuous line breaks.
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 716192b5..2af318f5 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -192,8 +192,16 @@ test_expect_success 'show multi-line notes' '
 	test_cmp expect-multiline actual
 '
 
-test_expect_success 'show -F notes' '
+test_expect_success 'add -F notes with binary' '
+	test_when_finished "git notes remove" &&
 	test_commit 4th &&
+	cp "$TEST_DIRECTORY"/test-binary-1.png . &&
+	git notes add -F test-binary-1.png &&
+	git notes show >actual &&
+	test_cmp test-binary-1.png actual
+'
+
+test_expect_success 'show -F notes' '
 	echo "xyzzy" >note5 &&
 	git notes add -F note5 &&
 	commit=$(git rev-parse HEAD) &&
-- 
2.40.0.rc2.4.gb9d12f0c


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* Re: [PATCH v7 3/4] notes.c: introduce '--separator=<paragraph-break>' option
  2023-03-28 14:28             ` [PATCH v7 3/4] notes.c: introduce '--separator=<paragraph-break>' option Teng Long
@ 2023-03-28 15:37               ` Junio C Hamano
  2023-03-29 14:15                 ` Teng Long
  0 siblings, 1 reply; 186+ messages in thread
From: Junio C Hamano @ 2023-03-28 15:37 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> 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 | cat
>      foo
>
>      bar
>
> The default behavour sometimes is not enough, the user may want
> to use a custom delimiter between paragraphs, like when
> specifiy '-m', '-F', '-C', '-c' options. So this commit

"like when specifying", you mean?

> introduce a new '--separator' option for 'git notes add' and
> 'git notes append', for example when execute:

"when executing"?

>     $ git notes add -m foo -m bar --separator="-"
>     $ git notes show HEAD | cat
>     foo
>     -
>     bar
>
> A newline is added to the value given to --separator if it

"a newline is ...", because this is not starting a new sentence, but
continuing from the "for example, when ..." above.

> does not end with one already. So execute:
>
>       $ git notes add -m foo -m bar --separator="-"
> and
>       $ export LF="
>       "
>       $ git notes add -m foo -m bar --separator="-$LF"
>
> Running A and B produces the same result.

Here, it is totally unclear what A and B refers to.  "both
... and ... produce the same result" or something?

> @@ -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.

Excellent.

> @@ -101,6 +102,9 @@ struct note_data {
>  	int use_editor;
>  	char *edit_path;
>  	struct strbuf buf;
> +	struct strbuf **messages;
> +	size_t msg_nr;
> +	size_t msg_alloc;
>  };

Hmph, OK.  I'd think twice before allowing an array of strbufs,
though.  strbuf is an excellent data structure to allow editing
string safely, and an array of strbufs may be very useful when these
strings need to be edited simultaneously, but such a use case is
very rare and I suspect this would not be it---rather, don't we take
one string at a time while processing each option, perhaps running
stripspace, and then after that aren't we done with the string until
when we finally have these N strings to loop over and concatenate
into a single string?  It would be sensible to use a strbuf to
produce the concatenation, but the strings to be concatenated do not
have to come from strbufs.  So my intuition, before reading the code
but after seeing the data structure, says that "const char **messages"
might be more appropriate here.  It sends a strong message about the
code structure and "phases" of processing, i.e. "we read each piece
and store it in the array once we are done preprocessing; then in
the next phase we concatenate them".

> @@ -209,71 +213,110 @@ 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)
> +		strbuf_insertstr(message, pos, "\n");
> +	else if (separator[strlen(separator) - 1] == '\n')
> +		strbuf_insertstr(message, pos, separator);
> +	else
> +		strbuf_insertf(message, pos, "%s%s", separator, "\n");
> +}

Do we need "insert" separator?  The caller in "concat" certainly
shouldn't---all it needs is "append".

> +/* Consume messages and append them into d->buf, then free them */
> +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, d->messages[i]->len);
> +		strbuf_addbuf(&d->buf, &msg);
> +		strbuf_reset(&msg);
> +		strbuf_release(d->messages[i]);
> +		free(d->messages[i]);
> +	}
> +	strbuf_release(&msg);
> +	free(d->messages);
> +}

As I suspected earlier, with the way d->messages[i] are used, they
have no reason to be strbufs---they can and should be a simple
string "const char *".

>  static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
>  {
>  	struct note_data *d = opt->value;
> +	struct strbuf *buf;
>  
>  	BUG_ON_OPT_NEG(unset);
>  
> -	if (d->buf.len)
> -		strbuf_addch(&d->buf, '\n');
> -	strbuf_addstr(&d->buf, arg);
> -	strbuf_stripspace(&d->buf, 0);
> +	buf = xmalloc(sizeof(*buf));
> +	strbuf_init(buf, strlen(arg));
> +	strbuf_addstr(buf, arg);
> +	strbuf_stripspace(buf, 0);
>  
> -	d->given = 1;
> +	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
> +	d->messages[d->msg_nr - 1] = buf;
>  	return 0;
>  }

And this one can use an on-stack strbuf (initialized with STRBUF_INIT),
and strbuf_detach() the result into d->messages[].

>  static int parse_file_arg(const struct option *opt, const char *arg, int unset)
>  {
>  	struct note_data *d = opt->value;
> +	struct strbuf *buf;

Likewise.

>  
>  	BUG_ON_OPT_NEG(unset);
>  
> -	if (d->buf.len)
> -		strbuf_addch(&d->buf, '\n');
> +	buf = xmalloc(sizeof(*buf));
> +	strbuf_init(buf, 0);
> +
>  	if (!strcmp(arg, "-")) {
> -		if (strbuf_read(&d->buf, 0, 1024) < 0)
> +		if (strbuf_read(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(buf, arg, 1024) < 0)
>  		die_errno(_("could not open or read '%s'"), arg);
> -	strbuf_stripspace(&d->buf, 0);
> +	strbuf_stripspace(buf, 0);
>  
> -	d->given = 1;
> +	// we will note stripspace the buf here, because the file maybe
> +	// is a binary and it maybe contains multiple continuous line breaks.

No // comments in our codebase.

> +	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
> +	d->messages[d->msg_nr - 1] = buf;
>  	return 0;
>  }

>  static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)
>  {
> ...
>  }

Ditto.

> @@ -567,7 +617,8 @@ 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, .messages = NULL };

Why explicitly initialize .messages to NULL when we are leaving
other members to zero initialized implicitly by using designated
initializers?

> @@ -618,7 +675,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
>  		char *prev_buf = read_object_file(note, &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);

Having to insert two things in front of d.buf feels somewhat
wasteful.  I wonder if we can easily reorder the logic to first read
the previous one, and then append new stuff to it, perhaps?

It wouldn't be a huge deal.  If this weren't the only reason why we
need to invent insertstr that takes "where", I wouldn't even be
mentioning it.

> diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
> index 3288aaec..716192b5 100755
> --- a/t/t3301-notes.sh
> +++ b/t/t3301-notes.sh
> @@ -5,6 +5,7 @@
>  
>  test_description='Test commit notes'
>  
> +TEST_PASSES_SANITIZE_LEAK=true
>  . ./test-lib.sh

This is a strange commit to add this.  If the test and the code
involved in the test were leak-sanitizer clean before this commit,
then you should have been able to insert this without any other
change, and we should do it that way in a separate commit.  If we
are fixing an existing leak that the sanitizer complains, then that
is a different matter.  If that is the case, it makes perfect sense
to have this here, but the proposed commit log message should
explain that it is what is happening.

Other than that, looks very nicely done.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v7 4/4] notes.c: don't do stripespace when parse file arg
  2023-03-28 14:28             ` [PATCH v7 4/4] notes.c: don't do stripespace when parse file arg Teng Long
@ 2023-03-28 15:54               ` Junio C Hamano
  2023-03-29 12:06                 ` Teng Long
  0 siblings, 1 reply; 186+ messages in thread
From: Junio C Hamano @ 2023-03-28 15:54 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> From: Teng Long <dyroneteng@gmail.com>
>
> The file maybe is a binary and it could contains multiple line

"could contain"?

> breaks, if we do the stripespace on it's content, the notes will

"stripspace"?

> be different to the original content.

If a file is "binary" then by definition there is no "line" and no
"line break" in it, so while I can see what you are trying to say,
this needs a bit rephrasing to make it easier to follow for future
readers.  Perhaps something like...

    The file given to the "-F" option could be binary, in which case
    we want to store it verbatim without corrupting its contents.

But I do not necessarily agree with the logic here, regardless of
how it is phrased.  Existing users are used to seeing the contents
fed from "-F <file>" get cleaned up, and to them, this unconditional
removal of stripspace() will be a regression.  Besides, don't they
expect that 

	-m="$(cat file)"

be equivalent to

	-F file

for their text file?

A --binary-file=<file> option that is incompatible with -m/-F and
"append" action may make sense if we really want to have a feature
to help those who want to attach binary contents as a note to
objects.


^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v7 4/4] notes.c: don't do stripespace when parse file arg
  2023-03-28 15:54               ` Junio C Hamano
@ 2023-03-29 12:06                 ` Teng Long
  2023-03-29 16:21                   ` Junio C Hamano
  0 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-03-29 12:06 UTC (permalink / raw)
  To: gitster; +Cc: avarab, dyroneteng, git, sunshine, tenglong.tl

Junio C Hamano <gitster@pobox.com> writes:

> > The file maybe is a binary and it could contains multiple line
>
> "could contain"?

Will fix.

> > breaks, if we do the stripespace on it's content, the notes will
>
> "stripspace"?

Will fix, also in the commit subject.

> If a file is "binary" then by definition there is no "line" and no
> "line break" in it, so while I can see what you are trying to say,
> this needs a bit rephrasing to make it easier to follow for future
> readers.  Perhaps something like...
>
>     The file given to the "-F" option could be binary, in which case
>     we want to store it verbatim without corrupting its contents.

Thanks, the phrasing is obviously better.

> But I do not necessarily agree with the logic here, regardless of
> how it is phrased.  Existing users are used to seeing the contents
> fed from "-F <file>" get cleaned up, and to them, this unconditional
> removal of stripspace() will be a regression.  Besides, don't they
> expect that
>
> 	-m="$(cat file)"
>
> be equivalent to
>
> 	-F file

> for their text file?

Yes, for text file. I agree and I actually notice it will be
a regression, because I didn't figure out when user might
store a binary file's content in notes, so I'd like to discuss
about this issue.

> A --binary-file=<file> option that is incompatible with -m/-F and
> "append" action may make sense if we really want to have a feature
> to help those who want to attach binary contents as a note to
> objects.

Actually, -m/-F/-c/-C in 'git notes add' is quite like the
options in 'git commit', rather than "--binary-file=", I think
maybe '--[no-]stripspace' is a bit better.  Defaults to
'--stripspace' which keeps old logic (stripspace the string fed by
-m/-F). If specifying '--no-stripspace', we do not stripespace.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v7 3/4] notes.c: introduce '--separator=<paragraph-break>' option
  2023-03-28 15:37               ` Junio C Hamano
@ 2023-03-29 14:15                 ` Teng Long
  2023-03-29 21:48                   ` Junio C Hamano
  0 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-03-29 14:15 UTC (permalink / raw)
  To: gitster; +Cc: avarab, dyroneteng, git, sunshine, tenglong.tl

Junio C Hamano <gitster@pobox.com> writes:

> > The default behavour sometimes is not enough, the user may want
> > to use a custom delimiter between paragraphs, like when
> > specifiy '-m', '-F', '-C', '-c' options. So this commit
>
> "like when specifying", you mean?
>
> "when executing"?
>
> >     $ git notes add -m foo -m bar --separator="-"
> >     $ git notes show HEAD | cat
> >     foo
> >     -
> >     bar
> >
> > A newline is added to the value given to --separator if it
>
> "a newline is ...", because this is not starting a new sentence, but
> continuing from the "for example, when ..." above.
>
> "a newline is ...", because this is not starting a new sentence, but
> continuing from the "for example, when ..." above.
>
> > does not end with one already. So execute:
> >
> >       $ git notes add -m foo -m bar --separator="-"
> > and
> >       $ export LF="
> >       "
> >       $ git notes add -m foo -m bar --separator="-$LF"
> >
> > Running A and B produces the same result.
>
> Here, it is totally unclear what A and B refers to.  "both
> ... and ... produce the same result" or something?

will fix, thanks.

> > @@ -101,6 +102,9 @@ struct note_data {
> >  	int use_editor;
> >  	char *edit_path;
> >  	struct strbuf buf;
> > +	struct strbuf **messages;
> > +	size_t msg_nr;
> > +	size_t msg_alloc;
> >  };
>
> Hmph, OK.  I'd think twice before allowing an array of strbufs,
> though.  strbuf is an excellent data structure to allow editing
> string safely, and an array of strbufs may be very useful when these
> strings need to be edited simultaneously, but such a use case is
> very rare and I suspect this would not be it---rather, don't we take
> one string at a time while processing each option, perhaps running
> stripspace, and then after that aren't we done with the string until
> when we finally have these N strings to loop over and concatenate
> into a single string?  It would be sensible to use a strbuf to
> produce the concatenation, but the strings to be concatenated do not
> have to come from strbufs.  So my intuition, before reading the code
> but after seeing the data structure, says that "const char **messages"
> might be more appropriate here.  It sends a strong message about the
> code structure and "phases" of processing, i.e. "we read each piece
> and store it in the array once we are done preprocessing; then in
> the next phase we concatenate them".

I used string in previous patch, but I found there will be a problem when
specify a binary file to '-F', that is, a binary file maybe contains NUL in
the middle, then if we feed the content to a string, it will be cut off when
appending to the 'string_list'.  So, it seems that strbuf could solve the
issue (will be reproduced as a failure in t3307).

> > @@ -209,71 +213,110 @@ 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)
> > +		strbuf_insertstr(message, pos, "\n");
> > +	else if (separator[strlen(separator) - 1] == '\n')
> > +		strbuf_insertstr(message, pos, separator);
> > +	else
> > +		strbuf_insertf(message, pos, "%s%s", separator, "\n");
> > +}
>
> Do we need "insert" separator?  The caller in "concat" certainly
> shouldn't---all it needs is "append".

OK, I'm willing to try to do that, "insert at a position" actually because
there is a related codepath need to add the separator in the very beginning.

> > +/* Consume messages and append them into d->buf, then free them */
> > +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, d->messages[i]->len);
> > +		strbuf_addbuf(&d->buf, &msg);
> > +		strbuf_reset(&msg);
> > +		strbuf_release(d->messages[i]);
> > +		free(d->messages[i]);
> > +	}
> > +	strbuf_release(&msg);
> > +	free(d->messages);
> > +}
>
> As I suspected earlier, with the way d->messages[i] are used, they
> have no reason to be strbufs---they can and should be a simple
> string "const char *".

As I said about why I choose strbuf in the above, but I expect your
further advice on that.

> >  static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
> >  {
> >  	struct note_data *d = opt->value;
> > +	struct strbuf *buf;
> >
> >  	BUG_ON_OPT_NEG(unset);
> >
> > -	if (d->buf.len)
> > -		strbuf_addch(&d->buf, '\n');
> > -	strbuf_addstr(&d->buf, arg);
> > -	strbuf_stripspace(&d->buf, 0);
> > +	buf = xmalloc(sizeof(*buf));
> > +	strbuf_init(buf, strlen(arg));
> > +	strbuf_addstr(buf, arg);
> > +	strbuf_stripspace(buf, 0);
> >
> > -	d->given = 1;
> > +	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
> > +	d->messages[d->msg_nr - 1] = buf;
> >  	return 0;
> >  }
>
> And this one can use an on-stack strbuf (initialized with STRBUF_INIT),
> and strbuf_detach() the result into d->messages[].

> >  static int parse_file_arg(const struct option *opt, const char *arg, int unset)
> >  {
> >  	struct note_data *d = opt->value;
> > +	struct strbuf *buf;

> Likewise.

For the same reason as above I think.

> >
> >  	BUG_ON_OPT_NEG(unset);
> >
> > -	if (d->buf.len)
> > -		strbuf_addch(&d->buf, '\n');
> > +	buf = xmalloc(sizeof(*buf));
> > +	strbuf_init(buf, 0);
> > +
> >  	if (!strcmp(arg, "-")) {
> > -		if (strbuf_read(&d->buf, 0, 1024) < 0)
> > +		if (strbuf_read(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(buf, arg, 1024) < 0)
> >  		die_errno(_("could not open or read '%s'"), arg);
> > -	strbuf_stripspace(&d->buf, 0);
> > +	strbuf_stripspace(buf, 0);
> >
> > -	d->given = 1;
> > +	// we will note stripspace the buf here, because the file maybe
> > +	// is a binary and it maybe contains multiple continuous line breaks.
>
> No // comments in our codebase.

Will fix.

> > @@ -567,7 +617,8 @@ 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, .messages = NULL };
>
> Why explicitly initialize .messages to NULL when we are leaving
> other members to zero initialized implicitly by using designated
> initializers?

My bad, will fix.

> > @@ -618,7 +675,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
> >  		char *prev_buf = read_object_file(note, &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);
>
> Having to insert two things in front of d.buf feels somewhat
> wasteful. ...

"wasteful" for?

> ... I wonder if we can easily reorder the logic to first read
> the previous one, and then append new stuff to it, perhaps?

I think it can be done in this way, but in another single commit, perhaps?

> It wouldn't be a huge deal.  If this weren't the only reason why we
> need to invent insertstr that takes "where", I wouldn't even be
> mentioning it.

This makes me think that there is a need for a method 'insert_separator'? For
example, to initialize 'separator' and then use 'strbuf_insertstr' directly?
I'm not sure, but it feels like it could work.


> > diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
> > index 3288aaec..716192b5 100755
> > --- a/t/t3301-notes.sh
> > +++ b/t/t3301-notes.sh
> > @@ -5,6 +5,7 @@
> >
> >  test_description='Test commit notes'
> >
> > +TEST_PASSES_SANITIZE_LEAK=true
> >  . ./test-lib.sh
>
> This is a strange commit to add this.  If the test and the code
> involved in the test were leak-sanitizer clean before this commit,
> then you should have been able to insert this without any other
> change, and we should do it that way in a separate commit.  If we
> are fixing an existing leak that the sanitizer complains, then that
> is a different matter.  If that is the case, it makes perfect sense
> to have this here, but the proposed commit log message should
> explain that it is what is happening.

Will fix.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v7 4/4] notes.c: don't do stripespace when parse file arg
  2023-03-29 12:06                 ` Teng Long
@ 2023-03-29 16:21                   ` Junio C Hamano
  0 siblings, 0 replies; 186+ messages in thread
From: Junio C Hamano @ 2023-03-29 16:21 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> Actually, -m/-F/-c/-C in 'git notes add' is quite like the
> options in 'git commit', rather than "--binary-file=", I think
> maybe '--[no-]stripspace' is a bit better.  Defaults to
> '--stripspace' which keeps old logic (stripspace the string fed by
> -m/-F). If specifying '--no-stripspace', we do not stripespace.

It is a fairly easy way to both implement and explain.  The approach
is certainly attractive from the "expectation management" point of
view.

With "git notes add --no-stripspace -F file0 -F file1 ...", we may
still be making the resulting note unusable as a binary file due to
splicing the inter-paragraph separator, but the option name does not
hint that we are giving this option to users to help them use binary
contents at all, to prevent them from complaining.  If it is truly
useful is a separate matter, but I somehow suspect that there will
be a lot more users who want "no stripspace on text contents" than
those who want "binary contents in note", so it probably is a good
way to go after all.  I like that.

Before proceeding further on this front, we'd probably need a bit
more tests that are specifically designed to catch behaviour change
before and after this series with respect to stripspace behaviour.
The current code without these patches calls stripspace() on the
whole thing every time it receives and appends an additional piece
of contents.  The parse_msg_arg() function, for example, handles "-m
<possibly multiline message>" by first appending a blank line (if we
already have some contents) and then appending the new message, and
runs stripspace on the whole thing.  "-F <file>" is handled by
parse_file_arg() the same way, i.e. append the file data and then
stripspace the result.  This can of course be optimized somewhat
(e.g. an early part of the buffer may have already been cleansed by
stripspace before appending new data, so as long as we do not
introduce double-blanks and other things that are to be corrected at
the boundary of appending new data, we shouldn't have to run
stripspace on the earlier part in order to behave "as if we did a
stripspace the whole thing over and over again"), but we should make
sure we do not change the behaviour to cause regression.  After
doing something silly like this:

    cat >file1 <<-\EOF &&

    With a leading blank line, this is the second line of the file.
    And with a trailing blank line, this is the second from the last.

    EOF
    git notes add -m '
    Message from the command line.


    Two blank lines above.' -F file1    

we should see the leading blank line before "Message from the
command line." removed, two blank lines from the "-m" option
squeezed into one, only one blank line to appear before the contents
taken from file1, and the trailing blank line removed.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v7 3/4] notes.c: introduce '--separator=<paragraph-break>' option
  2023-03-29 14:15                 ` Teng Long
@ 2023-03-29 21:48                   ` Junio C Hamano
  2023-04-13  9:36                     ` Teng Long
  0 siblings, 1 reply; 186+ messages in thread
From: Junio C Hamano @ 2023-03-29 21:48 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

>> > diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
>> > index 3288aaec..716192b5 100755
>> > --- a/t/t3301-notes.sh
>> > +++ b/t/t3301-notes.sh
>> > @@ -5,6 +5,7 @@
>> >
>> >  test_description='Test commit notes'
>> >
>> > +TEST_PASSES_SANITIZE_LEAK=true
>> >  . ./test-lib.sh
>>
>> This is a strange commit to add this.  If the test and the code
>> involved in the test were leak-sanitizer clean before this commit,
>> then you should have been able to insert this without any other
>> change, and we should do it that way in a separate commit.  If we
>> are fixing an existing leak that the sanitizer complains, then that
>> is a different matter.  If that is the case, it makes perfect sense
>> to have this here, but the proposed commit log message should
>> explain that it is what is happening.
>
> Will fix.

Thanks.

Just FYI, it seems that it is indeed premature to declare that this
one to be leak-free.  With this topic merged to 'seen', the leak
checker CI job seems to choke on t3301 (in addition, it also barfs
on t3307).

https://github.com/git/git/actions/runs/4557669485/jobs/8039614419#step:4:1817


^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v7 2/4] notes.c: cleanup for "designated init"
  2023-03-28 14:28             ` [PATCH v7 2/4] notes.c: cleanup for "designated init" Teng Long
@ 2023-03-29 22:17               ` Junio C Hamano
  0 siblings, 0 replies; 186+ messages in thread
From: Junio C Hamano @ 2023-03-29 22:17 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> From: Teng Long <dyroneteng@gmail.com>
>
> The "struct note_data d = { 0, 0, NULL, STRBUF_INIT };" style could be
> replaced with designated init format.

I think our codebase calls it "designated initializer".

    notes.c: use designated initializers for clarity
    
    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>

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v7 3/4] notes.c: introduce '--separator=<paragraph-break>' option
  2023-03-29 21:48                   ` Junio C Hamano
@ 2023-04-13  9:36                     ` Teng Long
  0 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-04-13  9:36 UTC (permalink / raw)
  To: gitster; +Cc: avarab, dyroneteng, git, sunshine, tenglong.tl

Junio C Hamano <gitster@pobox.com> writes:

> Just FYI, it seems that it is indeed premature to declare that this
> one to be leak-free.  With this topic merged to 'seen', the leak
> checker CI job seems to choke on t3301 (in addition, it also barfs
> on t3307).

Yes, it contains a leak in function "parse_reuse_arg()", will fix.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* [PATCH 0/6] notes.c: introduce "--separator" option
  2023-03-28 14:28           ` [PATCH v7 0/4] " Teng Long
                               ` (3 preceding siblings ...)
  2023-03-28 14:28             ` [PATCH v7 4/4] notes.c: don't do stripespace when parse file arg Teng Long
@ 2023-04-25 13:34             ` Teng Long
  2023-04-25 13:34               ` [PATCH v8 1/6] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
                                 ` (6 more replies)
  4 siblings, 7 replies; 186+ messages in thread
From: Teng Long @ 2023-04-25 13:34 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

From: Teng Long <dyroneteng@gmail.com>

Diff since v7:

1. Fix problems that Junio's replies in last patch, except:

   * array of strbufs -> string_list (in [1]): the message may from
   a binary file which may contain '\0' in middle, string will be
   cut off then make a unexpected result.

2. New commit [3/6] for adding test cases about stripspace behavior.
3. New commit [6/6] for supporting "--[no-]stripspace".

[1] https://public-inbox.org/git/xmqq5yakhoo9.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 '--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             | 144 +++++++---
 t/t3301-notes.sh            | 126 +++++++++
 t/t3321-notes-stripspace.sh | 521 ++++++++++++++++++++++++++++++++++++
 4 files changed, 791 insertions(+), 42 deletions(-)
 create mode 100755 t/t3321-notes-stripspace.sh

Range-diff against v7:
1:  ea9c199e ! 1:  0634434e notes.c: cleanup 'strbuf_grow' call in 'append_edit'
    @@ builtin/notes.c: static int parse_msg_arg(const struct option *opt, const char *
      		strbuf_addch(&d->buf, '\n');
      	strbuf_addstr(&d->buf, arg);
     @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char *prefix)
    - 		enum object_type type;
    - 		char *prev_buf = read_object_file(note, &type, &size);
    + 		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)
2:  9dc123c8 ! 2:  4ad78405 notes.c: cleanup for "designated init"
    @@ Metadata
     Author: Teng Long <dyroneteng@gmail.com>
     
      ## Commit message ##
    -    notes.c: cleanup for "designated init"
    +    notes.c: use designated initializers for clarity
     
         The "struct note_data d = { 0, 0, NULL, STRBUF_INIT };" style could be
    -    replaced with designated init format.
    +    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 ##
     @@ builtin/notes.c: static int add(int argc, const char **argv, const char *prefix)
-:  -------- > 3:  6dfb5bf2 t3321: add test cases about the notes stripspace behavior
3:  d1febf86 ! 4:  be86f9ca notes.c: introduce '--separator=<paragraph-break>' option
    @@ Commit message
     
         The default behavour sometimes is not enough, the user may want
         to use a custom delimiter between paragraphs, like when
    -    specifiy '-m', '-F', '-C', '-c' options. So this commit
    +    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 execute:
    +    'git notes append', for example when executing:
     
             $ git notes add -m foo -m bar --separator="-"
             $ git notes show HEAD | cat
    @@ Commit message
             -
             bar
     
    -    A newline is added to the value given to --separator if it
    -    does not end with one already. So execute:
    +    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
    @@ Commit message
               "
               $ git notes add -m foo -m bar --separator="-$LF"
     
    -    Running A and B produces the same result.
    +    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: OPTIONS
     
      ## builtin/notes.c ##
     @@
    - #include "notes-utils.h"
    +  */
    + 
    + #include "cache.h"
    ++#include "alloc.h"
    + #include "config.h"
    + #include "builtin.h"
    + #include "gettext.h"
    +@@
      #include "worktree.h"
    + #include "write-or-die.h"
      
     +static char *separator = NULL;
      static const char * const git_notes_usage[] = {
    @@ builtin/notes.c
      	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: struct note_data {
    +@@ 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:");
    + 
    ++struct note_msg {
    ++	int stripspace;
    ++	struct strbuf buf;
    ++};
    ++
    + struct note_data {
    + 	int given;
      	int use_editor;
      	char *edit_path;
      	struct strbuf buf;
    -+	struct strbuf **messages;
    ++	struct note_msg **messages;
     +	size_t msg_nr;
     +	size_t msg_alloc;
      };
      
      static void free_note_data(struct note_data *d)
    +@@ builtin/notes.c: static void free_note_data(struct note_data *d)
    + 		free(d->edit_path);
    + 	}
    + 	strbuf_release(&d->buf);
    ++
    ++	while (d->msg_nr) {
    ++		--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,
     @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_id *oid)
      	}
      }
    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_
     +		strbuf_insertf(message, pos, "%s%s", separator, "\n");
     +}
     +
    -+/* Consume messages and append them into d->buf, then free them */
     +static void concat_messages(struct note_data *d)
     +{
     +	struct strbuf msg = STRBUF_INIT;
    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_
     +	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, d->messages[i]->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(d->messages[i]);
    -+		free(d->messages[i]);
     +	}
     +	strbuf_release(&msg);
    -+	free(d->messages);
     +}
     +
      static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
      {
      	struct note_data *d = opt->value;
    -+	struct strbuf *buf;
    ++	struct note_msg *msg = xmalloc(sizeof(*msg));
      
      	BUG_ON_OPT_NEG(unset);
      
    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_
     -		strbuf_addch(&d->buf, '\n');
     -	strbuf_addstr(&d->buf, arg);
     -	strbuf_stripspace(&d->buf, 0);
    -+	buf = xmalloc(sizeof(*buf));
    -+	strbuf_init(buf, strlen(arg));
    -+	strbuf_addstr(buf, arg);
    -+	strbuf_stripspace(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] = buf;
    ++	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 strbuf *buf;
    ++	struct note_msg *msg = xmalloc(sizeof(*msg));
      
      	BUG_ON_OPT_NEG(unset);
      
     -	if (d->buf.len)
     -		strbuf_addch(&d->buf, '\n');
    -+	buf = xmalloc(sizeof(*buf));
    -+	strbuf_init(buf, 0);
    -+
    ++	strbuf_init(&msg->buf , 0);
      	if (!strcmp(arg, "-")) {
     -		if (strbuf_read(&d->buf, 0, 1024) < 0)
    -+		if (strbuf_read(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(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);
    -+	strbuf_stripspace(buf, 0);
      
     -	d->given = 1;
    -+	// we will note stripspace the buf here, because the file maybe
    -+	// is a binary and it maybe contains multiple continuous line breaks.
     +	ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
    -+	d->messages[d->msg_nr - 1] = buf;
    ++	d->messages[d->msg_nr - 1] = msg;
    ++	msg->stripspace = 1;
      	return 0;
      }
      
    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_
      {
      	struct note_data *d = opt->value;
     -	char *buf;
    -+	struct strbuf *buf;
    ++	struct note_msg *msg = xmalloc(sizeof(*msg));
    ++	char *value;
      	struct object_id object;
      	enum object_type type;
      	unsigned long len;
    @@ builtin/notes.c: static void write_note_data(struct note_data *d, struct object_
     -	if (d->buf.len)
     -		strbuf_addch(&d->buf, '\n');
     -
    -+	buf = xmalloc(sizeof(*buf));
    - 	if (get_oid(arg, &object))
    ++	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 = read_object_file(&object, &type, &len)))
    -+	if (!(buf->buf = read_object_file(&object, &type, &len)))
    +-	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) {
    -+		strbuf_release(buf);
    - 		free(buf);
    +-		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;
    -+	// The reuse object maybe is a binary content which could
    -+	// contains '\0' in the middle, so let's set the correct
    -+	// lenth of strbuf to concat all of content.
    -+	buf->len = len;
    ++	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] = buf;
    ++	d->messages[d->msg_nr - 1] = msg;
    ++	msg->stripspace = 0;
      	return 0;
      }
      
    - static int parse_reedit_arg(const struct option *opt, const char *arg, int unset)
    - {
    - 	struct note_data *d = opt->value;
    -+
    - 	BUG_ON_OPT_NEG(unset);
    - 	d->use_editor = 1;
    - 	return parse_reuse_arg(opt, arg, unset);
     @@ builtin/notes.c: 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, .messages = NULL };
    + 	struct note_data d = { .buf = STRBUF_INIT };
     +
      	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)
     +
      	object_ref = argc > 1 ? argv[1] : "HEAD";
      
    - 	if (get_oid(object_ref, &object))
    -@@ builtin/notes.c: 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, .messages = NULL };
    -+
    - 	struct option options[] = {
    - 		OPT_CALLBACK_F('m', "message", &d, N_("message"),
    - 			N_("note contents as a string"), PARSE_OPT_NONEG,
    + 	if (repo_get_oid(the_repository, object_ref, &object))
     @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char *prefix)
      			parse_reuse_arg),
      		OPT_BOOL(0, "allow-empty", &allow_empty,
    @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char
      		fprintf(stderr, _("The -m/-F/-c/-C options have been deprecated "
      			"for the 'edit' subcommand.\n"
     @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char *prefix)
    - 		char *prev_buf = read_object_file(note, &type, &size);
    + 						       &type, &size);
      
      		if (d.buf.len && prev_buf && size)
     -			strbuf_insertstr(&d.buf, 0, "\n");
    @@ builtin/notes.c: static int append_edit(int argc, const char **argv, const char
      		free(prev_buf);
     
      ## t/t3301-notes.sh ##
    -@@
    - 
    - test_description='Test commit notes'
    - 
    -+TEST_PASSES_SANITIZE_LEAK=true
    - . ./test-lib.sh
    - 
    - write_script fake_editor <<\EOF
     @@ t/t3301-notes.sh: test_expect_success 'do not create empty note with -m ""' '
      '
      
4:  b9d12f0c < -:  -------- notes.c: don't do stripespace when parse file arg
-:  -------- > 5:  ef40e0ef notes.c: append separator instead of insert by pos
-:  -------- > 6:  f60f7432 notes.c: introduce "--[no-]stripspace" option
-- 
2.40.0.358.g931d6dc6


^ permalink raw reply	[flat|nested] 186+ messages in thread

* [PATCH v8 1/6] notes.c: cleanup 'strbuf_grow' call in 'append_edit'
  2023-04-25 13:34             ` [PATCH 0/6] notes.c: introduce "--separator" option Teng Long
@ 2023-04-25 13:34               ` Teng Long
  2023-04-25 13:34               ` [PATCH v8 2/6] notes.c: use designated initializers for clarity Teng Long
                                 ` (5 subsequent siblings)
  6 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-04-25 13:34 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

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(-)

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.g931d6dc6


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v8 2/6] notes.c: use designated initializers for clarity
  2023-04-25 13:34             ` [PATCH 0/6] notes.c: introduce "--separator" option Teng Long
  2023-04-25 13:34               ` [PATCH v8 1/6] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
@ 2023-04-25 13:34               ` Teng Long
  2023-04-25 13:34               ` [PATCH v8 3/6] t3321: add test cases about the notes stripspace behavior Teng Long
                                 ` (4 subsequent siblings)
  6 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-04-25 13:34 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

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(-)

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.g931d6dc6


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v8 3/6] t3321: add test cases about the notes stripspace behavior
  2023-04-25 13:34             ` [PATCH 0/6] notes.c: introduce "--separator" option Teng Long
  2023-04-25 13:34               ` [PATCH v8 1/6] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
  2023-04-25 13:34               ` [PATCH v8 2/6] notes.c: use designated initializers for clarity Teng Long
@ 2023-04-25 13:34               ` Teng Long
  2023-04-25 16:25                 ` Junio C Hamano
  2023-04-25 13:34               ` [PATCH v8 4/6] notes.c: introduce '--separator=<paragraph-break>' option Teng Long
                                 ` (3 subsequent siblings)
  6 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-04-25 13:34 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

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

diff --git a/t/t3321-notes-stripspace.sh b/t/t3321-notes-stripspace.sh
new file mode 100755
index 00000000..7c26b184
--- /dev/null
+++ b/t/t3321-notes-stripspace.sh
@@ -0,0 +1,291 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 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.g931d6dc6


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v8 4/6] notes.c: introduce '--separator=<paragraph-break>' option
  2023-04-25 13:34             ` [PATCH 0/6] notes.c: introduce "--separator" option Teng Long
                                 ` (2 preceding siblings ...)
  2023-04-25 13:34               ` [PATCH v8 3/6] t3321: add test cases about the notes stripspace behavior Teng Long
@ 2023-04-25 13:34               ` Teng Long
  2023-04-25 17:34                 ` Junio C Hamano
  2023-04-25 17:35                 ` Junio C Hamano
  2023-04-25 13:34               ` [PATCH v8 5/6] notes.c: append separator instead of insert by pos Teng Long
                                 ` (2 subsequent siblings)
  6 siblings, 2 replies; 186+ messages in thread
From: Teng Long @ 2023-04-25 13:34 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

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 | cat
     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 | cat
    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             | 111 ++++++++++++++++++++++++-------
 t/t3301-notes.sh            | 126 ++++++++++++++++++++++++++++++++++++
 3 files changed, 229 insertions(+), 29 deletions(-)

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..6ae8b57b 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 = NULL;
 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,13 @@ static void free_note_data(struct note_data *d)
 		free(d->edit_path);
 	}
 	strbuf_release(&d->buf);
+
+	while (d->msg_nr) {
+		--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 +230,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)
+		strbuf_insertstr(message, pos, "\n");
+	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;
 }
 
@@ -406,6 +456,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 +473,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 +486,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 +644,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 +659,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 +686,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.g931d6dc6


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v8 5/6] notes.c: append separator instead of insert by pos
  2023-04-25 13:34             ` [PATCH 0/6] notes.c: introduce "--separator" option Teng Long
                                 ` (3 preceding siblings ...)
  2023-04-25 13:34               ` [PATCH v8 4/6] notes.c: introduce '--separator=<paragraph-break>' option Teng Long
@ 2023-04-25 13:34               ` Teng Long
  2023-04-25 17:47                 ` Junio C Hamano
  2023-04-25 13:34               ` [PATCH v8 6/6] notes.c: introduce "--[no-]stripspace" option Teng Long
  2023-04-28  9:23               ` [PATCH v9 0/6] notes.c: introduce "--separator" option Teng Long
  6 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-04-25 13:34 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

From: Teng Long <dyroneteng@gmail.com>

This commit 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 | 23 +++++++++++++----------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index 6ae8b57b..84bc09ed 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -230,14 +230,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)
-		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);
 	else
-		strbuf_insertf(message, pos, "%s%s", separator, "\n");
+		strbuf_insertf(message, message->len, "%s%s", separator, "\n");
 }
 
 static void concat_messages(struct note_data *d)
@@ -247,7 +247,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)
@@ -682,14 +682,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.g931d6dc6


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v8 6/6] notes.c: introduce "--[no-]stripspace" option
  2023-04-25 13:34             ` [PATCH 0/6] notes.c: introduce "--separator" option Teng Long
                                 ` (4 preceding siblings ...)
  2023-04-25 13:34               ` [PATCH v8 5/6] notes.c: append separator instead of insert by pos Teng Long
@ 2023-04-25 13:34               ` Teng Long
  2023-04-25 17:49                 ` Junio C Hamano
  2023-04-28  9:23               ` [PATCH v9 0/6] notes.c: introduce "--separator" option Teng Long
  6 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-04-25 13:34 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

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, 304 insertions(+), 47 deletions(-)

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 84bc09ed..b27d82d7 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;
@@ -214,7 +221,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);
 	}
 }
 
@@ -250,7 +258,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);
 	}
@@ -268,7 +278,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;
 }
 
@@ -288,7 +298,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;
 }
 
@@ -321,7 +331,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;
 }
 
@@ -455,7 +465,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"),
@@ -475,6 +485,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()
 	};
 
@@ -628,7 +640,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,
@@ -646,6 +658,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 7c26b184..8e921157 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,25 +75,37 @@ 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 '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  &&
+	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
@@ -85,34 +113,30 @@ 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 &&
@@ -123,12 +147,12 @@ 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
@@ -158,6 +182,89 @@ 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_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" &&
+	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
 '
 
@@ -221,6 +328,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 +375,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 +388,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 +441,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 +460,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.g931d6dc6


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* Re: [PATCH v8 3/6] t3321: add test cases about the notes stripspace behavior
  2023-04-25 13:34               ` [PATCH v8 3/6] t3321: add test cases about the notes stripspace behavior Teng Long
@ 2023-04-25 16:25                 ` Junio C Hamano
  2023-04-27  3:47                   ` Teng Long
  0 siblings, 1 reply; 186+ messages in thread
From: Junio C Hamano @ 2023-04-25 16:25 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> 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

Looks quite thorough.

> diff --git a/t/t3321-notes-stripspace.sh b/t/t3321-notes-stripspace.sh
> new file mode 100755
> index 00000000..7c26b184
> --- /dev/null
> +++ b/t/t3321-notes-stripspace.sh
> @@ -0,0 +1,291 @@
> +#!/bin/sh
> +#
> +# Copyright (c) 2007 Teng Long

It's 2023 now.

> +test_expect_success 'add note by editor' '
> +	test_when_finished "git notes remove" &&
> +	cat >expect <<-EOF &&
> +		first-line
> +
> +		second-line
> +	EOF
> ...
> +test_expect_success 'append note by specifying multiple "-m"' '
> +	test_when_finished "git notes remove" &&
> +	cat >expect <<-EOF &&
> +	first-line
> +
> +	second-line
> +	EOF

Inconsistent indentation confuses readers if the author meant
something unexplained by the difference between the two.  Stick to
one style (I personally prefer the "indent to the same level as the
starting 'cat' and ending 'EOF'" but it is OK to pick the other one,
as long as it is consistent within a single test script).

> +	cat >note-file <<-EOF &&
> +		${LF}
> +		first-line
> +		${MULTI_LF}
> +		second-line
> +		${LF}
> +	EOF

This is a bit misleading, as there are TWO blank lines before the
"first-line" (one from the here text itself, the other is from
${LF}).  

I do not think it matters too much, because the point of stripspace
is to remove any number of leading or trailing blank lines, and
squash one or more blank lines elsewhere into one, so having two
blank lines at the beginning or at the end of the file is just as
good an example as having a single blank line.  I am mentioning it
primarily because I had to spend some time thinking about ways to
make it less misleading.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v8 4/6] notes.c: introduce '--separator=<paragraph-break>' option
  2023-04-25 13:34               ` [PATCH v8 4/6] notes.c: introduce '--separator=<paragraph-break>' option Teng Long
@ 2023-04-25 17:34                 ` Junio C Hamano
  2023-04-27  7:21                   ` Teng Long
  2023-04-25 17:35                 ` Junio C Hamano
  1 sibling, 1 reply; 186+ messages in thread
From: Junio C Hamano @ 2023-04-25 17:34 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> 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 | cat
>      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 | cat
>     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             | 111 ++++++++++++++++++++++++-------
>  t/t3301-notes.sh            | 126 ++++++++++++++++++++++++++++++++++++
>  3 files changed, 229 insertions(+), 29 deletions(-)
>
> 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..6ae8b57b 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 = NULL;
>  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,13 @@ static void free_note_data(struct note_data *d)
>  		free(d->edit_path);
>  	}
>  	strbuf_release(&d->buf);
> +
> +	while (d->msg_nr) {
> +		--d->msg_nr;
> +		strbuf_release(&d->messages[d->msg_nr]->buf);
> +		free(d->messages[d->msg_nr]);
> +	}

        while (d->msg_nr--) {
                strbuf_relesae(...);
                free(d->messages[d->msg_nr]);
        }

> +	free(d->messages);
>  }



> @@ -213,65 +230,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)
> +		strbuf_insertstr(message, pos, "\n");
> +	else if (separator[strlen(separator) - 1] == '\n')
> +		strbuf_insertstr(message, pos, separator);
> +	else
> +		strbuf_insertf(message, pos, "%s%s", separator, "\n");
> +}

If you massage an unspecified separator beforehand into "\n"
(e.g. initialize separator to "\n" instead of NULL), you can lose
one of these three at runtime.

> +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);
> +}

Interesting.  

I would have expected that where we add to d->messages[] array a new
note_msg structure and set its .stripspace member, we already knew
if the associated strbuf needs to be stripspace'd and instead of
maintaining the .stripspace member for each note_msg, we can just
have it contain only a string (not even a strbuf).

The above loop, and the design to have .stripspace per each
note_msg, smell iffy to me for one thing.  The .stripspace member is
used to flag "after adding this element, the whole thing need to be
treated with stripspace".  Is it intended?  What it means is that if
you have two elements in d->messages[] array, one of them with and
the other one without the .stripspace bit set, depending on the
order of the elements, the result would be vastly different.  When
the later element has the stripspace bit set, everything gets
cleansed, but when the earlier element has it, only the earlier
element gets cleansed and the later element is added with multiple
blank lines and trailing whitespaces intact.  It does not sound
quite right.

I wonder if the semantics intended (not implemented) by this series
is to concatenate if none of the elements want stripspace and cleanse
the whitespaces if any of them want stripspace, in which case an
obvious implementation would be to just concatenate everything in
the loop with separators, while seeing if any of them have the
stripspace bit set, and then after the loop finishes, run stripspace
on the whole thing just once if any of d->messages[] asked for it?
If that is the case, an even better design could be to move the
stripspace bit out of d->messages[] and make it d->stripspace to
apply to the whole thing.

Another possiblity is to just stripspace the elements taken from the
source you want to stripspace (like "-m" and "-F" but not the ones
taken from parse_reuse_arg()) before you even add them to
d->messages[] array.  That way, the only possible source of multiple
blank lines and trailing whitespaces in the concatenation of
elements you want stripspace would be from the separator, which is
under control by the end user.  So your concatenation could just
ignore running stripspace at all---if the user does (or does not)
want multiple blank lines or trailing whitespaces on the separator
line, that's under their control.  That sounds like the simplest and
cleanest design---we strip as we read from each source and make them
into simple strings to be kept in d->messages[] without having to
allocate and keep a strbuf per each source, and we concatenate just
once, without worrying about stripspace.

The simple approach may break when any of the elements ended up to
be truly empty, but then we can solve that by refraining to push an
empty result into d->messages[] array, or something?  I dunno.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v8 4/6] notes.c: introduce '--separator=<paragraph-break>' option
  2023-04-25 13:34               ` [PATCH v8 4/6] notes.c: introduce '--separator=<paragraph-break>' option Teng Long
  2023-04-25 17:34                 ` Junio C Hamano
@ 2023-04-25 17:35                 ` Junio C Hamano
  1 sibling, 0 replies; 186+ messages in thread
From: Junio C Hamano @ 2023-04-25 17:35 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> 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 | cat
>      foo
>
>      bar

That's a pointless piping to "cat".  Piping to "cat -n" might be a
good way to illustrate what happens, though, i.e.

    $ git notes show HEAD | cat -n
         1  foo
         2
         3  bar

I have no strong preference either way (the other more obvious
alternative is to just remove "| cat").

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v8 5/6] notes.c: append separator instead of insert by pos
  2023-04-25 13:34               ` [PATCH v8 5/6] notes.c: append separator instead of insert by pos Teng Long
@ 2023-04-25 17:47                 ` Junio C Hamano
  2023-04-27  7:51                   ` Teng Long
  0 siblings, 1 reply; 186+ messages in thread
From: Junio C Hamano @ 2023-04-25 17:47 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> From: Teng Long <dyroneteng@gmail.com>
>
> This commit rename "insert_separator" to "append_separator" and also

"This commit rename" -> "Rename".

> 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.

OK.

> 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

Funny placement of comma?

> +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);
>  	else
> -		strbuf_insertf(message, pos, "%s%s", separator, "\n");
> +		strbuf_insertf(message, message->len, "%s%s", separator, "\n");
>  }

Is it still needed to use strbuf_insert*() variants when the only
thing being done is to append at the end?  Use of strbuf_add*()
would let you drop message->len from the arguments.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v8 6/6] notes.c: introduce "--[no-]stripspace" option
  2023-04-25 13:34               ` [PATCH v8 6/6] notes.c: introduce "--[no-]stripspace" option Teng Long
@ 2023-04-25 17:49                 ` Junio C Hamano
  2023-04-28  7:40                   ` Teng Long
  0 siblings, 1 reply; 186+ messages in thread
From: Junio C Hamano @ 2023-04-25 17:49 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> 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.

Makes sense.

>     ... 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 sounds more like a design/implementation mistake that we may
want to fix.


^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v8 3/6] t3321: add test cases about the notes stripspace behavior
  2023-04-25 16:25                 ` Junio C Hamano
@ 2023-04-27  3:47                   ` Teng Long
  0 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-04-27  3:47 UTC (permalink / raw)
  To: gitster; +Cc: avarab, dyroneteng, git, sunshine, tenglong.tl

Junio C Hamano <gitster@pobox.com> writes:

>> ---
>>  t/t3321-notes-stripspace.sh | 291 ++++++++++++++++++++++++++++++++++++
>>  1 file changed, 291 insertions(+)
>>  create mode 100755 t/t3321-notes-stripspace.sh
>
>Looks quite thorough.

I'm not sure if I've covered everything, but I've tried to addi
some basic test cases to give us a chance to add on top of that later.

>> diff --git a/t/t3321-notes-stripspace.sh b/t/t3321-notes-stripspace.sh
>> new file mode 100755
>> index 00000000..7c26b184
>> --- /dev/null
>> +++ b/t/t3321-notes-stripspace.sh
>> @@ -0,0 +1,291 @@
>> +#!/bin/sh
>> +#
>> +# Copyright (c) 2007 Teng Long
>
>It's 2023 now.

Will fix.

>> +test_expect_success 'add note by editor' '
>> +	test_when_finished "git notes remove" &&
>> +	cat >expect <<-EOF &&
>> +		first-line
>> +
>> +		second-line
>> +	EOF
>> ...
>> +test_expect_success 'append note by specifying multiple "-m"' '
>> +	test_when_finished "git notes remove" &&
>> +	cat >expect <<-EOF &&
>> +	first-line
>> +
>> +	second-line
>> +	EOF
>
>Inconsistent indentation confuses readers if the author meant
>something unexplained by the difference between the two.  Stick to
>one style (I personally prefer the "indent to the same level as the
>starting 'cat' and ending 'EOF'" but it is OK to pick the other one,
>as long as it is consistent within a single test script).

I will follow as your prefer format, thanks.

>> +	cat >note-file <<-EOF &&
>> +		${LF}
>> +		first-line
>> +		${MULTI_LF}
>> +		second-line
>> +		${LF}
>> +	EOF
>
>This is a bit misleading, as there are TWO blank lines before the
>"first-line" (one from the here text itself, the other is from
>${LF}).  
>
>I do not think it matters too much, because the point of stripspace
>is to remove any number of leading or trailing blank lines, and
>squash one or more blank lines elsewhere into one, so having two
>blank lines at the beginning or at the end of the file is just as
>good an example as having a single blank line.  I am mentioning it
>primarily because I had to spend some time thinking about ways to
>make it less misleading.

Yes, a little bit, I don't found a more clealy so far. It works for
now but I'm willing to optimize if there's a better way.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v8 4/6] notes.c: introduce '--separator=<paragraph-break>' option
  2023-04-25 17:34                 ` Junio C Hamano
@ 2023-04-27  7:21                   ` Teng Long
  2023-04-27 18:21                     ` Junio C Hamano
  0 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-04-27  7:21 UTC (permalink / raw)
  To: gitster; +Cc: avarab, dyroneteng, git, sunshine, tenglong.tl

Junio C Hamano <gitster@pobox.com> writes:

>> +	while (d->msg_nr) {
>> +		--d->msg_nr;
>> +		strbuf_release(&d->messages[d->msg_nr]->buf);
>> +		free(d->messages[d->msg_nr]);
>> +	}
>
>        while (d->msg_nr--) {
>                strbuf_relesae(...);
>                free(d->messages[d->msg_nr]);
>        }

Will fix or optimize.

>> +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);
>> +}
>
>Interesting.  
>
>I would have expected that where we add to d->messages[] array a new
>note_msg structure and set its .stripspace member, we already knew
>if the associated strbuf needs to be stripspace'd and instead of
>maintaining the .stripspace member for each note_msg, we can just
>have it contain only a string (not even a strbuf).

That's because the "-C" option, it will not do stripspace for the
whole note, but support to use together with "-m" or "-F"(they will
do stripspace). So, for the consistent result with backforward
compatibility, we have to get the information about whether to do
stripspace when concating each message.

>The above loop, and the design to have .stripspace per each
>note_msg, smell iffy to me for one thing.  The .stripspace member is
>used to flag "after adding this element, the whole thing need to be
>treated with stripspace".  Is it intended?  What it means is that if
>you have two elements in d->messages[] array, one of them with and
>the other one without the .stripspace bit set, depending on the
>order of the elements, the result would be vastly different.  When
>the later element has the stripspace bit set, everything gets
>cleansed, but when the earlier element has it, only the earlier
>element gets cleansed and the later element is added with multiple
>blank lines and trailing whitespaces intact.  It does not sound
>quite right.

In the previous patch, I actually followed your suggestion to only
stripspace message itself but not the whole concatenate message. But
actually, there's a problem with this, in fact it's also caused by
"-C", "-C" doesn't stripspace, means that when mixed with "-m" and
"-F", the order of the options matters.

I'm not sure if this is a design flaw or if it's intentional, but
it's special about the stripspace handling of "-C" right now, which
is why I've added two new test cases in [3/6], just to make sure my
subsequent patches don't break them:

    * 'add notes with "-C" and "-m", "-m" will stripspace all together'

    * 'add notes with "-m" and "-C", "-C" will not stripspace all together'

>I wonder if the semantics intended (not implemented) by this series
>is to concatenate if none of the elements want stripspace and cleanse
>the whitespaces if any of them want stripspace, in which case an
>obvious implementation would be to just concatenate everything in
>the loop with separators, while seeing if any of them have the
>stripspace bit set, and then after the loop finishes, run stripspace
>on the whole thing just once if any of d->messages[] asked for it?
>If that is the case, an even better design could be to move the
>stripspace bit out of d->messages[] and make it d->stripspace to
>apply to the whole thing.

I agree, but this seems to break the effect of "the order matters" because of
the "-C" argument. 

If we think that the "-C" particularity is a case that should be optimized or
fixed(), then I strongly agree with this modification approach, and I think
that compared to this particularity, on the one hand, our implementation
is simple, on the other hand, users may find it easier to understand.

>Another possiblity is to just stripspace the elements taken from the
>source you want to stripspace (like "-m" and "-F" but not the ones
>taken from parse_reuse_arg()) before you even add them to
>d->messages[] array.  That way, the only possible source of multiple
>blank lines and trailing whitespaces in the concatenation of
>elements you want stripspace would be from the separator, which is
>under control by the end user. ...

I think this approach is feasible, and add judgment logic when supporting
subsequent --[no-] stripspaces.

>... So your concatenation could just
>ignore running stripspace at all---if the user does (or does not)
>want multiple blank lines or trailing whitespaces on the separator
>line, that's under their control.  That sounds like the simplest and
>cleanest design---we strip as we read from each source and make them
>into simple strings to be kept in d->messages[] without having to
>allocate and keep a strbuf per each source, and we concatenate just
>once, without worrying about stripspace.

The strbuf can record the "len" of the buf, to make sure the string
will be cut of if the content contains '\0' in the middle, like a binary
file. Maybe using a string could make that, but I‘m not sure how to solve
it.

>The simple approach may break when any of the elements ended up to
>be truly empty, but then we can solve that by refraining to push an
>empty result into d->messages[] array, or something?  I dunno.

I will add some test cases about it and found it out.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v8 5/6] notes.c: append separator instead of insert by pos
  2023-04-25 17:47                 ` Junio C Hamano
@ 2023-04-27  7:51                   ` Teng Long
  0 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-04-27  7:51 UTC (permalink / raw)
  To: gitster; +Cc: avarab, dyroneteng, git, sunshine, tenglong.tl

Junio C Hamano <gitster@pobox.com> writes:

>> From: Teng Long <dyroneteng@gmail.com>
>>
>> This commit rename "insert_separator" to "append_separator" and also
>
>"This commit rename" -> "Rename".
>
>...
>
>> 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
>
>Funny placement of comma?
>
>> +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);
>>  	else
>> -		strbuf_insertf(message, pos, "%s%s", separator, "\n");
>> +		strbuf_insertf(message, message->len, "%s%s", separator, "\n");
>>  }
>
>Is it still needed to use strbuf_insert*() variants when the only
>thing being done is to append at the end?  Use of strbuf_add*()
>would let you drop message->len from the arguments.

Will fix.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v8 4/6] notes.c: introduce '--separator=<paragraph-break>' option
  2023-04-27  7:21                   ` Teng Long
@ 2023-04-27 18:21                     ` Junio C Hamano
  0 siblings, 0 replies; 186+ messages in thread
From: Junio C Hamano @ 2023-04-27 18:21 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> Junio C Hamano <gitster@pobox.com> writes:
>
>>> +	while (d->msg_nr) {
>>> +		--d->msg_nr;
>>> +		strbuf_release(&d->messages[d->msg_nr]->buf);
>>> +		free(d->messages[d->msg_nr]);
>>> +	}
>>
>>        while (d->msg_nr--) {
>>                strbuf_relesae(...);
>>                free(d->messages[d->msg_nr]);
>>        }
>
> Will fix or optimize.

Not a huge thing and I would be very surprised if you saw a
different machine code output.  The original stood out like a sore
thumb at me, primarily because we rarely if ever do pre-decrement or
pre-increment in the void context in our own code.

>>Interesting.  
>>
>>I would have expected that where we add to d->messages[] array a new
>>note_msg structure and set its .stripspace member, we already knew
>>if the associated strbuf needs to be stripspace'd and instead of
>>maintaining the .stripspace member for each note_msg, we can just
>>have it contain only a string (not even a strbuf).
>
> That's because the "-C" option, it will not do stripspace for the
> whole note, but support to use together with "-m" or "-F"(they will
> do stripspace). So, for the consistent result with backforward
> compatibility, we have to get the information about whether to do
> stripspace when concating each message.

OK.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v8 6/6] notes.c: introduce "--[no-]stripspace" option
  2023-04-25 17:49                 ` Junio C Hamano
@ 2023-04-28  7:40                   ` Teng Long
  2023-04-28 18:21                     ` Junio C Hamano
  0 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-04-28  7:40 UTC (permalink / raw)
  To: gitster; +Cc: avarab, dyroneteng, git, sunshine, tenglong.tl

>Teng Long <dyroneteng@gmail.com> writes:
>
>> 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.
>
>Makes sense.
>
>>     ... 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 sounds more like a design/implementation mistake that we may
>want to fix.

I doubted this either, but for compatibility, implementations of
this patchset have been made, such as "note_msg" recording the
"stripspace" field, to implement these two new options without
breaking the old behavior.

Thanks.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* [PATCH v9 0/6] notes.c: introduce "--separator" option
  2023-04-25 13:34             ` [PATCH 0/6] notes.c: introduce "--separator" option Teng Long
                                 ` (5 preceding siblings ...)
  2023-04-25 13:34               ` [PATCH v8 6/6] notes.c: introduce "--[no-]stripspace" option Teng Long
@ 2023-04-28  9:23               ` Teng Long
  2023-04-28  9:23                 ` [PATCH v9 1/6] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
                                   ` (8 more replies)
  6 siblings, 9 replies; 186+ messages in thread
From: Teng Long @ 2023-04-28  9:23 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

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


^ permalink raw reply	[flat|nested] 186+ messages in thread

* [PATCH v9 1/6] notes.c: cleanup 'strbuf_grow' call in 'append_edit'
  2023-04-28  9:23               ` [PATCH v9 0/6] notes.c: introduce "--separator" option Teng Long
@ 2023-04-28  9:23                 ` Teng Long
  2023-04-28  9:23                 ` [PATCH v9 2/6] notes.c: use designated initializers for clarity Teng Long
                                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-04-28  9:23 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

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(-)

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


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v9 2/6] notes.c: use designated initializers for clarity
  2023-04-28  9:23               ` [PATCH v9 0/6] notes.c: introduce "--separator" option Teng Long
  2023-04-28  9:23                 ` [PATCH v9 1/6] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
@ 2023-04-28  9:23                 ` Teng Long
  2023-04-28  9:23                 ` [PATCH v9 3/6] t3321: add test cases about the notes stripspace behavior Teng Long
                                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-04-28  9:23 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

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(-)

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


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v9 3/6] t3321: add test cases about the notes stripspace behavior
  2023-04-28  9:23               ` [PATCH v9 0/6] notes.c: introduce "--separator" option Teng Long
  2023-04-28  9:23                 ` [PATCH v9 1/6] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
  2023-04-28  9:23                 ` [PATCH v9 2/6] notes.c: use designated initializers for clarity Teng Long
@ 2023-04-28  9:23                 ` Teng Long
  2023-04-28  9:23                 ` [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option Teng Long
                                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-04-28  9:23 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

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

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


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option
  2023-04-28  9:23               ` [PATCH v9 0/6] notes.c: introduce "--separator" option Teng Long
                                   ` (2 preceding siblings ...)
  2023-04-28  9:23                 ` [PATCH v9 3/6] t3321: add test cases about the notes stripspace behavior Teng Long
@ 2023-04-28  9:23                 ` Teng Long
  2023-04-28 20:44                   ` Junio C Hamano
                                     ` (2 more replies)
  2023-04-28  9:23                 ` [PATCH v9 5/6] notes.c: append separator instead of insert by pos Teng Long
                                   ` (4 subsequent siblings)
  8 siblings, 3 replies; 186+ messages in thread
From: Teng Long @ 2023-04-28  9:23 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

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(-)

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


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v9 5/6] notes.c: append separator instead of insert by pos
  2023-04-28  9:23               ` [PATCH v9 0/6] notes.c: introduce "--separator" option Teng Long
                                   ` (3 preceding siblings ...)
  2023-04-28  9:23                 ` [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option Teng Long
@ 2023-04-28  9:23                 ` Teng Long
  2023-04-28  9:23                 ` [PATCH v9 6/6] notes.c: introduce "--[no-]stripspace" option Teng Long
                                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-04-28  9:23 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

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(-)

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


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v9 6/6] notes.c: introduce "--[no-]stripspace" option
  2023-04-28  9:23               ` [PATCH v9 0/6] notes.c: introduce "--separator" option Teng Long
                                   ` (4 preceding siblings ...)
  2023-04-28  9:23                 ` [PATCH v9 5/6] notes.c: append separator instead of insert by pos Teng Long
@ 2023-04-28  9:23                 ` Teng Long
  2023-04-28 20:46                 ` [PATCH v9 0/6] notes.c: introduce "--separator" option Junio C Hamano
                                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-04-28  9:23 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

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(-)

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


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* Re: [PATCH v8 6/6] notes.c: introduce "--[no-]stripspace" option
  2023-04-28  7:40                   ` Teng Long
@ 2023-04-28 18:21                     ` Junio C Hamano
  0 siblings, 0 replies; 186+ messages in thread
From: Junio C Hamano @ 2023-04-28 18:21 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

>>Teng Long <dyroneteng@gmail.com> writes:
>>
>>> 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.
>>
>>Makes sense.
>>
>>>     ... 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 sounds more like a design/implementation mistake that we may
>>want to fix.
>
> I doubted this either, but for compatibility, implementations of
> this patchset have been made, such as "note_msg" recording the
> "stripspace" field, to implement these two new options without
> breaking the old behavior.

Thanks, that is a sensible stance to take.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option
  2023-04-28  9:23                 ` [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option Teng Long
@ 2023-04-28 20:44                   ` Junio C Hamano
  2023-05-06  9:12                     ` Teng Long
  2023-05-10 19:19                   ` Kristoffer Haugsbakk
  2023-06-14  1:02                   ` Junio C Hamano
  2 siblings, 1 reply; 186+ messages in thread
From: Junio C Hamano @ 2023-04-28 20:44 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

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";

> @@ -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.


^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v9 0/6] notes.c: introduce "--separator" option
  2023-04-28  9:23               ` [PATCH v9 0/6] notes.c: introduce "--separator" option Teng Long
                                   ` (5 preceding siblings ...)
  2023-04-28  9:23                 ` [PATCH v9 6/6] notes.c: introduce "--[no-]stripspace" option Teng Long
@ 2023-04-28 20:46                 ` Junio C Hamano
  2023-05-01 22:29                 ` Junio C Hamano
  2023-05-18 12:02                 ` [PATCH v10 " Teng Long
  8 siblings, 0 replies; 186+ messages in thread
From: Junio C Hamano @ 2023-04-28 20:46 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

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.

> 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.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v9 0/6] notes.c: introduce "--separator" option
  2023-04-28  9:23               ` [PATCH v9 0/6] notes.c: introduce "--separator" option Teng Long
                                   ` (6 preceding siblings ...)
  2023-04-28 20:46                 ` [PATCH v9 0/6] notes.c: introduce "--separator" option Junio C Hamano
@ 2023-05-01 22:29                 ` Junio C Hamano
  2023-05-18 12:02                 ` [PATCH v10 " Teng Long
  8 siblings, 0 replies; 186+ messages in thread
From: Junio C Hamano @ 2023-05-01 22:29 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> 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.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option
  2023-04-28 20:44                   ` Junio C Hamano
@ 2023-05-06  9:12                     ` Teng Long
  2023-05-06  9:22                       ` Teng Long
  0 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-05-06  9:12 UTC (permalink / raw)
  To: gitster; +Cc: avarab, dyroneteng, git, sunshine, tenglong.tl

Junio C Hamano <gitster@pobox.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";
>
>> @@ -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.

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);

^ permalink raw reply related	[flat|nested] 186+ messages in thread

* Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option
  2023-05-06  9:12                     ` Teng Long
@ 2023-05-06  9:22                       ` Teng Long
  0 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-05-06  9:22 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

>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.

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);

^ permalink raw reply related	[flat|nested] 186+ messages in thread

* Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option
  2023-04-28  9:23                 ` [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option Teng Long
  2023-04-28 20:44                   ` Junio C Hamano
@ 2023-05-10 19:19                   ` Kristoffer Haugsbakk
  2023-05-12  4:07                     ` Teng Long
  2023-06-14  1:02                   ` Junio C Hamano
  2 siblings, 1 reply; 186+ messages in thread
From: Kristoffer Haugsbakk @ 2023-05-10 19:19 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, Junio C Hamano, sunshine, tenglong.tl

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]

> 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

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option
  2023-05-10 19:19                   ` Kristoffer Haugsbakk
@ 2023-05-12  4:07                     ` Teng Long
  2023-05-12  7:29                       ` Kristoffer Haugsbakk
  2023-05-16 17:00                       ` Junio C Hamano
  0 siblings, 2 replies; 186+ messages in thread
From: Teng Long @ 2023-05-12  4:07 UTC (permalink / raw)
  To: code; +Cc: avarab, dyroneteng, git, gitster, sunshine, tenglong.tl

"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.

>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.


^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option
  2023-05-12  4:07                     ` Teng Long
@ 2023-05-12  7:29                       ` Kristoffer Haugsbakk
  2023-05-16 17:00                       ` Junio C Hamano
  1 sibling, 0 replies; 186+ messages in thread
From: Kristoffer Haugsbakk @ 2023-05-12  7:29 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, Junio C Hamano, Eric Sunshine, tenglong.tl

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

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option
  2023-05-12  4:07                     ` Teng Long
  2023-05-12  7:29                       ` Kristoffer Haugsbakk
@ 2023-05-16 17:00                       ` Junio C Hamano
  2023-05-17  3:58                         ` Teng Long
  1 sibling, 1 reply; 186+ messages in thread
From: Junio C Hamano @ 2023-05-16 17:00 UTC (permalink / raw)
  To: Teng Long; +Cc: code, avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> 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.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option
  2023-05-16 17:00                       ` Junio C Hamano
@ 2023-05-17  3:58                         ` Teng Long
  2023-05-17 15:32                           ` Junio C Hamano
  0 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-05-17  3:58 UTC (permalink / raw)
  To: gitster; +Cc: avarab, code, dyroneteng, git, sunshine, tenglong.tl

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.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v9 4/6] notes.c: introduce '--separator=<paragraph-break>' option
  2023-05-17  3:58                         ` Teng Long
@ 2023-05-17 15:32                           ` Junio C Hamano
  0 siblings, 0 replies; 186+ messages in thread
From: Junio C Hamano @ 2023-05-17 15:32 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, code, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> 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.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* [PATCH v10 0/6] notes.c: introduce "--separator" option
  2023-04-28  9:23               ` [PATCH v9 0/6] notes.c: introduce "--separator" option Teng Long
                                   ` (7 preceding siblings ...)
  2023-05-01 22:29                 ` Junio C Hamano
@ 2023-05-18 12:02                 ` Teng Long
  2023-05-18 12:02                   ` [PATCH v10 1/6] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
                                     ` (8 more replies)
  8 siblings, 9 replies; 186+ messages in thread
From: Teng Long @ 2023-05-18 12:02 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

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


^ permalink raw reply	[flat|nested] 186+ messages in thread

* [PATCH v10 1/6] notes.c: cleanup 'strbuf_grow' call in 'append_edit'
  2023-05-18 12:02                 ` [PATCH v10 " Teng Long
@ 2023-05-18 12:02                   ` Teng Long
  2023-05-18 12:02                   ` [PATCH v10 2/6] notes.c: use designated initializers for clarity Teng Long
                                     ` (7 subsequent siblings)
  8 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-05-18 12:02 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

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(-)

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


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v10 2/6] notes.c: use designated initializers for clarity
  2023-05-18 12:02                 ` [PATCH v10 " Teng Long
  2023-05-18 12:02                   ` [PATCH v10 1/6] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
@ 2023-05-18 12:02                   ` Teng Long
  2023-05-18 12:02                   ` [PATCH v10 3/6] t3321: add test cases about the notes stripspace behavior Teng Long
                                     ` (6 subsequent siblings)
  8 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-05-18 12:02 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

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(-)

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


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v10 3/6] t3321: add test cases about the notes stripspace behavior
  2023-05-18 12:02                 ` [PATCH v10 " Teng Long
  2023-05-18 12:02                   ` [PATCH v10 1/6] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
  2023-05-18 12:02                   ` [PATCH v10 2/6] notes.c: use designated initializers for clarity Teng Long
@ 2023-05-18 12:02                   ` Teng Long
  2023-05-18 12:02                   ` [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option Teng Long
                                     ` (5 subsequent siblings)
  8 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-05-18 12:02 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

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

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


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option
  2023-05-18 12:02                 ` [PATCH v10 " Teng Long
                                     ` (2 preceding siblings ...)
  2023-05-18 12:02                   ` [PATCH v10 3/6] t3321: add test cases about the notes stripspace behavior Teng Long
@ 2023-05-18 12:02                   ` Teng Long
  2023-05-18 14:34                     ` Kristoffer Haugsbakk
  2023-05-19  0:54                     ` Jeff King
  2023-05-18 12:02                   ` [PATCH v10 5/6] notes.c: append separator instead of insert by pos Teng Long
                                     ` (4 subsequent siblings)
  8 siblings, 2 replies; 186+ messages in thread
From: Teng Long @ 2023-05-18 12:02 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

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(-)

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


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v10 5/6] notes.c: append separator instead of insert by pos
  2023-05-18 12:02                 ` [PATCH v10 " Teng Long
                                     ` (3 preceding siblings ...)
  2023-05-18 12:02                   ` [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option Teng Long
@ 2023-05-18 12:02                   ` Teng Long
  2023-05-18 12:02                   ` [PATCH v10 6/6] notes.c: introduce "--[no-]stripspace" option Teng Long
                                     ` (3 subsequent siblings)
  8 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-05-18 12:02 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

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(-)

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


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* [PATCH v10 6/6] notes.c: introduce "--[no-]stripspace" option
  2023-05-18 12:02                 ` [PATCH v10 " Teng Long
                                     ` (4 preceding siblings ...)
  2023-05-18 12:02                   ` [PATCH v10 5/6] notes.c: append separator instead of insert by pos Teng Long
@ 2023-05-18 12:02                   ` Teng Long
  2023-05-18 13:56                   ` [PATCH v10 0/6] notes.c: introduce "--separator" option Kristoffer Haugsbakk
                                     ` (2 subsequent siblings)
  8 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-05-18 12:02 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl

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(-)

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


^ permalink raw reply related	[flat|nested] 186+ messages in thread

* Re: [PATCH v10 0/6] notes.c: introduce "--separator" option
  2023-05-18 12:02                 ` [PATCH v10 " Teng Long
                                     ` (5 preceding siblings ...)
  2023-05-18 12:02                   ` [PATCH v10 6/6] notes.c: introduce "--[no-]stripspace" option Teng Long
@ 2023-05-18 13:56                   ` Kristoffer Haugsbakk
  2023-05-20 10:22                     ` Teng Long
  2023-05-18 15:17                   ` Junio C Hamano
  2023-05-27  7:57                   ` [PATCH v11 0/7] notes.c: introduce "--separator" Teng Long
  8 siblings, 1 reply; 186+ messages in thread
From: Kristoffer Haugsbakk @ 2023-05-18 13:56 UTC (permalink / raw)
  To: Teng Long
  Cc: Ævar Arnfjörð Bjarmason, git, Junio C Hamano,
	Eric Sunshine, tenglong.tl

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/

-- 
Kristoffer Haugsbakk

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option
  2023-05-18 12:02                   ` [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option Teng Long
@ 2023-05-18 14:34                     ` Kristoffer Haugsbakk
  2023-05-20 10:41                       ` Teng Long
  2023-05-19  0:54                     ` Jeff King
  1 sibling, 1 reply; 186+ messages in thread
From: Kristoffer Haugsbakk @ 2023-05-18 14:34 UTC (permalink / raw)
  To: Teng Long
  Cc: Ævar Arnfjörð Bjarmason, git, Junio C Hamano,
	Eric Sunshine, tenglong.tl

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):

> 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

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v10 0/6] notes.c: introduce "--separator" option
  2023-05-18 12:02                 ` [PATCH v10 " Teng Long
                                     ` (6 preceding siblings ...)
  2023-05-18 13:56                   ` [PATCH v10 0/6] notes.c: introduce "--separator" option Kristoffer Haugsbakk
@ 2023-05-18 15:17                   ` Junio C Hamano
  2023-05-20 10:59                     ` Teng Long
  2023-05-27  7:57                   ` [PATCH v11 0/7] notes.c: introduce "--separator" Teng Long
  8 siblings, 1 reply; 186+ messages in thread
From: Junio C Hamano @ 2023-05-18 15:17 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, sunshine, tenglong.tl

Teng Long <dyroneteng@gmail.com> writes:

> 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.






^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option
  2023-05-18 12:02                   ` [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option Teng Long
  2023-05-18 14:34                     ` Kristoffer Haugsbakk
@ 2023-05-19  0:54                     ` Jeff King
  2023-05-27  7:17                       ` Teng Long
  1 sibling, 1 reply; 186+ messages in thread
From: Jeff King @ 2023-05-19  0:54 UTC (permalink / raw)
  To: Teng Long; +Cc: avarab, git, gitster, sunshine, tenglong.tl

On Thu, May 18, 2023 at 08:02:09PM +0800, Teng Long wrote:

> +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:

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.

^ permalink raw reply related	[flat|nested] 186+ messages in thread

* Re: [PATCH v10 0/6] notes.c: introduce "--separator" option
  2023-05-18 13:56                   ` [PATCH v10 0/6] notes.c: introduce "--separator" option Kristoffer Haugsbakk
@ 2023-05-20 10:22                     ` Teng Long
  0 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-05-20 10:22 UTC (permalink / raw)
  To: code; +Cc: avarab, dyroneteng, git, gitster, sunshine, tenglong.tl

"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.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option
  2023-05-18 14:34                     ` Kristoffer Haugsbakk
@ 2023-05-20 10:41                       ` Teng Long
  2023-05-20 16:12                         ` Kristoffer Haugsbakk
  0 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-05-20 10:41 UTC (permalink / raw)
  To: code; +Cc: avarab, dyroneteng, git, gitster, sunshine, tenglong.tl

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

> > 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.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v10 0/6] notes.c: introduce "--separator" option
  2023-05-18 15:17                   ` Junio C Hamano
@ 2023-05-20 10:59                     ` Teng Long
  0 siblings, 0 replies; 186+ messages in thread
From: Teng Long @ 2023-05-20 10:59 UTC (permalink / raw)
  To: gitster; +Cc: avarab, dyroneteng, git, sunshine, tenglong.tl

Junio C Hamano <gitster@pobox.com> writes:

> > 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.

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option
  2023-05-20 10:41                       ` Teng Long
@ 2023-05-20 16:12                         ` Kristoffer Haugsbakk
  0 siblings, 0 replies; 186+ messages in thread
From: Kristoffer Haugsbakk @ 2023-05-20 16:12 UTC (permalink / raw)
  To: Teng Long
  Cc: Ævar Arnfjörð Bjarmason, git, Junio C Hamano,
	Eric Sunshine, tenglong.tl

Hi Teng Long

On Sat, May 20, 2023, at 12:41, Teng Long wrote:
> 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

^ permalink raw reply	[flat|nested] 186+ messages in thread

* Re: [PATCH v10 4/6] notes.c: introduce '[--[no-]separator|--separator=<paragraph-break>]' option
  2023-05-19  0:54                     ` Jeff King
@ 2023-05-27  7:17                       ` Teng Long
  2023-05-27 17:19                         ` Jeff King
  0 siblings, 1 reply; 186+ messages in thread
From: Teng Long @ 2023-05-27  7:17 UTC (permalink / raw)
  To: peff; +Cc: avarab, dyroneteng, git, gitster, sunshine, tenglong.tl

Jeff King <peff@peff.net> writes:

> > +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.

You definitely correct, will fix.

> 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.

> 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.

> 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.

> [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/

^ permalink raw reply	[flat|nested] 186+ messages in thread

* [PATCH v11 0/7] notes.c: introduce "--separator"
  2023-05-18 12:02                 ` [PATCH v10 " Teng Long
                                     ` (7 preceding siblings ...)
  2023-05-18 15:17                   ` Junio C Hamano
@ 2023-05-27  7:57                   ` Teng Long
  2023-05-27  7:57                     ` [PATCH v11 1/7] notes.c: cleanup 'strbuf_grow' call in 'append_edit' Teng Long
                                       ` (7 more replies)
  8 siblings, 8 replies; 186+ messages in thread
From: Teng Long @ 2023-05-27  7:57 UTC (permalink / raw)
  To: dyroneteng; +Cc: avarab, git, gitster, sunshine, tenglong.tl, peff, code

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
    @@