Merge branch 'tl/notes-separator'

'git notes append' was taught '--separator' to specify string to insert
between paragraphs.

* tl/notes-separator:
  notes: introduce "--no-separator" option
  notes.c: introduce "--[no-]stripspace" option
  notes.c: append separator instead of insert by pos
  notes.c: introduce '--separator=<paragraph-break>' option
  t3321: add test cases about the notes stripspace behavior
  notes.c: use designated initializers for clarity
  notes.c: cleanup 'strbuf_grow' call in 'append_edit'
maint
Junio C Hamano 2023-07-06 11:54:47 -07:00
commit a9cc3b8fc7
4 changed files with 906 additions and 42 deletions

View File

@ -9,10 +9,10 @@ SYNOPSIS
-------- --------
[verse] [verse]
'git notes' [list [<object>]] '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>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
'git notes' copy [-f] ( --stdin | <from-object> [<to-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>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
'git notes' edit [--allow-empty] [<object>] 'git notes' edit [--allow-empty] [<object>] [--[no-]stripspace]
'git notes' show [<object>] 'git notes' show [<object>]
'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref> 'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>
'git notes' merge --commit [-v | -q] 'git notes' merge --commit [-v | -q]
@ -65,7 +65,9 @@ add::
However, if you're using `add` interactively (using an editor However, if you're using `add` interactively (using an editor
to supply the notes contents), then - instead of aborting - to supply the notes contents), then - instead of aborting -
the existing notes will be opened in the editor (like the `edit` 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::
Copy the notes for the first object onto the second object (defaults to 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.) the command can read the input given to the `post-rewrite` hook.)


append:: append::
Append to the notes of an existing object (defaults to HEAD). Append new message(s) given by `-m` or `-F` options to an
Creates a new notes object if needed. 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::
Edit the notes for a given object (defaults to HEAD). Edit the notes for a given object (defaults to HEAD).
@ -135,20 +141,26 @@ OPTIONS
If multiple `-m` options are given, their values If multiple `-m` options are given, their values
are concatenated as separate paragraphs. are concatenated as separate paragraphs.
Lines starting with `#` and empty lines other than a 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>:: -F <file>::
--file=<file>:: --file=<file>::
Take the note message from the given file. Use '-' to Take the note message from the given file. Use '-' to
read the note message from the standard input. read the note message from the standard input.
Lines starting with `#` and empty lines other than a 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>:: -C <object>::
--reuse-message=<object>:: --reuse-message=<object>::
Take the given blob object (for example, another note) as the Take the given blob object (for example, another note) as the
note message. (Use `git notes copy <object>` instead to 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>:: -c <object>::
--reedit-message=<object>:: --reedit-message=<object>::
@ -159,6 +171,19 @@ OPTIONS
Allow an empty note object to be stored. The default behavior is Allow an empty note object to be stored. The default behavior is
to automatically remove empty notes. 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.

--[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>:: --ref <ref>::
Manipulate the notes tree in <ref>. This overrides Manipulate the notes tree in <ref>. This overrides
`GIT_NOTES_REF` and the "core.notesRef" configuration. The ref `GIT_NOTES_REF` and the "core.notesRef" configuration. The ref

View File

@ -9,6 +9,7 @@


#include "builtin.h" #include "builtin.h"
#include "config.h" #include "config.h"
#include "alloc.h"
#include "editor.h" #include "editor.h"
#include "environment.h" #include "environment.h"
#include "gettext.h" #include "gettext.h"
@ -30,11 +31,12 @@
#include "worktree.h" #include "worktree.h"
#include "write-or-die.h" #include "write-or-die.h"


static const char *separator = "\n";
static const char * const git_notes_usage[] = { static const char * const git_notes_usage[] = {
N_("git notes [--ref <notes-ref>] [list [<object>]]"), 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>] [--[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>] 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>] [--[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>] edit [--allow-empty] [<object>]"),
N_("git notes [--ref <notes-ref>] show [<object>]"), N_("git notes [--ref <notes-ref>] show [<object>]"),
N_("git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>"), N_("git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>"),
@ -102,11 +104,26 @@ static const char * const git_notes_get_ref_usage[] = {
static const char note_template[] = static const char note_template[] =
N_("Write/edit the notes for the following object:"); N_("Write/edit the notes for the following object:");


enum notes_stripspace {
UNSPECIFIED = -1,
NO_STRIPSPACE = 0,
STRIPSPACE = 1,
};

struct note_msg {
enum notes_stripspace stripspace;
struct strbuf buf;
};

struct note_data { struct note_data {
int given; int given;
int use_editor; int use_editor;
int stripspace;
char *edit_path; char *edit_path;
struct strbuf buf; struct strbuf buf;
struct note_msg **messages;
size_t msg_nr;
size_t msg_alloc;
}; };


static void free_note_data(struct note_data *d) static void free_note_data(struct note_data *d)
@ -116,6 +133,12 @@ static void free_note_data(struct note_data *d)
free(d->edit_path); free(d->edit_path);
} }
strbuf_release(&d->buf); 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, static int list_each_note(const struct object_id *object_oid,
@ -201,7 +224,8 @@ static void prepare_note_data(const struct object_id *object, struct note_data *
if (launch_editor(d->edit_path, &d->buf, NULL)) { if (launch_editor(d->edit_path, &d->buf, NULL)) {
die(_("please supply the note contents using either -m or -F option")); die(_("please supply the note contents using either -m or -F option"));
} }
strbuf_stripspace(&d->buf, comment_line_char); if (d->stripspace)
strbuf_stripspace(&d->buf, comment_line_char);
} }
} }


@ -217,66 +241,102 @@ static void write_note_data(struct note_data *d, struct object_id *oid)
} }
} }


static void append_separator(struct strbuf *message)
{
size_t sep_len = 0;

if (!separator)
return;
else if ((sep_len = strlen(separator)) && separator[sep_len - 1] == '\n')
strbuf_addstr(message, separator);
else
strbuf_addf(message, "%s%s", separator, "\n");
}

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);
strbuf_add(&msg, d->messages[i]->buf.buf, d->messages[i]->buf.len);
strbuf_addbuf(&d->buf, &msg);
if ((d->stripspace == UNSPECIFIED &&
d->messages[i]->stripspace == STRIPSPACE) ||
d->stripspace == 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) static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
{ {
struct note_data *d = opt->value; struct note_data *d = opt->value;
struct note_msg *msg = xmalloc(sizeof(*msg));


BUG_ON_OPT_NEG(unset); BUG_ON_OPT_NEG(unset);


strbuf_grow(&d->buf, strlen(arg) + 2); strbuf_init(&msg->buf, strlen(arg));
if (d->buf.len) strbuf_addstr(&msg->buf, arg);
strbuf_addch(&d->buf, '\n'); ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
strbuf_addstr(&d->buf, arg); d->messages[d->msg_nr - 1] = msg;
strbuf_stripspace(&d->buf, '\0'); msg->stripspace = STRIPSPACE;

d->given = 1;
return 0; return 0;
} }


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


BUG_ON_OPT_NEG(unset); BUG_ON_OPT_NEG(unset);


if (d->buf.len) strbuf_init(&msg->buf , 0);
strbuf_addch(&d->buf, '\n');
if (!strcmp(arg, "-")) { 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); 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); 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 = STRIPSPACE;
return 0; return 0;
} }


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


BUG_ON_OPT_NEG(unset); BUG_ON_OPT_NEG(unset);


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

if (repo_get_oid(the_repository, arg, &object)) if (repo_get_oid(the_repository, arg, &object))
die(_("failed to resolve '%s' as a valid ref."), arg); 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); die(_("failed to read object '%s'."), arg);
if (type != OBJ_BLOB) { 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); 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 = NO_STRIPSPACE;
return 0; return 0;
} }


@ -288,6 +348,16 @@ static int parse_reedit_arg(const struct option *opt, const char *arg, int unset
return parse_reuse_arg(opt, arg, 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) static int notes_copy_from_stdin(int force, const char *rewrite_cmd)
{ {
struct strbuf buf = STRBUF_INIT; struct strbuf buf = STRBUF_INIT;
@ -410,7 +480,8 @@ static int add(int argc, const char **argv, const char *prefix)
struct notes_tree *t; struct notes_tree *t;
struct object_id object, new_note; struct object_id object, new_note;
const struct object_id *note; const struct object_id *note;
struct note_data d = { 0, 0, NULL, STRBUF_INIT }; struct note_data d = { .buf = STRBUF_INIT, .stripspace = UNSPECIFIED };

struct option options[] = { struct option options[] = {
OPT_CALLBACK_F('m', "message", &d, N_("message"), OPT_CALLBACK_F('m', "message", &d, N_("message"),
N_("note contents as a string"), PARSE_OPT_NONEG, N_("note contents as a string"), PARSE_OPT_NONEG,
@ -427,6 +498,12 @@ static int add(int argc, const char **argv, const char *prefix)
OPT_BOOL(0, "allow-empty", &allow_empty, OPT_BOOL(0, "allow-empty", &allow_empty,
N_("allow storing empty note")), N_("allow storing empty note")),
OPT__FORCE(&force, N_("replace existing notes"), PARSE_OPT_NOCOMPLETE), 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_BOOL(0, "stripspace", &d.stripspace,
N_("remove unnecessary whitespace")),
OPT_END() OPT_END()
}; };


@ -438,6 +515,10 @@ static int add(int argc, const char **argv, const char *prefix)
usage_with_options(git_notes_add_usage, options); 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"; object_ref = argc > 1 ? argv[1] : "HEAD";


if (repo_get_oid(the_repository, object_ref, &object)) if (repo_get_oid(the_repository, object_ref, &object))
@ -576,7 +657,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
const struct object_id *note; const struct object_id *note;
char *logmsg; char *logmsg;
const char * const *usage; const char * const *usage;
struct note_data d = { 0, 0, NULL, STRBUF_INIT }; struct note_data d = { .buf = STRBUF_INIT, .stripspace = UNSPECIFIED };
struct option options[] = { struct option options[] = {
OPT_CALLBACK_F('m', "message", &d, N_("message"), OPT_CALLBACK_F('m', "message", &d, N_("message"),
N_("note contents as a string"), PARSE_OPT_NONEG, N_("note contents as a string"), PARSE_OPT_NONEG,
@ -592,6 +673,12 @@ static int append_edit(int argc, const char **argv, const char *prefix)
parse_reuse_arg), parse_reuse_arg),
OPT_BOOL(0, "allow-empty", &allow_empty, OPT_BOOL(0, "allow-empty", &allow_empty,
N_("allow storing empty note")), 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_BOOL(0, "stripspace", &d.stripspace,
N_("remove unnecessary whitespace")),
OPT_END() OPT_END()
}; };
int edit = !strcmp(argv[0], "edit"); int edit = !strcmp(argv[0], "edit");
@ -605,6 +692,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)
usage_with_options(usage, options); usage_with_options(usage, options);
} }


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

if (d.given && edit) if (d.given && edit)
fprintf(stderr, _("The -m/-F/-c/-C options have been deprecated " fprintf(stderr, _("The -m/-F/-c/-C options have been deprecated "
"for the 'edit' subcommand.\n" "for the 'edit' subcommand.\n"
@ -624,15 +715,17 @@ static int append_edit(int argc, const char **argv, const char *prefix)
/* Append buf to previous note contents */ /* Append buf to previous note contents */
unsigned long size; unsigned long size;
enum object_type type; enum object_type type;
char *prev_buf = repo_read_object_file(the_repository, note, struct strbuf buf = STRBUF_INIT;
&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)
strbuf_insertstr(&d.buf, 0, "\n");
if (prev_buf && size) 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); free(prev_buf);
strbuf_release(&buf);
} }


if (d.buf.len || allow_empty) { if (d.buf.len || allow_empty) {

View File

@ -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_expect_success 'create note with combination of -m and -F' '
test_when_finished git notes remove HEAD &&
cat >expect-combine_m_and_F <<-EOF && cat >expect-combine_m_and_F <<-EOF &&
foo 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_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"' ' test_expect_success 'remove note with "git notes remove"' '
git notes remove HEAD^ && git notes remove HEAD^ &&
git notes remove && git notes remove &&
@ -521,6 +557,112 @@ test_expect_success 'listing non-existing notes fails' '
test_must_be_empty actual 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"' ' test_expect_success 'append to existing note with "git notes append"' '
cat >expect <<-EOF && cat >expect <<-EOF &&
Initial set of notes 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_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_expect_success 'create note from other note with "git notes add -c"' '
test_commit 9th && test_commit 9th &&
commit=$(git rev-parse HEAD) && commit=$(git rev-parse HEAD) &&

577
t/t3321-notes-stripspace.sh Executable file
View File

@ -0,0 +1,577 @@
#!/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", "--stripspace" is the default behavior' '
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 &&
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 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

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 &&
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" &&
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 '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}" \
-m "${MULTI_LF}" \
-m "${LF}" >actual 2>&1 &&
test_i18ngrep "Removing note for object" actual
'

test_expect_success 'add note by specifying "-C", "--no-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 &&
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 'reuse with "-C" and add note with "-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 note with "-m" and reuse note with "-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_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