Skip to content

Commit a9cc3b8

Browse files
committed
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'
2 parents 5a1d9e8 + 3d6a316 commit a9cc3b8

File tree

4 files changed

+906
-42
lines changed

4 files changed

+906
-42
lines changed

Documentation/git-notes.txt

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ SYNOPSIS
99
--------
1010
[verse]
1111
'git notes' [list [<object>]]
12-
'git notes' add [-f] [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
12+
'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
1313
'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )
14-
'git notes' append [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
15-
'git notes' edit [--allow-empty] [<object>]
14+
'git notes' append [--allow-empty] [--[no-]separator | --separator=<paragraph-break>] [--[no-]stripspace] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
15+
'git notes' edit [--allow-empty] [<object>] [--[no-]stripspace]
1616
'git notes' show [<object>]
1717
'git notes' merge [-v | -q] [-s <strategy> ] <notes-ref>
1818
'git notes' merge --commit [-v | -q]
@@ -65,7 +65,9 @@ add::
6565
However, if you're using `add` interactively (using an editor
6666
to supply the notes contents), then - instead of aborting -
6767
the existing notes will be opened in the editor (like the `edit`
68-
subcommand).
68+
subcommand). If you specify multiple `-m` and `-F`, a blank
69+
line will be inserted between the messages. Use the `--separator`
70+
option to insert other delimiters.
6971

7072
copy::
7173
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
8587
the command can read the input given to the `post-rewrite` hook.)
8688

8789
append::
88-
Append to the notes of an existing object (defaults to HEAD).
89-
Creates a new notes object if needed.
90+
Append new message(s) given by `-m` or `-F` options to an
91+
existing note, or add them as a new note if one does not
92+
exist, for the object (defaults to HEAD). When appending to
93+
an existing note, a blank line is added before each new
94+
message as an inter-paragraph separator. The separator can
95+
be customized with the `--separator` option.
9096

9197
edit::
9298
Edit the notes for a given object (defaults to HEAD).
@@ -135,20 +141,26 @@ OPTIONS
135141
If multiple `-m` options are given, their values
136142
are concatenated as separate paragraphs.
137143
Lines starting with `#` and empty lines other than a
138-
single line between paragraphs will be stripped out.
144+
single line between paragraphs will be stripped out,
145+
if you wish to keep them verbatim, use `--no-stripspace`.
139146

140147
-F <file>::
141148
--file=<file>::
142149
Take the note message from the given file. Use '-' to
143150
read the note message from the standard input.
144151
Lines starting with `#` and empty lines other than a
145-
single line between paragraphs will be stripped out.
152+
single line between paragraphs will be stripped out,
153+
if you wish to keep them verbatim, use with
154+
`--no-stripspace` option.
146155

147156
-C <object>::
148157
--reuse-message=<object>::
149158
Take the given blob object (for example, another note) as the
150159
note message. (Use `git notes copy <object>` instead to
151-
copy notes between objects.)
160+
copy notes between objects.). By default, message will be
161+
copied verbatim, but if you wish to strip out the lines
162+
starting with `#` and empty lines other than a single line
163+
between paragraphs, use with`--stripspace` option.
152164

153165
-c <object>::
154166
--reedit-message=<object>::
@@ -159,6 +171,19 @@ OPTIONS
159171
Allow an empty note object to be stored. The default behavior is
160172
to automatically remove empty notes.
161173

174+
--[no-]separator, --separator=<paragraph-break>::
175+
Specify a string used as a custom inter-paragraph separator
176+
(a newline is added at the end as needed). If `--no-separator`, no
177+
separators will be added between paragraphs. Defaults to a blank
178+
line.
179+
180+
--[no-]stripspace::
181+
Strip leading and trailing whitespace from the note message.
182+
Also strip out empty lines other than a single line between
183+
paragraphs. For lines starting with `#` will be stripped out
184+
in non-editor cases like "-m", "-F" and "-C", but not in
185+
editor case like "git notes edit", "-c", etc.
186+
162187
--ref <ref>::
163188
Manipulate the notes tree in <ref>. This overrides
164189
`GIT_NOTES_REF` and the "core.notesRef" configuration. The ref

builtin/notes.c

Lines changed: 126 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include "builtin.h"
1111
#include "config.h"
12+
#include "alloc.h"
1213
#include "editor.h"
1314
#include "environment.h"
1415
#include "gettext.h"
@@ -30,11 +31,12 @@
3031
#include "worktree.h"
3132
#include "write-or-die.h"
3233

34+
static const char *separator = "\n";
3335
static const char * const git_notes_usage[] = {
3436
N_("git notes [--ref <notes-ref>] [list [<object>]]"),
35-
N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
37+
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>]"),
3638
N_("git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>"),
37-
N_("git notes [--ref <notes-ref>] append [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
39+
N_("git notes [--ref <notes-ref>] append [--allow-empty] [--[no-]separator|--separator=<paragraph-break>] [--[no-]stripspace] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
3840
N_("git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]"),
3941
N_("git notes [--ref <notes-ref>] show [<object>]"),
4042
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[] = {
102104
static const char note_template[] =
103105
N_("Write/edit the notes for the following object:");
104106

107+
enum notes_stripspace {
108+
UNSPECIFIED = -1,
109+
NO_STRIPSPACE = 0,
110+
STRIPSPACE = 1,
111+
};
112+
113+
struct note_msg {
114+
enum notes_stripspace stripspace;
115+
struct strbuf buf;
116+
};
117+
105118
struct note_data {
106119
int given;
107120
int use_editor;
121+
int stripspace;
108122
char *edit_path;
109123
struct strbuf buf;
124+
struct note_msg **messages;
125+
size_t msg_nr;
126+
size_t msg_alloc;
110127
};
111128

112129
static void free_note_data(struct note_data *d)
@@ -116,6 +133,12 @@ static void free_note_data(struct note_data *d)
116133
free(d->edit_path);
117134
}
118135
strbuf_release(&d->buf);
136+
137+
while (d->msg_nr--) {
138+
strbuf_release(&d->messages[d->msg_nr]->buf);
139+
free(d->messages[d->msg_nr]);
140+
}
141+
free(d->messages);
119142
}
120143

121144
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 *
201224
if (launch_editor(d->edit_path, &d->buf, NULL)) {
202225
die(_("please supply the note contents using either -m or -F option"));
203226
}
204-
strbuf_stripspace(&d->buf, comment_line_char);
227+
if (d->stripspace)
228+
strbuf_stripspace(&d->buf, comment_line_char);
205229
}
206230
}
207231

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

244+
static void append_separator(struct strbuf *message)
245+
{
246+
size_t sep_len = 0;
247+
248+
if (!separator)
249+
return;
250+
else if ((sep_len = strlen(separator)) && separator[sep_len - 1] == '\n')
251+
strbuf_addstr(message, separator);
252+
else
253+
strbuf_addf(message, "%s%s", separator, "\n");
254+
}
255+
256+
static void concat_messages(struct note_data *d)
257+
{
258+
struct strbuf msg = STRBUF_INIT;
259+
size_t i;
260+
261+
for (i = 0; i < d->msg_nr ; i++) {
262+
if (d->buf.len)
263+
append_separator(&d->buf);
264+
strbuf_add(&msg, d->messages[i]->buf.buf, d->messages[i]->buf.len);
265+
strbuf_addbuf(&d->buf, &msg);
266+
if ((d->stripspace == UNSPECIFIED &&
267+
d->messages[i]->stripspace == STRIPSPACE) ||
268+
d->stripspace == STRIPSPACE)
269+
strbuf_stripspace(&d->buf, 0);
270+
strbuf_reset(&msg);
271+
}
272+
strbuf_release(&msg);
273+
}
274+
220275
static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
221276
{
222277
struct note_data *d = opt->value;
278+
struct note_msg *msg = xmalloc(sizeof(*msg));
223279

224280
BUG_ON_OPT_NEG(unset);
225281

226-
strbuf_grow(&d->buf, strlen(arg) + 2);
227-
if (d->buf.len)
228-
strbuf_addch(&d->buf, '\n');
229-
strbuf_addstr(&d->buf, arg);
230-
strbuf_stripspace(&d->buf, '\0');
231-
232-
d->given = 1;
282+
strbuf_init(&msg->buf, strlen(arg));
283+
strbuf_addstr(&msg->buf, arg);
284+
ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
285+
d->messages[d->msg_nr - 1] = msg;
286+
msg->stripspace = STRIPSPACE;
233287
return 0;
234288
}
235289

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

240295
BUG_ON_OPT_NEG(unset);
241296

242-
if (d->buf.len)
243-
strbuf_addch(&d->buf, '\n');
297+
strbuf_init(&msg->buf , 0);
244298
if (!strcmp(arg, "-")) {
245-
if (strbuf_read(&d->buf, 0, 1024) < 0)
299+
if (strbuf_read(&msg->buf, 0, 1024) < 0)
246300
die_errno(_("cannot read '%s'"), arg);
247-
} else if (strbuf_read_file(&d->buf, arg, 1024) < 0)
301+
} else if (strbuf_read_file(&msg->buf, arg, 1024) < 0)
248302
die_errno(_("could not open or read '%s'"), arg);
249-
strbuf_stripspace(&d->buf, '\0');
250303

251-
d->given = 1;
304+
ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
305+
d->messages[d->msg_nr - 1] = msg;
306+
msg->stripspace = STRIPSPACE;
252307
return 0;
253308
}
254309

255310
static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)
256311
{
257312
struct note_data *d = opt->value;
258-
char *buf;
313+
struct note_msg *msg = xmalloc(sizeof(*msg));
314+
char *value;
259315
struct object_id object;
260316
enum object_type type;
261317
unsigned long len;
262318

263319
BUG_ON_OPT_NEG(unset);
264320

265-
if (d->buf.len)
266-
strbuf_addch(&d->buf, '\n');
267-
321+
strbuf_init(&msg->buf, 0);
268322
if (repo_get_oid(the_repository, arg, &object))
269323
die(_("failed to resolve '%s' as a valid ref."), arg);
270-
if (!(buf = repo_read_object_file(the_repository, &object, &type, &len)))
324+
if (!(value = repo_read_object_file(the_repository, &object, &type, &len)))
271325
die(_("failed to read object '%s'."), arg);
272326
if (type != OBJ_BLOB) {
273-
free(buf);
327+
strbuf_release(&msg->buf);
328+
free(value);
329+
free(msg);
274330
die(_("cannot read note data from non-blob object '%s'."), arg);
275331
}
276-
strbuf_add(&d->buf, buf, len);
277-
free(buf);
278332

279-
d->given = 1;
333+
strbuf_add(&msg->buf, value, len);
334+
free(value);
335+
336+
msg->buf.len = len;
337+
ALLOC_GROW_BY(d->messages, d->msg_nr, 1, d->msg_alloc);
338+
d->messages[d->msg_nr - 1] = msg;
339+
msg->stripspace = NO_STRIPSPACE;
280340
return 0;
281341
}
282342

@@ -288,6 +348,16 @@ static int parse_reedit_arg(const struct option *opt, const char *arg, int unset
288348
return parse_reuse_arg(opt, arg, unset);
289349
}
290350

351+
static int parse_separator_arg(const struct option *opt, const char *arg,
352+
int unset)
353+
{
354+
if (unset)
355+
*(const char **)opt->value = NULL;
356+
else
357+
*(const char **)opt->value = arg ? arg : "\n";
358+
return 0;
359+
}
360+
291361
static int notes_copy_from_stdin(int force, const char *rewrite_cmd)
292362
{
293363
struct strbuf buf = STRBUF_INIT;
@@ -410,7 +480,8 @@ static int add(int argc, const char **argv, const char *prefix)
410480
struct notes_tree *t;
411481
struct object_id object, new_note;
412482
const struct object_id *note;
413-
struct note_data d = { 0, 0, NULL, STRBUF_INIT };
483+
struct note_data d = { .buf = STRBUF_INIT, .stripspace = UNSPECIFIED };
484+
414485
struct option options[] = {
415486
OPT_CALLBACK_F('m', "message", &d, N_("message"),
416487
N_("note contents as a string"), PARSE_OPT_NONEG,
@@ -427,6 +498,12 @@ static int add(int argc, const char **argv, const char *prefix)
427498
OPT_BOOL(0, "allow-empty", &allow_empty,
428499
N_("allow storing empty note")),
429500
OPT__FORCE(&force, N_("replace existing notes"), PARSE_OPT_NOCOMPLETE),
501+
OPT_CALLBACK_F(0, "separator", &separator,
502+
N_("<paragraph-break>"),
503+
N_("insert <paragraph-break> between paragraphs"),
504+
PARSE_OPT_OPTARG, parse_separator_arg),
505+
OPT_BOOL(0, "stripspace", &d.stripspace,
506+
N_("remove unnecessary whitespace")),
430507
OPT_END()
431508
};
432509

@@ -438,6 +515,10 @@ static int add(int argc, const char **argv, const char *prefix)
438515
usage_with_options(git_notes_add_usage, options);
439516
}
440517

518+
if (d.msg_nr)
519+
concat_messages(&d);
520+
d.given = !!d.buf.len;
521+
441522
object_ref = argc > 1 ? argv[1] : "HEAD";
442523

443524
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)
576657
const struct object_id *note;
577658
char *logmsg;
578659
const char * const *usage;
579-
struct note_data d = { 0, 0, NULL, STRBUF_INIT };
660+
struct note_data d = { .buf = STRBUF_INIT, .stripspace = UNSPECIFIED };
580661
struct option options[] = {
581662
OPT_CALLBACK_F('m', "message", &d, N_("message"),
582663
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)
592673
parse_reuse_arg),
593674
OPT_BOOL(0, "allow-empty", &allow_empty,
594675
N_("allow storing empty note")),
676+
OPT_CALLBACK_F(0, "separator", &separator,
677+
N_("<paragraph-break>"),
678+
N_("insert <paragraph-break> between paragraphs"),
679+
PARSE_OPT_OPTARG, parse_separator_arg),
680+
OPT_BOOL(0, "stripspace", &d.stripspace,
681+
N_("remove unnecessary whitespace")),
595682
OPT_END()
596683
};
597684
int edit = !strcmp(argv[0], "edit");
@@ -605,6 +692,10 @@ static int append_edit(int argc, const char **argv, const char *prefix)
605692
usage_with_options(usage, options);
606693
}
607694

695+
if (d.msg_nr)
696+
concat_messages(&d);
697+
d.given = !!d.buf.len;
698+
608699
if (d.given && edit)
609700
fprintf(stderr, _("The -m/-F/-c/-C options have been deprecated "
610701
"for the 'edit' subcommand.\n"
@@ -624,15 +715,17 @@ static int append_edit(int argc, const char **argv, const char *prefix)
624715
/* Append buf to previous note contents */
625716
unsigned long size;
626717
enum object_type type;
627-
char *prev_buf = repo_read_object_file(the_repository, note,
628-
&type, &size);
718+
struct strbuf buf = STRBUF_INIT;
719+
char *prev_buf = repo_read_object_file(the_repository, note, &type, &size);
629720

630-
strbuf_grow(&d.buf, size + 1);
631-
if (d.buf.len && prev_buf && size)
632-
strbuf_insertstr(&d.buf, 0, "\n");
633721
if (prev_buf && size)
634-
strbuf_insert(&d.buf, 0, prev_buf, size);
722+
strbuf_add(&buf, prev_buf, size);
723+
if (d.buf.len && prev_buf && size)
724+
append_separator(&buf);
725+
strbuf_insert(&d.buf, 0, buf.buf, buf.len);
726+
635727
free(prev_buf);
728+
strbuf_release(&buf);
636729
}
637730

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

0 commit comments

Comments
 (0)