@@ -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
@@ -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);
@@ -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) &&