git/list[1] front-page[2] threads[3] people[4] search[5] about
 

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

From
Junio C Hamano <gitster@pobox.com>
Date
Apr 25, 2023, 17:34 UTC
Message-ID
<xmqqsfcn27e0.fsf@gitster.g>
In-Reply-To
<be86f9cac3e651ab5aab49f89b6506012b756dab.1682429602.git.dyroneteng@gmail.com>
Teng Long <dyroneteng@gmail.com> writes:
Show 156 quoted lines
> From: Teng Long <dyroneteng@gmail.com>
>
> When adding new notes or appending to an existing notes, we will
> insert a blank line between the paragraphs, like:
>
>      $ 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);
>  }
Show 13 quoted lines
> @@ -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.

Show 16 quoted lines
> +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.

Previous: Teng LongNext: Teng Long
Message 33 of 40 in “notes.c: introduce "--separator" option”
  1. 0/3 notes.c: introduce "--separator" optionTeng Long, Feb 23, 2023
  2. 2/3 notes.c: cleanup for "designated init"Teng Long, Feb 23, 2023
  3. 1/3 notes.c: cleanup 'strbuf_grow' call in 'append_edit'Teng Long, Feb 23, 2023
  4. 3/3 notes.c: introduce '--separator=<paragraph-break>' optionTeng Long, Feb 23, 2023
  5. Junio C HamanoFeb 23, 2023
  6. Teng LongFeb 28, 2023
  7. Junio C HamanoFeb 25, 2023
  8. Teng LongFeb 28, 2023
  9. 0/3 notes.c: introduce "--separator" optionTeng Long, Mar 27, 2023
  10. 0/4 notes.c: introduce "--separator" optionTeng Long, Mar 28, 2023
  11. 1/4 notes.c: cleanup 'strbuf_grow' call in 'append_edit'Teng Long, Mar 28, 2023
  12. 2/4 notes.c: cleanup for "designated init"Teng Long, Mar 28, 2023
  13. Junio C HamanoMar 29, 2023
  14. 3/4 notes.c: introduce '--separator=<paragraph-break>' optionTeng Long, Mar 28, 2023
  15. Junio C HamanoMar 28, 2023
  16. Teng LongMar 29, 2023
  17. Junio C HamanoMar 29, 2023
  18. Teng LongApr 13, 2023
  19. 4/4 notes.c: don't do stripespace when parse file argTeng Long, Mar 28, 2023
  20. Junio C HamanoMar 28, 2023
  21. Teng LongMar 29, 2023
  22. Junio C HamanoMar 29, 2023
  23. 0/6 notes.c: introduce "--separator" optionTeng Long, Apr 25, 2023
  24. 1/6 notes.c: cleanup 'strbuf_grow' call in 'append_edit'Teng Long, Apr 25, 2023
  25. 2/6 notes.c: use designated initializers for clarityTeng Long, Apr 25, 2023
  26. 3/6 t3321: add test cases about the notes stripspace behaviorTeng Long, Apr 25, 2023
  27. Junio C HamanoApr 25, 2023
  28. Teng LongApr 27, 2023
  29. 5/6 notes.c: append separator instead of insert by posTeng Long, Apr 25, 2023
  30. Junio C HamanoApr 25, 2023
  31. Teng LongApr 27, 2023
  32. 4/6 notes.c: introduce '--separator=<paragraph-break>' optionTeng Long, Apr 25, 2023
  33. Junio C HamanoApr 25, 2023
  34. Teng LongApr 27, 2023
  35. Junio C HamanoApr 27, 2023
  36. Junio C HamanoApr 25, 2023
  37. 6/6 notes.c: introduce "--[no-]stripspace" optionTeng Long, Apr 25, 2023
  38. Junio C HamanoApr 25, 2023
  39. Teng LongApr 28, 2023
  40. Junio C HamanoApr 28, 2023

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.