Skip to content

Commit

Permalink
notes.c: introduce '--separator=<paragraph-break>' option
Browse files Browse the repository at this point in the history
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>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
dyrone authored and gitster committed Jun 21, 2023
1 parent 5958704 commit 90bc19b
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 29 deletions.
21 changes: 16 additions & 5 deletions Documentation/git-notes.txt
Expand Up @@ -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>
Expand Down Expand Up @@ -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
Expand All @@ -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).
Expand Down Expand Up @@ -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
Expand Down
109 changes: 85 additions & 24 deletions builtin/notes.c
Expand Up @@ -8,6 +8,7 @@
*/

#include "cache.h"
#include "alloc.h"
#include "config.h"
#include "builtin.h"
#include "gettext.h"
Expand All @@ -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>"),
Expand Down Expand Up @@ -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)
Expand All @@ -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,
Expand Down Expand Up @@ -213,65 +229,97 @@ static void write_note_data(struct note_data *d, struct object_id *oid)
}
}

static void insert_separator(struct strbuf *message, size_t pos)
{
size_t sep_len = strlen(separator);
if (sep_len && separator[sep_len - 1] == '\n')
strbuf_insertstr(message, pos, separator);
else
strbuf_insertf(message, pos, "%s%s", separator, "\n");
}

static void concat_messages(struct note_data *d)
{
struct strbuf msg = STRBUF_INIT;

size_t i;
for (i = 0; i < d->msg_nr ; i++) {
if (d->buf.len)
insert_separator(&d->buf, d->buf.len);
strbuf_add(&msg, d->messages[i]->buf.buf, d->messages[i]->buf.len);
strbuf_addbuf(&d->buf, &msg);
if (d->messages[i]->stripspace)
strbuf_stripspace(&d->buf, 0);
strbuf_reset(&msg);
}
strbuf_release(&msg);
}

static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
{
struct note_data *d = opt->value;
struct note_msg *msg = xmalloc(sizeof(*msg));

BUG_ON_OPT_NEG(unset);

if (d->buf.len)
strbuf_addch(&d->buf, '\n');
strbuf_addstr(&d->buf, arg);
strbuf_stripspace(&d->buf, 0);

d->given = 1;
strbuf_init(&msg->buf, strlen(arg));
strbuf_addstr(&msg->buf, arg);
ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
d->messages[d->msg_nr - 1] = msg;
msg->stripspace = 1;
return 0;
}

static int parse_file_arg(const struct option *opt, const char *arg, int unset)
{
struct note_data *d = opt->value;
struct note_msg *msg = xmalloc(sizeof(*msg));

BUG_ON_OPT_NEG(unset);

if (d->buf.len)
strbuf_addch(&d->buf, '\n');
strbuf_init(&msg->buf , 0);
if (!strcmp(arg, "-")) {
if (strbuf_read(&d->buf, 0, 1024) < 0)
if (strbuf_read(&msg->buf, 0, 1024) < 0)
die_errno(_("cannot read '%s'"), arg);
} else if (strbuf_read_file(&d->buf, arg, 1024) < 0)
} else if (strbuf_read_file(&msg->buf, arg, 1024) < 0)
die_errno(_("could not open or read '%s'"), arg);
strbuf_stripspace(&d->buf, 0);

d->given = 1;
ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
d->messages[d->msg_nr - 1] = msg;
msg->stripspace = 1;
return 0;
}

static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)
{
struct note_data *d = opt->value;
char *buf;
struct note_msg *msg = xmalloc(sizeof(*msg));
char *value;
struct object_id object;
enum object_type type;
unsigned long len;

BUG_ON_OPT_NEG(unset);

if (d->buf.len)
strbuf_addch(&d->buf, '\n');

strbuf_init(&msg->buf, 0);
if (repo_get_oid(the_repository, arg, &object))
die(_("failed to resolve '%s' as a valid ref."), arg);
if (!(buf = repo_read_object_file(the_repository, &object, &type, &len)))
if (!(value = repo_read_object_file(the_repository, &object, &type, &len)))
die(_("failed to read object '%s'."), arg);
if (type != OBJ_BLOB) {
free(buf);
strbuf_release(&msg->buf);
free(value);
free(msg);
die(_("cannot read note data from non-blob object '%s'."), arg);
}
strbuf_add(&d->buf, buf, len);
free(buf);

d->given = 1;
strbuf_add(&msg->buf, value, len);
free(value);

msg->buf.len = len;
ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
d->messages[d->msg_nr - 1] = msg;
msg->stripspace = 0;
return 0;
}

Expand Down Expand Up @@ -406,6 +454,7 @@ static int add(int argc, const char **argv, const char *prefix)
struct object_id object, new_note;
const struct object_id *note;
struct note_data d = { .buf = STRBUF_INIT };

struct option options[] = {
OPT_CALLBACK_F('m', "message", &d, N_("message"),
N_("note contents as a string"), PARSE_OPT_NONEG,
Expand All @@ -422,6 +471,8 @@ static int add(int argc, const char **argv, const char *prefix)
OPT_BOOL(0, "allow-empty", &allow_empty,
N_("allow storing empty note")),
OPT__FORCE(&force, N_("replace existing notes"), PARSE_OPT_NOCOMPLETE),
OPT_STRING(0, "separator", &separator, N_("separator"),
N_("insert <paragraph-break> between paragraphs")),
OPT_END()
};

Expand All @@ -433,6 +484,10 @@ static int add(int argc, const char **argv, const char *prefix)
usage_with_options(git_notes_add_usage, options);
}

if (d.msg_nr)
concat_messages(&d);
d.given = !!d.buf.len;

object_ref = argc > 1 ? argv[1] : "HEAD";

if (repo_get_oid(the_repository, object_ref, &object))
Expand Down Expand Up @@ -587,6 +642,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)
parse_reuse_arg),
OPT_BOOL(0, "allow-empty", &allow_empty,
N_("allow storing empty note")),
OPT_STRING(0, "separator", &separator, N_("separator"),
N_("insert <paragraph-break> between paragraphs")),
OPT_END()
};
int edit = !strcmp(argv[0], "edit");
Expand All @@ -600,6 +657,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)
usage_with_options(usage, options);
}

if (d.msg_nr)
concat_messages(&d);
d.given = !!d.buf.len;

if (d.given && edit)
fprintf(stderr, _("The -m/-F/-c/-C options have been deprecated "
"for the 'edit' subcommand.\n"
Expand All @@ -623,7 +684,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
&type, &size);

if (d.buf.len && prev_buf && size)
strbuf_insertstr(&d.buf, 0, "\n");
insert_separator(&d.buf, 0);
if (prev_buf && size)
strbuf_insert(&d.buf, 0, prev_buf, size);
free(prev_buf);
Expand Down

0 comments on commit 90bc19b

Please sign in to comment.