Code

Merge branch 'np/index-pack-memsave' into maint
[git.git] / builtin / notes.c
index 4543d113113f90d7fb262b5fe54cbe5f6dfe29fd..26617546c8cadac493f06473d02f27f320713b28 100644 (file)
 #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"))
@@ -376,7 +416,7 @@ int notes_copy_from_stdin(int force, const char *rewrite_cmd)
 {
        struct strbuf buf = STRBUF_INIT;
        struct notes_rewrite_cfg *c = NULL;
-       struct notes_tree *t;
+       struct notes_tree *t = NULL;
        int ret = 0;
 
        if (rewrite_cmd) {
@@ -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];
+       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/)",
+                   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];
-
-       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;
+       const unsigned char *note;
        struct msg_arg msg = { 0, 0, STRBUF_INIT };
-       const char *rewrite_cmd = NULL;
-       const char *override_notes_ref = NULL;
        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,196 +538,325 @@ 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_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
-               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_add_usage,
+                            0);
 
-       if (override_notes_ref) {
-               struct strbuf sb = STRBUF_INIT;
-               if (!prefixcmp(override_notes_ref, "refs/notes/"))
-                       /* we're happy */;
-               else if (!prefixcmp(override_notes_ref, "notes/"))
-                       strbuf_addstr(&sb, "refs/");
-               else
-                       strbuf_addstr(&sb, "refs/notes/");
-               strbuf_addstr(&sb, override_notes_ref);
-               setenv("GIT_NOTES_REF", sb.buf, 1);
-               strbuf_release(&sb);
+       if (1 < argc) {
+               error("too many parameters");
+               usage_with_options(git_notes_add_usage, options);
        }
 
-       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;
-       }
+       object_ref = argc ? argv[0] : "HEAD";
 
-       if (list + add + copy + append + edit + show + remove + prune != 1)
-               usage_with_options(git_notes_usage, options);
+       if (get_sha1(object_ref, object))
+               die("Failed to resolve '%s' as a valid ref.", object_ref);
 
-       if (msg.given && !(add || append || edit)) {
-               error("cannot use -m/-F/-c/-C options with %s subcommand.",
-                     argv[0]);
-               usage_with_options(git_notes_usage, options);
-       }
+       t = init_notes_check("add");
+       note = get_note(t, object);
 
-       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 (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));
        }
 
-       if (force && !(add || copy)) {
-               error("cannot use -f option with %s subcommand.", argv[0]);
-               usage_with_options(git_notes_usage, options);
-       }
+       create_note(object, &msg, 0, note, new_note);
 
-       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 (is_null_sha1(new_note))
+               remove_note(t, object);
+       else
+               add_note(t, object, new_note, combine_notes_overwrite);
 
-       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);
+       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);
                }
-               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)) {
+       if (2 < argc) {
                error("too many parameters");
-               usage_with_options(git_notes_usage, options);
+               usage_with_options(git_notes_copy_usage, options);
        }
 
-       if (get_sha1(object_ref, object))
-               die("Failed to resolve '%s' as a valid ref.", object_ref);
+       if (get_sha1(argv[0], from_obj))
+               die("Failed to resolve '%s' as a valid ref.", argv[0]);
 
-       init_notes(NULL, NULL, NULL, 0);
-       t = &default_notes_tree;
+       object_ref = 1 < argc ? argv[1] : "HEAD";
 
-       if (prefixcmp(t->ref, "refs/notes/"))
-               die("Refusing to %s notes in %s (outside of refs/notes/)",
-                   argv[0], t->ref);
+       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);
 
-       /* 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;
+       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));
        }
 
-       /* 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;
+       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/append/edit/remove/prune command */
+       add_note(t, object, from_note, combine_notes_overwrite);
+       commit_notes(t, "Notes added by 'git notes copy'");
+out:
+       free_notes(t);
+       return retval;
+}
 
-       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));
-       }
+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 (remove) {
-               msg.given = 1;
-               msg.use_editor = 0;
-               strbuf_reset(&(msg.buf));
+       if (2 < argc) {
+               error("too many parameters");
+               usage_with_options(usage, options);
        }
 
-       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 (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);
 
-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 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_END()
+       };
+
+       git_config(git_default_config, NULL);
+       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;
+               if (!prefixcmp(override_notes_ref, "refs/notes/"))
+                       /* we're happy */;
+               else if (!prefixcmp(override_notes_ref, "notes/"))
+                       strbuf_addstr(&sb, "refs/");
+               else
+                       strbuf_addstr(&sb, "refs/notes/");
+               strbuf_addstr(&sb, override_notes_ref);
+               setenv("GIT_NOTES_REF", sb.buf, 1);
+               strbuf_release(&sb);
+       }
+
+       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);
+       }
+
+       return result ? 1 : 0;
+}