{"thread":{"id":"22815","subject":"[RFC/PATCH] notes: rework subcommands and parse options","startedAt":"2010-02-25T08:24:21Z","lastAt":"2010-02-27T08:59:22Z","messageCount":4,"participants":["Stephen Boyd","Tim Henigan","Johan Herland"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"135659","messageId":"1267086261-7675-1-git-send-email-bebarino@gmail.com","threadId":"22815","inReplyTo":null,"subject":"[RFC/PATCH] notes: rework subcommands and parse options","fromName":"Stephen Boyd","fromEmail":"bebarino@gmail.com","sentAt":"2010-02-25T08:24:21Z","receivedAt":"2010-02-25T08:24:21Z","isPatch":true,"sender":{"key":"bebarino@gmail.com","avatar":"https://avatars.githubusercontent.com/u/38832?v=4"},"body":"Running 'git notes copy -h' is not very helfpul right now. It lists\nthe options for all the git notes subcommands and is rather confusing.\nFix this by splitting cmd_notes() into separate functions for each\nsubcommand (besides append and edit since they're very similar) and\nonly providing a usage message for the subcommand.\n\nThis has an added benefit of reducing the code complexity while making\nit safer and easier to read. The downside is we get some code bloat\nfrom similar setup and teardown needed for notes and options parsing.\nWe also get a bit stricter in options parsing by only allowing\nthe ref option to come before the subcommand.\n\nCc: Johan Herland <johan@herland.net>\nCc: Thomas Rast <trast@student.ethz.ch>\nSigned-off-by: Stephen Boyd <bebarino@gmail.com>\n---\n\nThis is based on tr/notes-display in pu.\n\nI think this will be a better approach and probably easier to\nmaintain in the long run. Maybe some more common code can be combined?\n\n builtin-notes.c |  543 ++++++++++++++++++++++++++++++++++++++-----------------\n 1 files changed, 379 insertions(+), 164 deletions(-)\n\ndiff --git a/builtin-notes.c b/builtin-notes.c\nindex 2e45be9..a8a8bef 100644\n--- a/builtin-notes.c\n+++ b/builtin-notes.c\n@@ -30,6 +30,46 @@ static const char * const git_notes_usage[] = {\n \tNULL\n };\n \n+static const char * const git_notes_list_usage[] = {\n+\t\"git notes [list [<object>]]\",\n+\tNULL\n+};\n+\n+static const char * const git_notes_add_usage[] = {\n+\t\"git notes add [<options>] [<object>]\",\n+\tNULL\n+};\n+\n+static const char * const git_notes_copy_usage[] = {\n+\t\"git notes copy [<options>] <from-object> <to-object>\",\n+\tNULL\n+};\n+\n+static const char * const git_notes_append_usage[] = {\n+\t\"git notes append [<options>] [<object>]\",\n+\tNULL\n+};\n+\n+static const char * const git_notes_edit_usage[] = {\n+\t\"git notes edit [<object>]\",\n+\tNULL\n+};\n+\n+static const char * const git_notes_show_usage[] = {\n+\t\"git notes show [<object>]\",\n+\tNULL\n+};\n+\n+static const char * const git_notes_remove_usage[] = {\n+\t\"git notes remove [<object>]\",\n+\tNULL\n+};\n+\n+static const char * const git_notes_prune_usage[] = {\n+\t\"git notes prune\",\n+\tNULL\n+};\n+\n static const char note_template[] =\n \t\"\\n\"\n \t\"#\\n\"\n@@ -279,7 +319,6 @@ int commit_notes(struct notes_tree *t, const char *msg)\n \treturn 0;\n }\n \n-\n combine_notes_fn *parse_combine_notes_fn(const char *v)\n {\n \tif (!strcasecmp(v, \"overwrite\"))\n@@ -434,219 +473,395 @@ int notes_copy_from_stdin(int force, const char *rewrite_cmd)\n \treturn ret;\n }\n \n-int cmd_notes(int argc, const char **argv, const char *prefix)\n+static struct notes_tree *init_notes_check(const char *subcommand)\n {\n \tstruct notes_tree *t;\n-\tunsigned char object[20], from_obj[20], new_note[20];\n-\tconst unsigned char *note;\n-\tconst char *object_ref;\n-\tchar logmsg[100];\n+\tinit_notes(NULL, NULL, NULL, 0);\n+\tt = &default_notes_tree;\n \n-\tint list = 0, add = 0, copy = 0, append = 0, edit = 0, show = 0,\n-\t    remove = 0, prune = 0, force = 0, from_stdin = 0;\n-\tint given_object = 0, i = 1, retval = 0;\n-\tstruct msg_arg msg = { 0, 0, STRBUF_INIT };\n-\tconst char *rewrite_cmd = NULL;\n-\tconst char *override_notes_ref = NULL;\n+\tif (prefixcmp(t->ref, \"refs/notes/\"))\n+\t\tdie(\"Refusing to %s notes in %s (outside of refs/notes/)\",\n+\t\t    subcommand, t->ref);\n+\treturn t;\n+}\n+\n+static int list(int argc, const char **argv, const char *prefix)\n+{\n+\tconst char *object_ref;\n+\tstruct notes_tree *t;\n+\tunsigned char object[20];\n+\tconst unsigned char *note;\n+\tint retval = -1;\n \tstruct option options[] = {\n-\t\tOPT_GROUP(\"Notes options\"),\n-\t\tOPT_CALLBACK('m', \"message\", &msg, \"MSG\",\n-\t\t\t     \"note contents as a string\", parse_msg_arg),\n-\t\tOPT_CALLBACK('F', \"file\", &msg, \"FILE\",\n-\t\t\t     \"note contents in a file\", parse_file_arg),\n-\t\tOPT_CALLBACK('c', \"reedit-message\", &msg, \"OBJECT\",\n-\t\t\t   \"reuse and edit specified note object\", parse_reedit_arg),\n-\t\tOPT_CALLBACK('C', \"reuse-message\", &msg, \"OBJECT\",\n-\t\t\t   \"reuse specified note object\", parse_reuse_arg),\n-\t\tOPT_BOOLEAN('f', \"force\", &force, \"replace existing notes\"),\n-\t\tOPT_BOOLEAN(0, \"stdin\", &from_stdin, \"read objects from stdin\"),\n-\t\tOPT_STRING(0, \"ref\", &override_notes_ref, \"notes_ref\",\n-\t\t\t   \"use notes from <notes_ref>\"),\n-\t\tOPT_STRING(0, \"for-rewrite\", &rewrite_cmd, \"command\",\n-\t\t\t   \"load rewriting config for <command> (implies --stdin)\"),\n \t\tOPT_END()\n \t};\n \n-\tgit_config(git_default_config, NULL);\n-\n-\targc = parse_options(argc, argv, prefix, options, git_notes_usage, 0);\n+\tif (argc)\n+\t\targc = parse_options(argc, argv, prefix, options,\n+\t\t\t\t     git_notes_list_usage, 0);\n \n-\tif (override_notes_ref) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\tif (!prefixcmp(override_notes_ref, \"refs/notes/\"))\n-\t\t\t/* we're happy */;\n-\t\telse if (!prefixcmp(override_notes_ref, \"notes/\"))\n-\t\t\tstrbuf_addstr(&sb, \"refs/\");\n-\t\telse\n-\t\t\tstrbuf_addstr(&sb, \"refs/notes/\");\n-\t\tstrbuf_addstr(&sb, override_notes_ref);\n-\t\tsetenv(\"GIT_NOTES_REF\", sb.buf, 1);\n-\t\tstrbuf_release(&sb);\n-\t}\n-\n-\tif (argc && !strcmp(argv[0], \"list\"))\n-\t\tlist = 1;\n-\telse if (argc && !strcmp(argv[0], \"add\"))\n-\t\tadd = 1;\n-\telse if (argc && !strcmp(argv[0], \"copy\"))\n-\t\tcopy = 1;\n-\telse if (argc && !strcmp(argv[0], \"append\"))\n-\t\tappend = 1;\n-\telse if (argc && !strcmp(argv[0], \"edit\"))\n-\t\tedit = 1;\n-\telse if (argc && !strcmp(argv[0], \"show\"))\n-\t\tshow = 1;\n-\telse if (argc && !strcmp(argv[0], \"remove\"))\n-\t\tremove = 1;\n-\telse if (argc && !strcmp(argv[0], \"prune\"))\n-\t\tprune = 1;\n-\telse if (!argc) {\n-\t\tlist = 1; /* Default to 'list' if no other subcommand given */\n-\t\ti = 0;\n+\tif (1 < argc) {\n+\t\terror(\"too many parameters\");\n+\t\tusage_with_options(git_notes_list_usage, options);\n \t}\n \n-\tif (list + add + copy + append + edit + show + remove + prune != 1)\n-\t\tusage_with_options(git_notes_usage, options);\n+\tobject_ref = argc ? argv[0] : \"HEAD\";\n \n-\tif (msg.given && !(add || append || edit)) {\n-\t\terror(\"cannot use -m/-F/-c/-C options with %s subcommand.\",\n-\t\t      argv[0]);\n-\t\tusage_with_options(git_notes_usage, options);\n-\t}\n+\tif (get_sha1(object_ref, object))\n+\t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n \n-\tif (msg.given && edit) {\n-\t\tfprintf(stderr, \"The -m/-F/-c/-C options have been deprecated \"\n-\t\t\t\"for the 'edit' subcommand.\\n\"\n-\t\t\t\"Please use 'git notes add -f -m/-F/-c/-C' instead.\\n\");\n-\t}\n+\tt = init_notes_check(\"list\");\n+\tnote = get_note(t, object);\n \n-\tif (force && !(add || copy)) {\n-\t\terror(\"cannot use -f option with %s subcommand.\", argv[0]);\n-\t\tusage_with_options(git_notes_usage, options);\n-\t}\n+\tif (note) {\n+\t\tputs(sha1_to_hex(note));\n+\t\tretval = 0;\n+\t} else if (argc == 0)\n+\t\tretval = for_each_note(t, 0, list_each_note, NULL);\n \n-\tif (!copy && rewrite_cmd) {\n-\t\terror(\"cannot use --for-rewrite with %s subcommand.\", argv[0]);\n-\t\tusage_with_options(git_notes_usage, options);\n-\t}\n-\tif (!copy && from_stdin) {\n-\t\terror(\"cannot use --stdin with %s subcommand.\", argv[0]);\n-\t\tusage_with_options(git_notes_usage, options);\n-\t}\n+\tfree_notes(t);\n+\treturn retval;\n+}\n \n-\tif (copy) {\n-\t\tconst char *from_ref;\n-\t\tif (from_stdin || rewrite_cmd) {\n-\t\t\tif (argc > 1) {\n-\t\t\t\terror(\"too many parameters\");\n-\t\t\t\tusage_with_options(git_notes_usage, options);\n-\t\t\t} else {\n-\t\t\t\treturn notes_copy_from_stdin(force, rewrite_cmd);\n-\t\t\t}\n-\t\t}\n-\t\tif (argc < 3) {\n-\t\t\terror(\"too few parameters\");\n-\t\t\tusage_with_options(git_notes_usage, options);\n-\t\t}\n-\t\tfrom_ref = argv[i++];\n-\t\tif (get_sha1(from_ref, from_obj))\n-\t\t\tdie(\"Failed to resolve '%s' as a valid ref.\", from_ref);\n-\t}\n+static int add(int argc, const char **argv, const char *prefix)\n+{\n+\tint force = 0;\n+\tconst char *object_ref;\n+\tstruct notes_tree *t;\n+\tunsigned char object[20], new_note[20];\n+\tchar logmsg[100];\n+\tconst unsigned char *note;\n+\tstruct msg_arg msg = { 0, 0, STRBUF_INIT };\n+\tstruct option options[] = {\n+\t\t{ OPTION_CALLBACK, 'm', \"message\", &msg, \"MSG\",\n+\t\t\t\"note contents as a string\", PARSE_OPT_NONEG,\n+\t\t\tparse_msg_arg},\n+\t\t{ OPTION_CALLBACK, 'F', \"file\", &msg, \"FILE\",\n+\t\t\t\"note contents in a file\", PARSE_OPT_NONEG,\n+\t\t\tparse_file_arg},\n+\t\t{ OPTION_CALLBACK, 'c', \"reedit-message\", &msg, \"OBJECT\",\n+\t\t\t\"reuse and edit specified note object\", PARSE_OPT_NONEG,\n+\t\t\tparse_reedit_arg},\n+\t\t{ OPTION_CALLBACK, 'C', \"reuse-message\", &msg, \"OBJECT\",\n+\t\t\t\"reuse specified note object\", PARSE_OPT_NONEG,\n+\t\t\tparse_reuse_arg},\n+\t\tOPT_BOOLEAN('f', \"force\", &force, \"replace existing notes\"),\n+\t\tOPT_END()\n+\t};\n \n-\tgiven_object = argc > i;\n-\tobject_ref = given_object ? argv[i++] : \"HEAD\";\n+\targc = parse_options(argc, argv, prefix, options, git_notes_add_usage,\n+\t\t\t     0);\n \n-\tif (argc > i || (prune && given_object)) {\n+\tif (1 < argc) {\n \t\terror(\"too many parameters\");\n-\t\tusage_with_options(git_notes_usage, options);\n+\t\tusage_with_options(git_notes_add_usage, options);\n \t}\n \n+\tobject_ref = argc ? argv[0] : \"HEAD\";\n+\n \tif (get_sha1(object_ref, object))\n \t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n \n-\tinit_notes(NULL, NULL, NULL, 0);\n-\tt = &default_notes_tree;\n+\tt = init_notes_check(\"add\");\n+\tnote = get_note(t, object);\n \n-\tif (prefixcmp(t->ref, \"refs/notes/\"))\n-\t\tdie(\"Refusing to %s notes in %s (outside of refs/notes/)\",\n-\t\t    argv[0], t->ref);\n+\tif (note) {\n+\t\tif (!force)\n+\t\t\treturn\terror(\"Cannot add notes. Found existing notes \"\n+\t\t\t\t      \"for object %s. Use '-f' to overwrite \"\n+\t\t\t\t      \"existing notes\", sha1_to_hex(object));\n+\t\tfprintf(stderr, \"Overwriting existing notes for object %s\\n\",\n+\t\t\tsha1_to_hex(object));\n+\t}\n \n-\tnote = get_note(t, object);\n+\tcreate_note(object, &msg, 0, note, new_note);\n \n-\t/* list command */\n+\tif (is_null_sha1(new_note))\n+\t\tremove_note(t, object);\n+\telse\n+\t\tadd_note(t, object, new_note, combine_notes_overwrite);\n \n-\tif (list) {\n-\t\tif (given_object) {\n-\t\t\tif (note) {\n-\t\t\t\tputs(sha1_to_hex(note));\n-\t\t\t\tgoto end;\n-\t\t\t}\n+\tsnprintf(logmsg, sizeof(logmsg), \"Notes %s by 'git notes %s'\",\n+\t\t is_null_sha1(new_note) ? \"removed\" : \"added\", \"add\");\n+\tcommit_notes(t, logmsg);\n+\tfree_notes(t);\n+\tstrbuf_release(&(msg.buf));\n+\treturn 0;\n+}\n+\n+static int copy(int argc, const char **argv, const char *prefix)\n+{\n+\tint force = 0, from_stdin = 0;\n+\tconst unsigned char *from_note, *note;\n+\tconst char *object_ref;\n+\tunsigned char object[20], from_obj[20], new_note[20];\n+\tchar logmsg[100];\n+\tstruct notes_tree *t;\n+\tconst char *rewrite_cmd = NULL;\n+\tstruct option options[] = {\n+\t\tOPT_BOOLEAN('f', \"force\", &force, \"replace existing notes\"),\n+\t\tOPT_BOOLEAN(0, \"stdin\", &from_stdin, \"read objects from stdin\"),\n+\t\tOPT_STRING(0, \"for-rewrite\", &rewrite_cmd, \"command\",\n+\t\t\t   \"load rewriting config for <command> (implies \"\n+\t\t\t   \"--stdin)\"),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options, git_notes_copy_usage,\n+\t\t\t     0);\n+\n+\tif (from_stdin || rewrite_cmd) {\n+\t\tif (argc) {\n+\t\t\terror(\"too many parameters\");\n+\t\t\tusage_with_options(git_notes_copy_usage, options);\n \t\t} else {\n-\t\t\tretval = for_each_note(t, 0, list_each_note, NULL);\n-\t\t\tgoto end;\n+\t\t\treturn notes_copy_from_stdin(force, rewrite_cmd);\n \t\t}\n \t}\n \n-\t/* show command */\n-\n-\tif ((list || show) && !note) {\n-\t\terror(\"No note found for object %s.\", sha1_to_hex(object));\n-\t\tretval = 1;\n-\t\tgoto end;\n-\t} else if (show) {\n-\t\tconst char *show_args[3] = {\"show\", sha1_to_hex(note), NULL};\n-\t\tretval = execv_git_cmd(show_args);\n-\t\tgoto end;\n+\tif (2 < argc) {\n+\t\terror(\"too few parameters\");\n+\t\tusage_with_options(git_notes_copy_usage, options);\n \t}\n \n-\t/* add/append/edit/remove/prune command */\n+\tif (get_sha1(argv[0], from_obj))\n+\t\tdie(\"Failed to resolve '%s' as a valid ref.\", argv[0]);\n \n-\tif ((add || copy) && note) {\n-\t\tif (!force) {\n-\t\t\terror(\"Cannot %s notes. Found existing notes for object\"\n-\t\t\t      \" %s. Use '-f' to overwrite existing notes\",\n-\t\t\t      argv[0], sha1_to_hex(object));\n-\t\t\tretval = 1;\n-\t\t\tgoto end;\n-\t\t}\n+\tobject_ref = 1 < argc ? argv[1] : \"HEAD\";\n+\n+\tif (get_sha1(object_ref, object))\n+\t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n+\n+\tt = init_notes_check(\"copy\");\n+\tnote = get_note(t, object);\n+\n+\tif (note) {\n+\t\tif (!force)\n+\t\t\treturn error(\"Cannot copy notes. Found existing notes \"\n+\t\t\t\t     \"for object %s. Use '-f' to overwrite \"\n+\t\t\t\t     \"existing notes\", sha1_to_hex(object));\n \t\tfprintf(stderr, \"Overwriting existing notes for object %s\\n\",\n \t\t\tsha1_to_hex(object));\n \t}\n \n-\tif (remove) {\n-\t\tmsg.given = 1;\n-\t\tmsg.use_editor = 0;\n-\t\tstrbuf_reset(&(msg.buf));\n+\tfrom_note = get_note(t, from_obj);\n+\tif (!from_note)\n+\t\treturn error(\"Missing notes on source object %s. Cannot copy.\",\n+\t\t\t     sha1_to_hex(from_obj));\n+\thashcpy(new_note, from_note);\n+\n+\tif (is_null_sha1(new_note))\n+\t\tremove_note(t, object);\n+\telse\n+\t\tadd_note(t, object, new_note, combine_notes_overwrite);\n+\n+\tsnprintf(logmsg, sizeof(logmsg), \"Notes %s by 'git notes %s'\",\n+\t\t is_null_sha1(new_note) ? \"removed\" : \"added\", \"copy\");\n+\tcommit_notes(t, logmsg);\n+\tfree_notes(t);\n+\treturn 0;\n+}\n+\n+static int append_edit(int argc, const char **argv, const char *prefix)\n+{\n+\tconst char *object_ref;\n+\tstruct notes_tree *t;\n+\tunsigned char object[20], new_note[20];\n+\tconst unsigned char *note;\n+\tchar logmsg[100];\n+\tconst char * const *usage;\n+\tstruct msg_arg msg = { 0, 0, STRBUF_INIT };\n+\tstruct option options[] = {\n+\t\t{ OPTION_CALLBACK, 'm', \"message\", &msg, \"MSG\",\n+\t\t\t\"note contents as a string\", PARSE_OPT_NONEG,\n+\t\t\tparse_msg_arg},\n+\t\t{ OPTION_CALLBACK, 'F', \"file\", &msg, \"FILE\",\n+\t\t\t\"note contents in a file\", PARSE_OPT_NONEG,\n+\t\t\tparse_file_arg},\n+\t\t{ OPTION_CALLBACK, 'c', \"reedit-message\", &msg, \"OBJECT\",\n+\t\t\t\"reuse and edit specified note object\", PARSE_OPT_NONEG,\n+\t\t\tparse_reedit_arg},\n+\t\t{ OPTION_CALLBACK, 'C', \"reuse-message\", &msg, \"OBJECT\",\n+\t\t\t\"reuse specified note object\", PARSE_OPT_NONEG,\n+\t\t\tparse_reuse_arg},\n+\t\tOPT_END()\n+\t};\n+\tint edit = !strcmp(argv[0], \"edit\");\n+\n+\tusage = edit ? git_notes_edit_usage : git_notes_append_usage;\n+\targc = parse_options(argc, argv, prefix, options, usage,\n+\t\t\t     PARSE_OPT_KEEP_ARGV0);\n+\n+\tif (2 < argc) {\n+\t\terror(\"too many parameters\");\n+\t\tusage_with_options(usage, options);\n \t}\n \n-\tif (prune) {\n-\t\thashclr(new_note);\n-\t\tprune_notes(t);\n-\t\tgoto commit;\n-\t} else if (copy) {\n-\t\tconst unsigned char *from_note = get_note(t, from_obj);\n-\t\tif (!from_note) {\n-\t\t\terror(\"Missing notes on source object %s. Cannot copy.\",\n-\t\t\t      sha1_to_hex(from_obj));\n-\t\t\tretval = 1;\n-\t\t\tgoto end;\n-\t\t}\n-\t\thashcpy(new_note, from_note);\n-\t} else\n-\t\tcreate_note(object, &msg, append, note, new_note);\n+\tif (msg.given && edit)\n+\t\tfprintf(stderr, \"The -m/-F/-c/-C options have been deprecated \"\n+\t\t\t\"for the 'edit' subcommand.\\n\"\n+\t\t\t\"Please use 'git notes add -f -m/-F/-c/-C' instead.\\n\");\n+\n+\tobject_ref = 1 < argc ? argv[1] : \"HEAD\";\n+\n+\tif (get_sha1(object_ref, object))\n+\t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n+\n+\tt = init_notes_check(argv[0]);\n+\tnote = get_note(t, object);\n+\n+\tcreate_note(object, &msg, !edit, note, new_note);\n \n \tif (is_null_sha1(new_note))\n \t\tremove_note(t, object);\n \telse\n \t\tadd_note(t, object, new_note, combine_notes_overwrite);\n \n-commit:\n \tsnprintf(logmsg, sizeof(logmsg), \"Notes %s by 'git notes %s'\",\n \t\t is_null_sha1(new_note) ? \"removed\" : \"added\", argv[0]);\n \tcommit_notes(t, logmsg);\n-\n-end:\n \tfree_notes(t);\n \tstrbuf_release(&(msg.buf));\n+\treturn 0;\n+}\n+\n+static int show(int argc, const char **argv, const char *prefix)\n+{\n+\tconst char *object_ref;\n+\tstruct notes_tree *t;\n+\tunsigned char object[20];\n+\tconst unsigned char *note;\n+\tint retval;\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options, git_notes_show_usage,\n+\t\t\t     0);\n+\n+\tif (1 < argc) {\n+\t\terror(\"too many parameters\");\n+\t\tusage_with_options(git_notes_show_usage, options);\n+\t}\n+\n+\tobject_ref = argc ? argv[0] : \"HEAD\";\n+\n+\tif (get_sha1(object_ref, object))\n+\t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n+\n+\tt = init_notes_check(\"show\");\n+\tnote = get_note(t, object);\n+\n+\tif (!note)\n+\t\tretval = error(\"No note found for object %s.\",\n+\t\t\t\tsha1_to_hex(object));\n+\telse {\n+\t\tconst char *show_args[3] = {\"show\", sha1_to_hex(note), NULL};\n+\t\tretval = execv_git_cmd(show_args);\n+\t}\n+\tfree_notes(t);\n \treturn retval;\n }\n+\n+static int remove_cmd(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\tconst char *object_ref;\n+\tstruct notes_tree *t;\n+\tunsigned char object[20];\n+\n+\targc = parse_options(argc, argv, prefix, options,\n+\t\t\t     git_notes_remove_usage, 0);\n+\n+\tif (1 < argc) {\n+\t\terror(\"too many parameters\");\n+\t\tusage_with_options(git_notes_remove_usage, options);\n+\t}\n+\n+\tobject_ref = argc ? argv[0] : \"HEAD\";\n+\n+\tif (get_sha1(object_ref, object))\n+\t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n+\n+\tt = init_notes_check(\"remove\");\n+\n+\tfprintf(stderr, \"Removing note for object %s\\n\", sha1_to_hex(object));\n+\tremove_note(t, object);\n+\n+\tcommit_notes(t, \"Notes removed by 'git notes remove'\");\n+\tfree_notes(t);\n+\treturn 0;\n+}\n+\n+static int prune(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct notes_tree *t;\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options, git_notes_prune_usage,\n+\t\t\t     0);\n+\n+\tif (argc) {\n+\t\terror(\"too many parameters\");\n+\t\tusage_with_options(git_notes_prune_usage, options);\n+\t}\n+\n+\tt = init_notes_check(\"prune\");\n+\n+\tprune_notes(t);\n+\tcommit_notes(t, \"Notes removed by 'git notes prune'\");\n+\tfree_notes(t);\n+\treturn 0;\n+}\n+\n+int cmd_notes(int argc, const char **argv, const char *prefix)\n+{\n+\tint result;\n+\tconst char *override_notes_ref = NULL;\n+\tstruct option options[] = {\n+\t\tOPT_STRING(0, \"ref\", &override_notes_ref, \"notes_ref\",\n+\t\t\t   \"use notes from <notes_ref>\"),\n+\t\tOPT_END()\n+\t};\n+\n+\tgit_config(git_default_config, NULL);\n+\targc = parse_options(argc, argv, prefix, options, git_notes_usage,\n+\t\t\t     PARSE_OPT_STOP_AT_NON_OPTION);\n+\n+\tif (override_notes_ref) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tif (!prefixcmp(override_notes_ref, \"refs/notes/\"))\n+\t\t\t/* we're happy */;\n+\t\telse if (!prefixcmp(override_notes_ref, \"notes/\"))\n+\t\t\tstrbuf_addstr(&sb, \"refs/\");\n+\t\telse\n+\t\t\tstrbuf_addstr(&sb, \"refs/notes/\");\n+\t\tstrbuf_addstr(&sb, override_notes_ref);\n+\t\tsetenv(\"GIT_NOTES_REF\", sb.buf, 1);\n+\t\tstrbuf_release(&sb);\n+\t}\n+\n+\tif (argc < 1 || !strcmp(argv[0], \"list\"))\n+\t\tresult = list(argc, argv, prefix);\n+\telse if (!strcmp(argv[0], \"add\"))\n+\t\tresult = add(argc, argv, prefix);\n+\telse if (!strcmp(argv[0], \"copy\"))\n+\t\tresult = copy(argc, argv, prefix);\n+\telse if (!strcmp(argv[0], \"append\") || !strcmp(argv[0], \"edit\"))\n+\t\tresult = append_edit(argc, argv, prefix);\n+\telse if (!strcmp(argv[0], \"show\"))\n+\t\tresult = show(argc, argv, prefix);\n+\telse if (!strcmp(argv[0], \"remove\"))\n+\t\tresult = remove_cmd(argc, argv, prefix);\n+\telse if (!strcmp(argv[0], \"prune\"))\n+\t\tresult = prune(argc, argv, prefix);\n+\telse {\n+\t\tresult = error(\"Unknown subcommand: %s\", argv[0]);\n+\t}\n+\n+\treturn result ? 1 : 0;\n+}\n-- \n1.7.0.90.g251a4\n"},{"id":"135687","messageId":"32c343771002251026n6d55ea7csbdb4c8899bb5fded@mail.gmail.com","threadId":"22815","inReplyTo":"1267086261-7675-1-git-send-email-bebarino@gmail.com","subject":"Re: [RFC/PATCH] notes: rework subcommands and parse options","fromName":"Tim Henigan","fromEmail":"tim.henigan@gmail.com","sentAt":"2010-02-25T18:26:19Z","receivedAt":"2010-02-25T18:26:19Z","isPatch":true,"sender":{"key":"tim.henigan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42022?v=4"},"body":"On Thu, Feb 25, 2010 at 3:24 AM, Stephen Boyd <bebarino@gmail.com> wrote:\n>\n> +       else {\n> +               result = error(\"Unknown subcommand: %s\", argv[0]);\n> +       }\n\nIn the case where an unknown subcommand was given, shouldn't the usage\ninstructions be printed?  i.e. add the following after the error()?\n\n    usage_with_options(git_notes_usage, options);\n\n-Tim\n"},{"id":"135747","messageId":"201002261222.25209.johan@herland.net","threadId":"22815","inReplyTo":"1267086261-7675-1-git-send-email-bebarino@gmail.com","subject":"Re: [RFC/PATCH] notes: rework subcommands and parse options","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-26T11:22:24Z","receivedAt":"2010-02-26T11:22:24Z","isPatch":true,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"On Thursday 25 February 2010, Stephen Boyd wrote:\n> Running 'git notes copy -h' is not very helfpul right now. It lists\n> the options for all the git notes subcommands and is rather confusing.\n> Fix this by splitting cmd_notes() into separate functions for each\n> subcommand (besides append and edit since they're very similar) and\n> only providing a usage message for the subcommand.\n> \n> This has an added benefit of reducing the code complexity while making\n> it safer and easier to read. The downside is we get some code bloat\n> from similar setup and teardown needed for notes and options parsing.\n> We also get a bit stricter in options parsing by only allowing\n> the ref option to come before the subcommand.\n> \n> Cc: Johan Herland <johan@herland.net>\n> Cc: Thomas Rast <trast@student.ethz.ch>\n> Signed-off-by: Stephen Boyd <bebarino@gmail.com>\n> ---\n> \n> This is based on tr/notes-display in pu.\n> \n> I think this will be a better approach and probably easier to\n> maintain in the long run. Maybe some more common code can be combined?\n\nI like the intent of this, but the implementation is not there yet:\n\n(your patch edited for clarity while revieweing)\n\n> +static int list(int argc, const char **argv, const char *prefix)\n\n[...]\n\n> +\tobject_ref = argc ? argv[0] : \"HEAD\";\n> \n> +\tif (get_sha1(object_ref, object))\n> +\t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n> \n> +\tt = init_notes_check(\"list\");\n> +\tnote = get_note(t, object);\n> \n> +\tif (note) {\n> +\t\tputs(sha1_to_hex(note));\n> +\t\tretval = 0;\n> +\t} else if (argc == 0)\n> +\t\tretval = for_each_note(t, 0, list_each_note, NULL);\n\nThis changes the semantics when calling \"git notes list\" (in the case where\nHEAD has notes) from displaying all notes, to displaying HEAD's notes.\n\nIMO, \"git notes list\" without further parameters should always show a list\nof all notes regardless of whether HEAD has notes or not.\n\nMy patch below fixes this, and adds a test verifying those semantics (i.e.\nthe test succeeds before applying your patch, fails after applying it, and\nsucceeds again after squashing in my additional patch below).\n\n> +static int add(int argc, const char **argv, const char *prefix)\n\n[...]\n\n> +\tif (note) {\n> +\t\tif (!force)\n> +\t\t\treturn\terror(\"Cannot add notes. Found existing notes \"\n> +\t\t\t\t      \"for object %s. Use '-f' to overwrite \"\n> +\t\t\t\t      \"existing notes\", sha1_to_hex(object));\n\nMissing cleanup code before the \"return\":\n\n\tfree_notes(t);\n\tstrbuf_release(&(msg.buf));\n\nAlso s/<tab>/<space>/ between \"return\" and \"error\"\n\n> +static int copy(int argc, const char **argv, const char *prefix)\n\n[...]\n\n> +\tif (2 < argc) {\n> +\t\terror(\"too few parameters\");\n\ns/few/many/\n\n[...]\n\n> +\tif (note) {\n> +\t\tif (!force)\n> +\t\t\treturn error(\"Cannot copy notes. Found existing notes \"\n> +\t\t\t\t     \"for object %s. Use '-f' to overwrite \"\n> +\t\t\t\t     \"existing notes\", sha1_to_hex(object));\n\nMissing cleanup code before the \"return\":\n\n\tfree_notes(t);\n\n>  \t\tfprintf(stderr, \"Overwriting existing notes for object %s\\n\",\n>  \t\t\tsha1_to_hex(object));\n>  \t}\n> \n> +\tfrom_note = get_note(t, from_obj);\n> +\tif (!from_note)\n> +\t\treturn error(\"Missing notes on source object %s. Cannot copy.\",\n> +\t\t\t     sha1_to_hex(from_obj));\n\nMissing cleanup code before the \"return\":\n\n\tfree_notes(t);\n\n> +\thashcpy(new_note, from_note);\n> +\n> +\tif (is_null_sha1(new_note))\n> +\t\tremove_note(t, object);\n> +\telse\n> +\t\tadd_note(t, object, new_note, combine_notes_overwrite);\n> +\n> +\tsnprintf(logmsg, sizeof(logmsg), \"Notes %s by 'git notes %s'\",\n> +\t\t is_null_sha1(new_note) ? \"removed\" : \"added\", \"copy\");\n> +\tcommit_notes(t, logmsg);\n\nCan get rid of \"new_note\" and \"logmsg\" in this block. Also is_null_sha1()\nshould never happen. Hence, simply replace with:\n\n\tadd_note(t, object, from_note, combine_notes_overwrite);\n\tcommit_notes(t, \"Notes added by 'git notes copy'\");\n\n> +static int show(int argc, const char **argv, const char *prefix)\n\n[...]\n\n> +\tif (!note)\n> +\t\tretval = error(\"No note found for object %s.\",\n> +\t\t\t\tsha1_to_hex(object));\n\nMinor whitespace issue (replace last TAB with 7 spaces)\n\n> +int cmd_notes(int argc, const char **argv, const char *prefix)\n> +{\n> +\tint result;\n> +\tconst char *override_notes_ref = NULL;\n> +\tstruct option options[] = {\n> +\t\tOPT_STRING(0, \"ref\", &override_notes_ref, \"notes_ref\",\n> +\t\t\t   \"use notes from <notes_ref>\"),\n> +\t\tOPT_END()\n> +\t};\n> +\tgit_config(git_default_config, NULL);\n> +\targc = parse_options(argc, argv, prefix, options, git_notes_usage,\n> +\t\t\t     PARSE_OPT_STOP_AT_NON_OPTION);\n\nHere, you move --ref from being a \"git notes <subcommand>\" option to a\n\"git notes\" option (i.e. given _before_ the <subcommand>). There is\nnothing wrong with this per se, but it needs to be reflected in\ngit_notes_usage[] (see my patch below for a proposal).\n\n[...]\n\n> +\tif (argc < 1 || !strcmp(argv[0], \"list\"))\n> +\t\tresult = list(argc, argv, prefix);\n> +\telse if (!strcmp(argv[0], \"add\"))\n> +\t\tresult = add(argc, argv, prefix);\n> +\telse if (!strcmp(argv[0], \"copy\"))\n> +\t\tresult = copy(argc, argv, prefix);\n> +\telse if (!strcmp(argv[0], \"append\") || !strcmp(argv[0], \"edit\"))\n> +\t\tresult = append_edit(argc, argv, prefix);\n> +\telse if (!strcmp(argv[0], \"show\"))\n> +\t\tresult = show(argc, argv, prefix);\n> +\telse if (!strcmp(argv[0], \"remove\"))\n> +\t\tresult = remove_cmd(argc, argv, prefix);\n> +\telse if (!strcmp(argv[0], \"prune\"))\n> +\t\tresult = prune(argc, argv, prefix);\n> +\telse {\n> +\t\tresult = error(\"Unknown subcommand: %s\", argv[0]);\n\nAs Tim Henigan already noted, you need\n\n\tusage_with_options(git_notes_usage, options);\n\nIf you squash in the following patch (fixes all of my above complaints),\nyou can add\n\n\tAcked-by: Johan Herland <johan@herland.net>\n\nto your commit message. :)\n\n---\n builtin/notes.c  |   77 ++++++++++++++++++++++++++---------------------------\n t/t3301-notes.sh |   11 +++++++\n 2 files changed, 49 insertions(+), 39 deletions(-)\n\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex d9dd1f2..e471b28 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -19,14 +19,14 @@\n #include \"string-list.h\"\n \n static const char * const git_notes_usage[] = {\n-\t\"git notes [list [<object>]]\",\n-\t\"git notes add [-f] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\",\n-\t\"git notes copy [-f] <from-object> <to-object>\",\n-\t\"git notes append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\",\n-\t\"git notes edit [<object>]\",\n-\t\"git notes show [<object>]\",\n-\t\"git notes remove [<object>]\",\n-\t\"git notes prune\",\n+\t\"git notes [--ref <notes_ref>] [list [<object>]]\",\n+\t\"git notes [--ref <notes_ref>] add [-f] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\",\n+\t\"git notes [--ref <notes_ref>] copy [-f] <from-object> <to-object>\",\n+\t\"git notes [--ref <notes_ref>] append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\",\n+\t\"git notes [--ref <notes_ref>] edit [<object>]\",\n+\t\"git notes [--ref <notes_ref>] show [<object>]\",\n+\t\"git notes [--ref <notes_ref>] remove [<object>]\",\n+\t\"git notes [--ref <notes_ref>] prune\",\n \tNULL\n };\n \n@@ -478,7 +478,6 @@ static struct notes_tree *init_notes_check(const char *subcommand)\n \n static int list(int argc, const char **argv, const char *prefix)\n {\n-\tconst char *object_ref;\n \tstruct notes_tree *t;\n \tunsigned char object[20];\n \tconst unsigned char *note;\n@@ -496,18 +495,18 @@ static int list(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(git_notes_list_usage, options);\n \t}\n \n-\tobject_ref = argc ? argv[0] : \"HEAD\";\n-\n-\tif (get_sha1(object_ref, object))\n-\t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n-\n \tt = init_notes_check(\"list\");\n-\tnote = get_note(t, object);\n-\n-\tif (note) {\n-\t\tputs(sha1_to_hex(note));\n-\t\tretval = 0;\n-\t} else if (argc == 0)\n+\tif (argc) {\n+\t\tif (get_sha1(argv[0], object))\n+\t\t\tdie(\"Failed to resolve '%s' as a valid ref.\", argv[0]);\n+\t\tnote = get_note(t, object);\n+\t\tif (note) {\n+\t\t\tputs(sha1_to_hex(note));\n+\t\t\tretval = 0;\n+\t\t} else\n+\t\t\tretval = error(\"No note found for object %s.\",\n+\t\t\t\t       sha1_to_hex(object));\n+\t} else\n \t\tretval = for_each_note(t, 0, list_each_note, NULL);\n \n \tfree_notes(t);\n@@ -557,10 +556,13 @@ static int add(int argc, const char **argv, const char *prefix)\n \tnote = get_note(t, object);\n \n \tif (note) {\n-\t\tif (!force)\n-\t\t\treturn\terror(\"Cannot add notes. Found existing notes \"\n-\t\t\t\t      \"for object %s. Use '-f' to overwrite \"\n-\t\t\t\t      \"existing notes\", sha1_to_hex(object));\n+\t\tif (!force) {\n+\t\t\tfree_notes(t);\n+\t\t\tstrbuf_release(&(msg.buf));\n+\t\t\treturn error(\"Cannot add notes. Found existing notes \"\n+\t\t\t\t     \"for object %s. Use '-f' to overwrite \"\n+\t\t\t\t     \"existing notes\", sha1_to_hex(object));\n+\t\t}\n \t\tfprintf(stderr, \"Overwriting existing notes for object %s\\n\",\n \t\t\tsha1_to_hex(object));\n \t}\n@@ -585,8 +587,7 @@ static int copy(int argc, const char **argv, const char *prefix)\n \tint force = 0, from_stdin = 0;\n \tconst unsigned char *from_note, *note;\n \tconst char *object_ref;\n-\tunsigned char object[20], from_obj[20], new_note[20];\n-\tchar logmsg[100];\n+\tunsigned char object[20], from_obj[20];\n \tstruct notes_tree *t;\n \tconst char *rewrite_cmd = NULL;\n \tstruct option options[] = {\n@@ -611,7 +612,7 @@ static int copy(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (2 < argc) {\n-\t\terror(\"too few parameters\");\n+\t\terror(\"too many parameters\");\n \t\tusage_with_options(git_notes_copy_usage, options);\n \t}\n \n@@ -627,28 +628,25 @@ static int copy(int argc, const char **argv, const char *prefix)\n \tnote = get_note(t, object);\n \n \tif (note) {\n-\t\tif (!force)\n+\t\tif (!force) {\n+\t\t\tfree_notes(t);\n \t\t\treturn error(\"Cannot copy notes. Found existing notes \"\n \t\t\t\t     \"for object %s. Use '-f' to overwrite \"\n \t\t\t\t     \"existing notes\", sha1_to_hex(object));\n+\t\t}\n \t\tfprintf(stderr, \"Overwriting existing notes for object %s\\n\",\n \t\t\tsha1_to_hex(object));\n \t}\n \n \tfrom_note = get_note(t, from_obj);\n-\tif (!from_note)\n+\tif (!from_note) {\n+\t\tfree_notes(t);\n \t\treturn error(\"Missing notes on source object %s. Cannot copy.\",\n \t\t\t     sha1_to_hex(from_obj));\n-\thashcpy(new_note, from_note);\n-\n-\tif (is_null_sha1(new_note))\n-\t\tremove_note(t, object);\n-\telse\n-\t\tadd_note(t, object, new_note, combine_notes_overwrite);\n+\t}\n \n-\tsnprintf(logmsg, sizeof(logmsg), \"Notes %s by 'git notes %s'\",\n-\t\t is_null_sha1(new_note) ? \"removed\" : \"added\", \"copy\");\n-\tcommit_notes(t, logmsg);\n+\tadd_note(t, object, from_note, combine_notes_overwrite);\n+\tcommit_notes(t, \"Notes added by 'git notes copy'\");\n \tfree_notes(t);\n \treturn 0;\n }\n@@ -745,7 +743,7 @@ static int show(int argc, const char **argv, const char *prefix)\n \n \tif (!note)\n \t\tretval = error(\"No note found for object %s.\",\n-\t\t\t\tsha1_to_hex(object));\n+\t\t\t       sha1_to_hex(object));\n \telse {\n \t\tconst char *show_args[3] = {\"show\", sha1_to_hex(note), NULL};\n \t\tretval = execv_git_cmd(show_args);\n@@ -852,6 +850,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\tresult = prune(argc, argv, prefix);\n \telse {\n \t\tresult = error(\"Unknown subcommand: %s\", argv[0]);\n+\t\tusage_with_options(git_notes_usage, options);\n \t}\n \n \treturn result ? 1 : 0;\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex a4a0b1d..1d6cd45 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -378,6 +378,17 @@ test_expect_success 'append to existing note with \"git notes append\"' '\n \ttest_cmp expect output\n '\n \n+cat > expect_list << EOF\n+c18dc024e14f08d18d14eea0d747ff692d66d6a3 1584215f1d29c65e99c6c6848626553fdd07fd75\n+c9c6af7f78bc47490dbf3e822cf2f3c24d4b9061 268048bfb8a1fb38e703baceb8ab235421bf80c5\n+4b6ad22357cc8a1296720574b8d2fbc22fab0671 bd1753200303d0a0344be813e504253b3d98e74d\n+EOF\n+\n+test_expect_success '\"git notes list\" does not expand to \"git notes list HEAD\"' '\n+\tgit notes list > output &&\n+\ttest_cmp expect_list output\n+'\n+\n test_expect_success 'appending empty string does not change existing note' '\n \tgit notes append -m \"\" &&\n \tgit notes show > output &&\n-- \n1.7.0.207.gac4ec\n\n\nHave fun! :)\n\n...Johan\n\n-- \nJohan Herland, <johan@herland.net>\nwww.herland.net\n"},{"id":"135818","messageId":"1267261162-31750-1-git-send-email-bebarino@gmail.com","threadId":"22815","inReplyTo":"1267086261-7675-1-git-send-email-bebarino@gmail.com","subject":"[PATCH] notes: rework subcommands and parse options","fromName":"Stephen Boyd","fromEmail":"bebarino@gmail.com","sentAt":"2010-02-27T08:59:22Z","receivedAt":"2010-02-27T08:59:22Z","isPatch":true,"sender":{"key":"bebarino@gmail.com","avatar":"https://avatars.githubusercontent.com/u/38832?v=4"},"body":"Running 'git notes copy -h' is not very helfpul right now. It lists\nthe options for all the git notes subcommands and is rather confusing.\nFix this by splitting cmd_notes() into separate functions for each\nsubcommand (besides append and edit since they're very similar) and\nonly providing a usage message for the subcommand.\n\nThis has an added benefit of reducing the code complexity while making\nit safer and easier to read. The downside is we get some code bloat\nfrom similar setup and teardown needed for notes and options parsing.\nWe also get a bit stricter in options parsing by only allowing\nthe ref option to come before the subcommand.\n\nAcked-by: Johan Herland <johan@herland.net>\nCc: Thomas Rast <trast@student.ethz.ch>\nSigned-off-by: Stephen Boyd <bebarino@gmail.com>\n---\n\nBased on latest version of tr/notes-display in pu.\n\nThanks to Tim Henigan for the suggestion and Johan Herland for the\nsuggestions/cleanup.\n\n builtin-notes.c  |  549 +++++++++++++++++++++++++++++++++++++----------------\n t/t3301-notes.sh |   11 +\n 2 files changed, 394 insertions(+), 166 deletions(-)\n\ndiff --git a/builtin-notes.c b/builtin-notes.c\nindex 2e45be9..3b84392 100644\n--- a/builtin-notes.c\n+++ b/builtin-notes.c\n@@ -19,13 +19,54 @@\n #include \"string-list.h\"\n \n static const char * const git_notes_usage[] = {\n+\t\"git notes [--ref <notes_ref>] [list [<object>]]\",\n+\t\"git notes [--ref <notes_ref>] add [-f] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\",\n+\t\"git notes [--ref <notes_ref>] copy [-f] <from-object> <to-object>\",\n+\t\"git notes [--ref <notes_ref>] append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\",\n+\t\"git notes [--ref <notes_ref>] edit [<object>]\",\n+\t\"git notes [--ref <notes_ref>] show [<object>]\",\n+\t\"git notes [--ref <notes_ref>] remove [<object>]\",\n+\t\"git notes [--ref <notes_ref>] prune\",\n+\tNULL\n+};\n+\n+static const char * const git_notes_list_usage[] = {\n \t\"git notes [list [<object>]]\",\n-\t\"git notes add [-f] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\",\n-\t\"git notes copy [-f] <from-object> <to-object>\",\n-\t\"git notes append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\",\n+\tNULL\n+};\n+\n+static const char * const git_notes_add_usage[] = {\n+\t\"git notes add [<options>] [<object>]\",\n+\tNULL\n+};\n+\n+static const char * const git_notes_copy_usage[] = {\n+\t\"git notes copy [<options>] <from-object> <to-object>\",\n+\t\"git notes copy --stdin [<from-object> <to-object>]...\",\n+\tNULL\n+};\n+\n+static const char * const git_notes_append_usage[] = {\n+\t\"git notes append [<options>] [<object>]\",\n+\tNULL\n+};\n+\n+static const char * const git_notes_edit_usage[] = {\n \t\"git notes edit [<object>]\",\n+\tNULL\n+};\n+\n+static const char * const git_notes_show_usage[] = {\n \t\"git notes show [<object>]\",\n+\tNULL\n+};\n+\n+static const char * const git_notes_remove_usage[] = {\n \t\"git notes remove [<object>]\",\n+\tNULL\n+};\n+\n+static const char * const git_notes_prune_usage[] = {\n \t\"git notes prune\",\n \tNULL\n };\n@@ -279,7 +320,6 @@ int commit_notes(struct notes_tree *t, const char *msg)\n \treturn 0;\n }\n \n-\n combine_notes_fn *parse_combine_notes_fn(const char *v)\n {\n \tif (!strcasecmp(v, \"overwrite\"))\n@@ -434,219 +474,396 @@ int notes_copy_from_stdin(int force, const char *rewrite_cmd)\n \treturn ret;\n }\n \n-int cmd_notes(int argc, const char **argv, const char *prefix)\n+static struct notes_tree *init_notes_check(const char *subcommand)\n {\n \tstruct notes_tree *t;\n-\tunsigned char object[20], from_obj[20], new_note[20];\n-\tconst unsigned char *note;\n-\tconst char *object_ref;\n-\tchar logmsg[100];\n+\tinit_notes(NULL, NULL, NULL, 0);\n+\tt = &default_notes_tree;\n \n-\tint list = 0, add = 0, copy = 0, append = 0, edit = 0, show = 0,\n-\t    remove = 0, prune = 0, force = 0, from_stdin = 0;\n-\tint given_object = 0, i = 1, retval = 0;\n-\tstruct msg_arg msg = { 0, 0, STRBUF_INIT };\n-\tconst char *rewrite_cmd = NULL;\n-\tconst char *override_notes_ref = NULL;\n+\tif (prefixcmp(t->ref, \"refs/notes/\"))\n+\t\tdie(\"Refusing to %s notes in %s (outside of refs/notes/)\",\n+\t\t    subcommand, t->ref);\n+\treturn t;\n+}\n+\n+static int list(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct notes_tree *t;\n+\tunsigned char object[20];\n+\tconst unsigned char *note;\n+\tint retval = -1;\n \tstruct option options[] = {\n-\t\tOPT_GROUP(\"Notes options\"),\n-\t\tOPT_CALLBACK('m', \"message\", &msg, \"MSG\",\n-\t\t\t     \"note contents as a string\", parse_msg_arg),\n-\t\tOPT_CALLBACK('F', \"file\", &msg, \"FILE\",\n-\t\t\t     \"note contents in a file\", parse_file_arg),\n-\t\tOPT_CALLBACK('c', \"reedit-message\", &msg, \"OBJECT\",\n-\t\t\t   \"reuse and edit specified note object\", parse_reedit_arg),\n-\t\tOPT_CALLBACK('C', \"reuse-message\", &msg, \"OBJECT\",\n-\t\t\t   \"reuse specified note object\", parse_reuse_arg),\n-\t\tOPT_BOOLEAN('f', \"force\", &force, \"replace existing notes\"),\n-\t\tOPT_BOOLEAN(0, \"stdin\", &from_stdin, \"read objects from stdin\"),\n-\t\tOPT_STRING(0, \"ref\", &override_notes_ref, \"notes_ref\",\n-\t\t\t   \"use notes from <notes_ref>\"),\n-\t\tOPT_STRING(0, \"for-rewrite\", &rewrite_cmd, \"command\",\n-\t\t\t   \"load rewriting config for <command> (implies --stdin)\"),\n \t\tOPT_END()\n \t};\n \n-\tgit_config(git_default_config, NULL);\n-\n-\targc = parse_options(argc, argv, prefix, options, git_notes_usage, 0);\n+\tif (argc)\n+\t\targc = parse_options(argc, argv, prefix, options,\n+\t\t\t\t     git_notes_list_usage, 0);\n \n-\tif (override_notes_ref) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\tif (!prefixcmp(override_notes_ref, \"refs/notes/\"))\n-\t\t\t/* we're happy */;\n-\t\telse if (!prefixcmp(override_notes_ref, \"notes/\"))\n-\t\t\tstrbuf_addstr(&sb, \"refs/\");\n-\t\telse\n-\t\t\tstrbuf_addstr(&sb, \"refs/notes/\");\n-\t\tstrbuf_addstr(&sb, override_notes_ref);\n-\t\tsetenv(\"GIT_NOTES_REF\", sb.buf, 1);\n-\t\tstrbuf_release(&sb);\n+\tif (1 < argc) {\n+\t\terror(\"too many parameters\");\n+\t\tusage_with_options(git_notes_list_usage, options);\n \t}\n \n-\tif (argc && !strcmp(argv[0], \"list\"))\n-\t\tlist = 1;\n-\telse if (argc && !strcmp(argv[0], \"add\"))\n-\t\tadd = 1;\n-\telse if (argc && !strcmp(argv[0], \"copy\"))\n-\t\tcopy = 1;\n-\telse if (argc && !strcmp(argv[0], \"append\"))\n-\t\tappend = 1;\n-\telse if (argc && !strcmp(argv[0], \"edit\"))\n-\t\tedit = 1;\n-\telse if (argc && !strcmp(argv[0], \"show\"))\n-\t\tshow = 1;\n-\telse if (argc && !strcmp(argv[0], \"remove\"))\n-\t\tremove = 1;\n-\telse if (argc && !strcmp(argv[0], \"prune\"))\n-\t\tprune = 1;\n-\telse if (!argc) {\n-\t\tlist = 1; /* Default to 'list' if no other subcommand given */\n-\t\ti = 0;\n-\t}\n+\tt = init_notes_check(\"list\");\n+\tif (argc) {\n+\t\tif (get_sha1(argv[0], object))\n+\t\t\tdie(\"Failed to resolve '%s' as a valid ref.\", argv[0]);\n+\t\tnote = get_note(t, object);\n+\t\tif (note) {\n+\t\t\tputs(sha1_to_hex(note));\n+\t\t\tretval = 0;\n+\t\t} else\n+\t\t\tretval = error(\"No note found for object %s.\",\n+\t\t\t\t       sha1_to_hex(object));\n+\t} else\n+\t\tretval = for_each_note(t, 0, list_each_note, NULL);\n \n-\tif (list + add + copy + append + edit + show + remove + prune != 1)\n-\t\tusage_with_options(git_notes_usage, options);\n+\tfree_notes(t);\n+\treturn retval;\n+}\n \n-\tif (msg.given && !(add || append || edit)) {\n-\t\terror(\"cannot use -m/-F/-c/-C options with %s subcommand.\",\n-\t\t      argv[0]);\n-\t\tusage_with_options(git_notes_usage, options);\n-\t}\n+static int add(int argc, const char **argv, const char *prefix)\n+{\n+\tint retval = 0, force = 0;\n+\tconst char *object_ref;\n+\tstruct notes_tree *t;\n+\tunsigned char object[20], new_note[20];\n+\tchar logmsg[100];\n+\tconst unsigned char *note;\n+\tstruct msg_arg msg = { 0, 0, STRBUF_INIT };\n+\tstruct option options[] = {\n+\t\t{ OPTION_CALLBACK, 'm', \"message\", &msg, \"MSG\",\n+\t\t\t\"note contents as a string\", PARSE_OPT_NONEG,\n+\t\t\tparse_msg_arg},\n+\t\t{ OPTION_CALLBACK, 'F', \"file\", &msg, \"FILE\",\n+\t\t\t\"note contents in a file\", PARSE_OPT_NONEG,\n+\t\t\tparse_file_arg},\n+\t\t{ OPTION_CALLBACK, 'c', \"reedit-message\", &msg, \"OBJECT\",\n+\t\t\t\"reuse and edit specified note object\", PARSE_OPT_NONEG,\n+\t\t\tparse_reedit_arg},\n+\t\t{ OPTION_CALLBACK, 'C', \"reuse-message\", &msg, \"OBJECT\",\n+\t\t\t\"reuse specified note object\", PARSE_OPT_NONEG,\n+\t\t\tparse_reuse_arg},\n+\t\tOPT_BOOLEAN('f', \"force\", &force, \"replace existing notes\"),\n+\t\tOPT_END()\n+\t};\n \n-\tif (msg.given && edit) {\n-\t\tfprintf(stderr, \"The -m/-F/-c/-C options have been deprecated \"\n-\t\t\t\"for the 'edit' subcommand.\\n\"\n-\t\t\t\"Please use 'git notes add -f -m/-F/-c/-C' instead.\\n\");\n-\t}\n+\targc = parse_options(argc, argv, prefix, options, git_notes_add_usage,\n+\t\t\t     0);\n \n-\tif (force && !(add || copy)) {\n-\t\terror(\"cannot use -f option with %s subcommand.\", argv[0]);\n-\t\tusage_with_options(git_notes_usage, options);\n+\tif (1 < argc) {\n+\t\terror(\"too many parameters\");\n+\t\tusage_with_options(git_notes_add_usage, options);\n \t}\n \n-\tif (!copy && rewrite_cmd) {\n-\t\terror(\"cannot use --for-rewrite with %s subcommand.\", argv[0]);\n-\t\tusage_with_options(git_notes_usage, options);\n-\t}\n-\tif (!copy && from_stdin) {\n-\t\terror(\"cannot use --stdin with %s subcommand.\", argv[0]);\n-\t\tusage_with_options(git_notes_usage, options);\n-\t}\n+\tobject_ref = argc ? argv[0] : \"HEAD\";\n \n-\tif (copy) {\n-\t\tconst char *from_ref;\n-\t\tif (from_stdin || rewrite_cmd) {\n-\t\t\tif (argc > 1) {\n-\t\t\t\terror(\"too many parameters\");\n-\t\t\t\tusage_with_options(git_notes_usage, options);\n-\t\t\t} else {\n-\t\t\t\treturn notes_copy_from_stdin(force, rewrite_cmd);\n-\t\t\t}\n-\t\t}\n-\t\tif (argc < 3) {\n-\t\t\terror(\"too few parameters\");\n-\t\t\tusage_with_options(git_notes_usage, options);\n-\t\t}\n-\t\tfrom_ref = argv[i++];\n-\t\tif (get_sha1(from_ref, from_obj))\n-\t\t\tdie(\"Failed to resolve '%s' as a valid ref.\", from_ref);\n-\t}\n+\tif (get_sha1(object_ref, object))\n+\t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n \n-\tgiven_object = argc > i;\n-\tobject_ref = given_object ? argv[i++] : \"HEAD\";\n+\tt = init_notes_check(\"add\");\n+\tnote = get_note(t, object);\n \n-\tif (argc > i || (prune && given_object)) {\n-\t\terror(\"too many parameters\");\n-\t\tusage_with_options(git_notes_usage, options);\n+\tif (note) {\n+\t\tif (!force) {\n+\t\t\tretval = error(\"Cannot add notes. Found existing notes \"\n+\t\t\t\t       \"for object %s. Use '-f' to overwrite \"\n+\t\t\t\t       \"existing notes\", sha1_to_hex(object));\n+\t\t\tgoto out;\n+\t\t}\n+\t\tfprintf(stderr, \"Overwriting existing notes for object %s\\n\",\n+\t\t\tsha1_to_hex(object));\n \t}\n \n-\tif (get_sha1(object_ref, object))\n-\t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n+\tcreate_note(object, &msg, 0, note, new_note);\n \n-\tinit_notes(NULL, NULL, NULL, 0);\n-\tt = &default_notes_tree;\n+\tif (is_null_sha1(new_note))\n+\t\tremove_note(t, object);\n+\telse\n+\t\tadd_note(t, object, new_note, combine_notes_overwrite);\n \n-\tif (prefixcmp(t->ref, \"refs/notes/\"))\n-\t\tdie(\"Refusing to %s notes in %s (outside of refs/notes/)\",\n-\t\t    argv[0], t->ref);\n+\tsnprintf(logmsg, sizeof(logmsg), \"Notes %s by 'git notes %s'\",\n+\t\t is_null_sha1(new_note) ? \"removed\" : \"added\", \"add\");\n+\tcommit_notes(t, logmsg);\n+out:\n+\tfree_notes(t);\n+\tstrbuf_release(&(msg.buf));\n+\treturn retval;\n+}\n \n-\tnote = get_note(t, object);\n+static int copy(int argc, const char **argv, const char *prefix)\n+{\n+\tint retval = 0, force = 0, from_stdin = 0;\n+\tconst unsigned char *from_note, *note;\n+\tconst char *object_ref;\n+\tunsigned char object[20], from_obj[20];\n+\tstruct notes_tree *t;\n+\tconst char *rewrite_cmd = NULL;\n+\tstruct option options[] = {\n+\t\tOPT_BOOLEAN('f', \"force\", &force, \"replace existing notes\"),\n+\t\tOPT_BOOLEAN(0, \"stdin\", &from_stdin, \"read objects from stdin\"),\n+\t\tOPT_STRING(0, \"for-rewrite\", &rewrite_cmd, \"command\",\n+\t\t\t   \"load rewriting config for <command> (implies \"\n+\t\t\t   \"--stdin)\"),\n+\t\tOPT_END()\n+\t};\n \n-\t/* list command */\n+\targc = parse_options(argc, argv, prefix, options, git_notes_copy_usage,\n+\t\t\t     0);\n \n-\tif (list) {\n-\t\tif (given_object) {\n-\t\t\tif (note) {\n-\t\t\t\tputs(sha1_to_hex(note));\n-\t\t\t\tgoto end;\n-\t\t\t}\n+\tif (from_stdin || rewrite_cmd) {\n+\t\tif (argc) {\n+\t\t\terror(\"too many parameters\");\n+\t\t\tusage_with_options(git_notes_copy_usage, options);\n \t\t} else {\n-\t\t\tretval = for_each_note(t, 0, list_each_note, NULL);\n-\t\t\tgoto end;\n+\t\t\treturn notes_copy_from_stdin(force, rewrite_cmd);\n \t\t}\n \t}\n \n-\t/* show command */\n-\n-\tif ((list || show) && !note) {\n-\t\terror(\"No note found for object %s.\", sha1_to_hex(object));\n-\t\tretval = 1;\n-\t\tgoto end;\n-\t} else if (show) {\n-\t\tconst char *show_args[3] = {\"show\", sha1_to_hex(note), NULL};\n-\t\tretval = execv_git_cmd(show_args);\n-\t\tgoto end;\n+\tif (2 < argc) {\n+\t\terror(\"too many parameters\");\n+\t\tusage_with_options(git_notes_copy_usage, options);\n \t}\n \n-\t/* add/append/edit/remove/prune command */\n+\tif (get_sha1(argv[0], from_obj))\n+\t\tdie(\"Failed to resolve '%s' as a valid ref.\", argv[0]);\n+\n+\tobject_ref = 1 < argc ? argv[1] : \"HEAD\";\n+\n+\tif (get_sha1(object_ref, object))\n+\t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n \n-\tif ((add || copy) && note) {\n+\tt = init_notes_check(\"copy\");\n+\tnote = get_note(t, object);\n+\n+\tif (note) {\n \t\tif (!force) {\n-\t\t\terror(\"Cannot %s notes. Found existing notes for object\"\n-\t\t\t      \" %s. Use '-f' to overwrite existing notes\",\n-\t\t\t      argv[0], sha1_to_hex(object));\n-\t\t\tretval = 1;\n-\t\t\tgoto end;\n+\t\t\tretval = error(\"Cannot copy notes. Found existing \"\n+\t\t\t\t       \"notes for object %s. Use '-f' to \"\n+\t\t\t\t       \"overwrite existing notes\",\n+\t\t\t\t       sha1_to_hex(object));\n+\t\t\tgoto out;\n \t\t}\n \t\tfprintf(stderr, \"Overwriting existing notes for object %s\\n\",\n \t\t\tsha1_to_hex(object));\n \t}\n \n-\tif (remove) {\n-\t\tmsg.given = 1;\n-\t\tmsg.use_editor = 0;\n-\t\tstrbuf_reset(&(msg.buf));\n+\tfrom_note = get_note(t, from_obj);\n+\tif (!from_note) {\n+\t\tretval = error(\"Missing notes on source object %s. Cannot \"\n+\t\t\t       \"copy.\", sha1_to_hex(from_obj));\n+\t\tgoto out;\n \t}\n \n-\tif (prune) {\n-\t\thashclr(new_note);\n-\t\tprune_notes(t);\n-\t\tgoto commit;\n-\t} else if (copy) {\n-\t\tconst unsigned char *from_note = get_note(t, from_obj);\n-\t\tif (!from_note) {\n-\t\t\terror(\"Missing notes on source object %s. Cannot copy.\",\n-\t\t\t      sha1_to_hex(from_obj));\n-\t\t\tretval = 1;\n-\t\t\tgoto end;\n-\t\t}\n-\t\thashcpy(new_note, from_note);\n-\t} else\n-\t\tcreate_note(object, &msg, append, note, new_note);\n+\tadd_note(t, object, from_note, combine_notes_overwrite);\n+\tcommit_notes(t, \"Notes added by 'git notes copy'\");\n+out:\n+\tfree_notes(t);\n+\treturn retval;\n+}\n+\n+static int append_edit(int argc, const char **argv, const char *prefix)\n+{\n+\tconst char *object_ref;\n+\tstruct notes_tree *t;\n+\tunsigned char object[20], new_note[20];\n+\tconst unsigned char *note;\n+\tchar logmsg[100];\n+\tconst char * const *usage;\n+\tstruct msg_arg msg = { 0, 0, STRBUF_INIT };\n+\tstruct option options[] = {\n+\t\t{ OPTION_CALLBACK, 'm', \"message\", &msg, \"MSG\",\n+\t\t\t\"note contents as a string\", PARSE_OPT_NONEG,\n+\t\t\tparse_msg_arg},\n+\t\t{ OPTION_CALLBACK, 'F', \"file\", &msg, \"FILE\",\n+\t\t\t\"note contents in a file\", PARSE_OPT_NONEG,\n+\t\t\tparse_file_arg},\n+\t\t{ OPTION_CALLBACK, 'c', \"reedit-message\", &msg, \"OBJECT\",\n+\t\t\t\"reuse and edit specified note object\", PARSE_OPT_NONEG,\n+\t\t\tparse_reedit_arg},\n+\t\t{ OPTION_CALLBACK, 'C', \"reuse-message\", &msg, \"OBJECT\",\n+\t\t\t\"reuse specified note object\", PARSE_OPT_NONEG,\n+\t\t\tparse_reuse_arg},\n+\t\tOPT_END()\n+\t};\n+\tint edit = !strcmp(argv[0], \"edit\");\n+\n+\tusage = edit ? git_notes_edit_usage : git_notes_append_usage;\n+\targc = parse_options(argc, argv, prefix, options, usage,\n+\t\t\t     PARSE_OPT_KEEP_ARGV0);\n+\n+\tif (2 < argc) {\n+\t\terror(\"too many parameters\");\n+\t\tusage_with_options(usage, options);\n+\t}\n+\n+\tif (msg.given && edit)\n+\t\tfprintf(stderr, \"The -m/-F/-c/-C options have been deprecated \"\n+\t\t\t\"for the 'edit' subcommand.\\n\"\n+\t\t\t\"Please use 'git notes add -f -m/-F/-c/-C' instead.\\n\");\n+\n+\tobject_ref = 1 < argc ? argv[1] : \"HEAD\";\n+\n+\tif (get_sha1(object_ref, object))\n+\t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n+\n+\tt = init_notes_check(argv[0]);\n+\tnote = get_note(t, object);\n+\n+\tcreate_note(object, &msg, !edit, note, new_note);\n \n \tif (is_null_sha1(new_note))\n \t\tremove_note(t, object);\n \telse\n \t\tadd_note(t, object, new_note, combine_notes_overwrite);\n \n-commit:\n \tsnprintf(logmsg, sizeof(logmsg), \"Notes %s by 'git notes %s'\",\n \t\t is_null_sha1(new_note) ? \"removed\" : \"added\", argv[0]);\n \tcommit_notes(t, logmsg);\n-\n-end:\n \tfree_notes(t);\n \tstrbuf_release(&(msg.buf));\n+\treturn 0;\n+}\n+\n+static int show(int argc, const char **argv, const char *prefix)\n+{\n+\tconst char *object_ref;\n+\tstruct notes_tree *t;\n+\tunsigned char object[20];\n+\tconst unsigned char *note;\n+\tint retval;\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options, git_notes_show_usage,\n+\t\t\t     0);\n+\n+\tif (1 < argc) {\n+\t\terror(\"too many parameters\");\n+\t\tusage_with_options(git_notes_show_usage, options);\n+\t}\n+\n+\tobject_ref = argc ? argv[0] : \"HEAD\";\n+\n+\tif (get_sha1(object_ref, object))\n+\t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n+\n+\tt = init_notes_check(\"show\");\n+\tnote = get_note(t, object);\n+\n+\tif (!note)\n+\t\tretval = error(\"No note found for object %s.\",\n+\t\t\t       sha1_to_hex(object));\n+\telse {\n+\t\tconst char *show_args[3] = {\"show\", sha1_to_hex(note), NULL};\n+\t\tretval = execv_git_cmd(show_args);\n+\t}\n+\tfree_notes(t);\n \treturn retval;\n }\n+\n+static int remove_cmd(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\tconst char *object_ref;\n+\tstruct notes_tree *t;\n+\tunsigned char object[20];\n+\n+\targc = parse_options(argc, argv, prefix, options,\n+\t\t\t     git_notes_remove_usage, 0);\n+\n+\tif (1 < argc) {\n+\t\terror(\"too many parameters\");\n+\t\tusage_with_options(git_notes_remove_usage, options);\n+\t}\n+\n+\tobject_ref = argc ? argv[0] : \"HEAD\";\n+\n+\tif (get_sha1(object_ref, object))\n+\t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n+\n+\tt = init_notes_check(\"remove\");\n+\n+\tfprintf(stderr, \"Removing note for object %s\\n\", sha1_to_hex(object));\n+\tremove_note(t, object);\n+\n+\tcommit_notes(t, \"Notes removed by 'git notes remove'\");\n+\tfree_notes(t);\n+\treturn 0;\n+}\n+\n+static int prune(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct notes_tree *t;\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options, git_notes_prune_usage,\n+\t\t\t     0);\n+\n+\tif (argc) {\n+\t\terror(\"too many parameters\");\n+\t\tusage_with_options(git_notes_prune_usage, options);\n+\t}\n+\n+\tt = init_notes_check(\"prune\");\n+\n+\tprune_notes(t);\n+\tcommit_notes(t, \"Notes removed by 'git notes prune'\");\n+\tfree_notes(t);\n+\treturn 0;\n+}\n+\n+int cmd_notes(int argc, const char **argv, const char *prefix)\n+{\n+\tint result;\n+\tconst char *override_notes_ref = NULL;\n+\tstruct option options[] = {\n+\t\tOPT_STRING(0, \"ref\", &override_notes_ref, \"notes_ref\",\n+\t\t\t   \"use notes from <notes_ref>\"),\n+\t\tOPT_END()\n+\t};\n+\n+\tgit_config(git_default_config, NULL);\n+\targc = parse_options(argc, argv, prefix, options, git_notes_usage,\n+\t\t\t     PARSE_OPT_STOP_AT_NON_OPTION);\n+\n+\tif (override_notes_ref) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tif (!prefixcmp(override_notes_ref, \"refs/notes/\"))\n+\t\t\t/* we're happy */;\n+\t\telse if (!prefixcmp(override_notes_ref, \"notes/\"))\n+\t\t\tstrbuf_addstr(&sb, \"refs/\");\n+\t\telse\n+\t\t\tstrbuf_addstr(&sb, \"refs/notes/\");\n+\t\tstrbuf_addstr(&sb, override_notes_ref);\n+\t\tsetenv(\"GIT_NOTES_REF\", sb.buf, 1);\n+\t\tstrbuf_release(&sb);\n+\t}\n+\n+\tif (argc < 1 || !strcmp(argv[0], \"list\"))\n+\t\tresult = list(argc, argv, prefix);\n+\telse if (!strcmp(argv[0], \"add\"))\n+\t\tresult = add(argc, argv, prefix);\n+\telse if (!strcmp(argv[0], \"copy\"))\n+\t\tresult = copy(argc, argv, prefix);\n+\telse if (!strcmp(argv[0], \"append\") || !strcmp(argv[0], \"edit\"))\n+\t\tresult = append_edit(argc, argv, prefix);\n+\telse if (!strcmp(argv[0], \"show\"))\n+\t\tresult = show(argc, argv, prefix);\n+\telse if (!strcmp(argv[0], \"remove\"))\n+\t\tresult = remove_cmd(argc, argv, prefix);\n+\telse if (!strcmp(argv[0], \"prune\"))\n+\t\tresult = prune(argc, argv, prefix);\n+\telse {\n+\t\tresult = error(\"Unknown subcommand: %s\", argv[0]);\n+\t\tusage_with_options(git_notes_usage, options);\n+\t}\n+\n+\treturn result ? 1 : 0;\n+}\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex aeec90a..5b26211 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -377,6 +377,17 @@ test_expect_success 'append to existing note with \"git notes append\"' '\n \ttest_cmp expect output\n '\n \n+cat > expect_list << EOF\n+c18dc024e14f08d18d14eea0d747ff692d66d6a3 1584215f1d29c65e99c6c6848626553fdd07fd75\n+c9c6af7f78bc47490dbf3e822cf2f3c24d4b9061 268048bfb8a1fb38e703baceb8ab235421bf80c5\n+4b6ad22357cc8a1296720574b8d2fbc22fab0671 bd1753200303d0a0344be813e504253b3d98e74d\n+EOF\n+\n+test_expect_success '\"git notes list\" does not expand to \"git notes list HEAD\"' '\n+\tgit notes list > output &&\n+\ttest_cmp expect_list output\n+'\n+\n test_expect_success 'appending empty string does not change existing note' '\n \tgit notes append -m \"\" &&\n \tgit notes show > output &&\n-- \n1.7.0.94.gf7311\n"}]}