Merge branch 'sb/notes-parse-opt'

* sb/notes-parse-opt:
  notes: rework subcommands and parse options

Conflicts:
	builtin/notes.c
This commit is contained in:
Junio C Hamano 2010-03-28 21:52:28 -07:00
commit 3b37d9c17e
2 changed files with 406 additions and 183 deletions

View file

@ -19,13 +19,54 @@
#include "string-list.h"
static const char * const git_notes_usage[] = {
"git notes [--ref <notes_ref>] [list [<object>]]",
"git notes [--ref <notes_ref>] add [-f] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
"git notes [--ref <notes_ref>] copy [-f] <from-object> <to-object>",
"git notes [--ref <notes_ref>] append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
"git notes [--ref <notes_ref>] edit [<object>]",
"git notes [--ref <notes_ref>] show [<object>]",
"git notes [--ref <notes_ref>] remove [<object>]",
"git notes [--ref <notes_ref>] prune",
NULL
};
static const char * const git_notes_list_usage[] = {
"git notes [list [<object>]]",
"git notes add [-f] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
"git notes copy [-f] <from-object> <to-object>",
"git notes append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
NULL
};
static const char * const git_notes_add_usage[] = {
"git notes add [<options>] [<object>]",
NULL
};
static const char * const git_notes_copy_usage[] = {
"git notes copy [<options>] <from-object> <to-object>",
"git notes copy --stdin [<from-object> <to-object>]...",
NULL
};
static const char * const git_notes_append_usage[] = {
"git notes append [<options>] [<object>]",
NULL
};
static const char * const git_notes_edit_usage[] = {
"git notes edit [<object>]",
NULL
};
static const char * const git_notes_show_usage[] = {
"git notes show [<object>]",
NULL
};
static const char * const git_notes_remove_usage[] = {
"git notes remove [<object>]",
NULL
};
static const char * const git_notes_prune_usage[] = {
"git notes prune",
NULL
};
@ -272,7 +313,6 @@ int commit_notes(struct notes_tree *t, const char *msg)
return 0;
}
combine_notes_fn *parse_combine_notes_fn(const char *v)
{
if (!strcasecmp(v, "overwrite"))
@ -427,22 +467,65 @@ int notes_copy_from_stdin(int force, const char *rewrite_cmd)
return ret;
}
int cmd_notes(int argc, const char **argv, const char *prefix)
static struct notes_tree *init_notes_check(const char *subcommand)
{
struct notes_tree *t;
unsigned char object[20], from_obj[20], new_note[20];
const unsigned char *note;
const char *object_ref;
char logmsg[100];
init_notes(NULL, NULL, NULL, 0);
t = &default_notes_tree;
int list = 0, add = 0, copy = 0, append = 0, edit = 0, show = 0,
remove = 0, prune = 0, force = 0, from_stdin = 0;
int given_object = 0, i = 1, retval = 0;
struct msg_arg msg = { 0, 0, STRBUF_INIT };
const char *rewrite_cmd = NULL;
const char *override_notes_ref = NULL;
if (prefixcmp(t->ref, "refs/notes/"))
die("Refusing to %s notes in %s (outside of refs/notes/)",
subcommand, t->ref);
return t;
}
static int list(int argc, const char **argv, const char *prefix)
{
struct notes_tree *t;
unsigned char object[20];
const unsigned char *note;
int retval = -1;
struct option options[] = {
OPT_END()
};
if (argc)
argc = parse_options(argc, argv, prefix, options,
git_notes_list_usage, 0);
if (1 < argc) {
error("too many parameters");
usage_with_options(git_notes_list_usage, options);
}
t = init_notes_check("list");
if (argc) {
if (get_sha1(argv[0], object))
die("Failed to resolve '%s' as a valid ref.", argv[0]);
note = get_note(t, object);
if (note) {
puts(sha1_to_hex(note));
retval = 0;
} else
retval = error("No note found for object %s.",
sha1_to_hex(object));
} else
retval = for_each_note(t, 0, list_each_note, NULL);
free_notes(t);
return retval;
}
static int add(int argc, const char **argv, const char *prefix)
{
int retval = 0, force = 0;
const char *object_ref;
struct notes_tree *t;
unsigned char object[20], new_note[20];
char logmsg[100];
const unsigned char *note;
struct msg_arg msg = { 0, 0, STRBUF_INIT };
struct option options[] = {
OPT_GROUP("Notes contents options"),
{ OPTION_CALLBACK, 'm', "message", &msg, "MSG",
"note contents as a string", PARSE_OPT_NONEG,
parse_msg_arg},
@ -455,19 +538,293 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
{ OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT",
"reuse specified note object", PARSE_OPT_NONEG,
parse_reuse_arg},
OPT_GROUP("Other options"),
OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
OPT_END()
};
argc = parse_options(argc, argv, prefix, options, git_notes_add_usage,
0);
if (1 < argc) {
error("too many parameters");
usage_with_options(git_notes_add_usage, options);
}
object_ref = argc ? argv[0] : "HEAD";
if (get_sha1(object_ref, object))
die("Failed to resolve '%s' as a valid ref.", object_ref);
t = init_notes_check("add");
note = get_note(t, object);
if (note) {
if (!force) {
retval = error("Cannot add notes. Found existing notes "
"for object %s. Use '-f' to overwrite "
"existing notes", sha1_to_hex(object));
goto out;
}
fprintf(stderr, "Overwriting existing notes for object %s\n",
sha1_to_hex(object));
}
create_note(object, &msg, 0, note, new_note);
if (is_null_sha1(new_note))
remove_note(t, object);
else
add_note(t, object, new_note, combine_notes_overwrite);
snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
is_null_sha1(new_note) ? "removed" : "added", "add");
commit_notes(t, logmsg);
out:
free_notes(t);
strbuf_release(&(msg.buf));
return retval;
}
static int copy(int argc, const char **argv, const char *prefix)
{
int retval = 0, force = 0, from_stdin = 0;
const unsigned char *from_note, *note;
const char *object_ref;
unsigned char object[20], from_obj[20];
struct notes_tree *t;
const char *rewrite_cmd = NULL;
struct option options[] = {
OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
"load rewriting config for <command> (implies "
"--stdin)"),
OPT_END()
};
argc = parse_options(argc, argv, prefix, options, git_notes_copy_usage,
0);
if (from_stdin || rewrite_cmd) {
if (argc) {
error("too many parameters");
usage_with_options(git_notes_copy_usage, options);
} else {
return notes_copy_from_stdin(force, rewrite_cmd);
}
}
if (2 < argc) {
error("too many parameters");
usage_with_options(git_notes_copy_usage, options);
}
if (get_sha1(argv[0], from_obj))
die("Failed to resolve '%s' as a valid ref.", argv[0]);
object_ref = 1 < argc ? argv[1] : "HEAD";
if (get_sha1(object_ref, object))
die("Failed to resolve '%s' as a valid ref.", object_ref);
t = init_notes_check("copy");
note = get_note(t, object);
if (note) {
if (!force) {
retval = error("Cannot copy notes. Found existing "
"notes for object %s. Use '-f' to "
"overwrite existing notes",
sha1_to_hex(object));
goto out;
}
fprintf(stderr, "Overwriting existing notes for object %s\n",
sha1_to_hex(object));
}
from_note = get_note(t, from_obj);
if (!from_note) {
retval = error("Missing notes on source object %s. Cannot "
"copy.", sha1_to_hex(from_obj));
goto out;
}
add_note(t, object, from_note, combine_notes_overwrite);
commit_notes(t, "Notes added by 'git notes copy'");
out:
free_notes(t);
return retval;
}
static int append_edit(int argc, const char **argv, const char *prefix)
{
const char *object_ref;
struct notes_tree *t;
unsigned char object[20], new_note[20];
const unsigned char *note;
char logmsg[100];
const char * const *usage;
struct msg_arg msg = { 0, 0, STRBUF_INIT };
struct option options[] = {
{ OPTION_CALLBACK, 'm', "message", &msg, "MSG",
"note contents as a string", PARSE_OPT_NONEG,
parse_msg_arg},
{ OPTION_CALLBACK, 'F', "file", &msg, "FILE",
"note contents in a file", PARSE_OPT_NONEG,
parse_file_arg},
{ OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT",
"reuse and edit specified note object", PARSE_OPT_NONEG,
parse_reedit_arg},
{ OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT",
"reuse specified note object", PARSE_OPT_NONEG,
parse_reuse_arg},
OPT_END()
};
int edit = !strcmp(argv[0], "edit");
usage = edit ? git_notes_edit_usage : git_notes_append_usage;
argc = parse_options(argc, argv, prefix, options, usage,
PARSE_OPT_KEEP_ARGV0);
if (2 < argc) {
error("too many parameters");
usage_with_options(usage, options);
}
if (msg.given && edit)
fprintf(stderr, "The -m/-F/-c/-C options have been deprecated "
"for the 'edit' subcommand.\n"
"Please use 'git notes add -f -m/-F/-c/-C' instead.\n");
object_ref = 1 < argc ? argv[1] : "HEAD";
if (get_sha1(object_ref, object))
die("Failed to resolve '%s' as a valid ref.", object_ref);
t = init_notes_check(argv[0]);
note = get_note(t, object);
create_note(object, &msg, !edit, note, new_note);
if (is_null_sha1(new_note))
remove_note(t, object);
else
add_note(t, object, new_note, combine_notes_overwrite);
snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
is_null_sha1(new_note) ? "removed" : "added", argv[0]);
commit_notes(t, logmsg);
free_notes(t);
strbuf_release(&(msg.buf));
return 0;
}
static int show(int argc, const char **argv, const char *prefix)
{
const char *object_ref;
struct notes_tree *t;
unsigned char object[20];
const unsigned char *note;
int retval;
struct option options[] = {
OPT_END()
};
argc = parse_options(argc, argv, prefix, options, git_notes_show_usage,
0);
if (1 < argc) {
error("too many parameters");
usage_with_options(git_notes_show_usage, options);
}
object_ref = argc ? argv[0] : "HEAD";
if (get_sha1(object_ref, object))
die("Failed to resolve '%s' as a valid ref.", object_ref);
t = init_notes_check("show");
note = get_note(t, object);
if (!note)
retval = error("No note found for object %s.",
sha1_to_hex(object));
else {
const char *show_args[3] = {"show", sha1_to_hex(note), NULL};
retval = execv_git_cmd(show_args);
}
free_notes(t);
return retval;
}
static int remove_cmd(int argc, const char **argv, const char *prefix)
{
struct option options[] = {
OPT_END()
};
const char *object_ref;
struct notes_tree *t;
unsigned char object[20];
argc = parse_options(argc, argv, prefix, options,
git_notes_remove_usage, 0);
if (1 < argc) {
error("too many parameters");
usage_with_options(git_notes_remove_usage, options);
}
object_ref = argc ? argv[0] : "HEAD";
if (get_sha1(object_ref, object))
die("Failed to resolve '%s' as a valid ref.", object_ref);
t = init_notes_check("remove");
fprintf(stderr, "Removing note for object %s\n", sha1_to_hex(object));
remove_note(t, object);
commit_notes(t, "Notes removed by 'git notes remove'");
free_notes(t);
return 0;
}
static int prune(int argc, const char **argv, const char *prefix)
{
struct notes_tree *t;
struct option options[] = {
OPT_END()
};
argc = parse_options(argc, argv, prefix, options, git_notes_prune_usage,
0);
if (argc) {
error("too many parameters");
usage_with_options(git_notes_prune_usage, options);
}
t = init_notes_check("prune");
prune_notes(t);
commit_notes(t, "Notes removed by 'git notes prune'");
free_notes(t);
return 0;
}
int cmd_notes(int argc, const char **argv, const char *prefix)
{
int result;
const char *override_notes_ref = NULL;
struct option options[] = {
OPT_STRING(0, "ref", &override_notes_ref, "notes_ref",
"use notes from <notes_ref>"),
OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
"load rewriting config for <command> (implies --stdin)"),
OPT_END()
};
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options, git_notes_usage, 0);
argc = parse_options(argc, argv, prefix, options, git_notes_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
if (override_notes_ref) {
struct strbuf sb = STRBUF_INIT;
@ -482,169 +839,24 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
strbuf_release(&sb);
}
if (argc && !strcmp(argv[0], "list"))
list = 1;
else if (argc && !strcmp(argv[0], "add"))
add = 1;
else if (argc && !strcmp(argv[0], "copy"))
copy = 1;
else if (argc && !strcmp(argv[0], "append"))
append = 1;
else if (argc && !strcmp(argv[0], "edit"))
edit = 1;
else if (argc && !strcmp(argv[0], "show"))
show = 1;
else if (argc && !strcmp(argv[0], "remove"))
remove = 1;
else if (argc && !strcmp(argv[0], "prune"))
prune = 1;
else if (!argc) {
list = 1; /* Default to 'list' if no other subcommand given */
i = 0;
}
if (list + add + copy + append + edit + show + remove + prune != 1)
usage_with_options(git_notes_usage, options);
if (msg.given && !(add || append || edit)) {
error("cannot use -m/-F/-c/-C options with %s subcommand.",
argv[0]);
if (argc < 1 || !strcmp(argv[0], "list"))
result = list(argc, argv, prefix);
else if (!strcmp(argv[0], "add"))
result = add(argc, argv, prefix);
else if (!strcmp(argv[0], "copy"))
result = copy(argc, argv, prefix);
else if (!strcmp(argv[0], "append") || !strcmp(argv[0], "edit"))
result = append_edit(argc, argv, prefix);
else if (!strcmp(argv[0], "show"))
result = show(argc, argv, prefix);
else if (!strcmp(argv[0], "remove"))
result = remove_cmd(argc, argv, prefix);
else if (!strcmp(argv[0], "prune"))
result = prune(argc, argv, prefix);
else {
result = error("Unknown subcommand: %s", argv[0]);
usage_with_options(git_notes_usage, options);
}
if (msg.given && edit) {
fprintf(stderr, "The -m/-F/-c/-C options have been deprecated "
"for the 'edit' subcommand.\n"
"Please use 'git notes add -f -m/-F/-c/-C' instead.\n");
}
if (force && !(add || copy)) {
error("cannot use -f option with %s subcommand.", argv[0]);
usage_with_options(git_notes_usage, options);
}
if (!copy && rewrite_cmd) {
error("cannot use --for-rewrite with %s subcommand.", argv[0]);
usage_with_options(git_notes_usage, options);
}
if (!copy && from_stdin) {
error("cannot use --stdin with %s subcommand.", argv[0]);
usage_with_options(git_notes_usage, options);
}
if (copy) {
const char *from_ref;
if (from_stdin || rewrite_cmd) {
if (argc > 1) {
error("too many parameters");
usage_with_options(git_notes_usage, options);
} else {
return notes_copy_from_stdin(force, rewrite_cmd);
}
}
if (argc < 3) {
error("too few parameters");
usage_with_options(git_notes_usage, options);
}
from_ref = argv[i++];
if (get_sha1(from_ref, from_obj))
die("Failed to resolve '%s' as a valid ref.", from_ref);
}
given_object = argc > i;
object_ref = given_object ? argv[i++] : "HEAD";
if (argc > i || (prune && given_object)) {
error("too many parameters");
usage_with_options(git_notes_usage, options);
}
if (get_sha1(object_ref, object))
die("Failed to resolve '%s' as a valid ref.", object_ref);
init_notes(NULL, NULL, NULL, 0);
t = &default_notes_tree;
if (prefixcmp(t->ref, "refs/notes/"))
die("Refusing to %s notes in %s (outside of refs/notes/)",
argv[0], t->ref);
note = get_note(t, object);
/* list command */
if (list) {
if (given_object) {
if (note) {
puts(sha1_to_hex(note));
goto end;
}
} else {
retval = for_each_note(t, 0, list_each_note, NULL);
goto end;
}
}
/* show command */
if ((list || show) && !note) {
error("No note found for object %s.", sha1_to_hex(object));
retval = 1;
goto end;
} else if (show) {
const char *show_args[3] = {"show", sha1_to_hex(note), NULL};
retval = execv_git_cmd(show_args);
goto end;
}
/* add/append/edit/remove/prune command */
if ((add || copy) && note) {
if (!force) {
error("Cannot %s notes. Found existing notes for object"
" %s. Use '-f' to overwrite existing notes",
argv[0], sha1_to_hex(object));
retval = 1;
goto end;
}
fprintf(stderr, "Overwriting existing notes for object %s\n",
sha1_to_hex(object));
}
if (remove) {
msg.given = 1;
msg.use_editor = 0;
strbuf_reset(&(msg.buf));
}
if (prune) {
hashclr(new_note);
prune_notes(t);
goto commit;
} else if (copy) {
const unsigned char *from_note = get_note(t, from_obj);
if (!from_note) {
error("Missing notes on source object %s. Cannot copy.",
sha1_to_hex(from_obj));
retval = 1;
goto end;
}
hashcpy(new_note, from_note);
} else
create_note(object, &msg, append, note, new_note);
if (is_null_sha1(new_note))
remove_note(t, object);
else
add_note(t, object, new_note, combine_notes_overwrite);
commit:
snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
is_null_sha1(new_note) ? "removed" : "added", argv[0]);
commit_notes(t, logmsg);
end:
free_notes(t);
strbuf_release(&(msg.buf));
return retval;
return result ? 1 : 0;
}

View file

@ -378,6 +378,17 @@ test_expect_success 'append to existing note with "git notes append"' '
test_cmp expect output
'
cat > expect_list << EOF
c18dc024e14f08d18d14eea0d747ff692d66d6a3 1584215f1d29c65e99c6c6848626553fdd07fd75
c9c6af7f78bc47490dbf3e822cf2f3c24d4b9061 268048bfb8a1fb38e703baceb8ab235421bf80c5
4b6ad22357cc8a1296720574b8d2fbc22fab0671 bd1753200303d0a0344be813e504253b3d98e74d
EOF
test_expect_success '"git notes list" does not expand to "git notes list HEAD"' '
git notes list > output &&
test_cmp expect_list output
'
test_expect_success 'appending empty string does not change existing note' '
git notes append -m "" &&
git notes show > output &&