i18n: git-bisect bisect_replay + $1 messages
[gitweb.git] / builtin / notes.c
index 32d8a249446d2b1dfa2105cce9900291a72fc643..1fb1f734397668280b444e63dc8d3c129d41e3b9 100644 (file)
@@ -26,9 +26,12 @@ static const char * const git_notes_usage[] = {
        "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>] merge [-v | -q] <notes_ref>",
+       "git notes [--ref <notes_ref>] merge [-v | -q] [-s <strategy> ] <notes_ref>",
+       "git notes merge --commit [-v | -q]",
+       "git notes merge --abort [-v | -q]",
        "git notes [--ref <notes_ref>] remove [<object>]",
        "git notes [--ref <notes_ref>] prune [-n | -v]",
+       "git notes [--ref <notes_ref>] get-ref",
        NULL
 };
 
@@ -65,6 +68,8 @@ static const char * const git_notes_show_usage[] = {
 
 static const char * const git_notes_merge_usage[] = {
        "git notes merge [<options>] <notes_ref>",
+       "git notes merge --commit [<options>]",
+       "git notes merge --abort [<options>]",
        NULL
 };
 
@@ -78,6 +83,11 @@ static const char * const git_notes_prune_usage[] = {
        NULL
 };
 
+static const char * const git_notes_get_ref_usage[] = {
+       "git notes get-ref",
+       NULL
+};
+
 static const char note_template[] =
        "\n"
        "#\n"
@@ -90,16 +100,6 @@ struct msg_arg {
        struct strbuf buf;
 };
 
-static void expand_notes_ref(struct strbuf *sb)
-{
-       if (!prefixcmp(sb->buf, "refs/notes/"))
-               return; /* we're happy */
-       else if (!prefixcmp(sb->buf, "notes/"))
-               strbuf_insert(sb, 0, "refs/", 5);
-       else
-               strbuf_insert(sb, 0, "refs/notes/", 11);
-}
-
 static int list_each_note(const unsigned char *object_sha1,
                const unsigned char *note_sha1, char *note_path,
                void *cb_data)
@@ -136,13 +136,13 @@ static void write_commented_object(int fd, const unsigned char *object)
        show.err = 0;
        show.git_cmd = 1;
        if (start_command(&show))
-               die("unable to start 'show' for object '%s'",
+               die(_("unable to start 'show' for object '%s'"),
                    sha1_to_hex(object));
 
        /* Open the output as FILE* so strbuf_getline() can be used. */
        show_out = xfdopen(show.out, "r");
        if (show_out == NULL)
-               die_errno("can't fdopen 'show' output fd");
+               die_errno(_("can't fdopen 'show' output fd"));
 
        /* Prepend "# " to each output line and write result to 'fd' */
        while (strbuf_getline(&buf, show_out, '\n') != EOF) {
@@ -152,10 +152,10 @@ static void write_commented_object(int fd, const unsigned char *object)
        }
        strbuf_release(&buf);
        if (fclose(show_out))
-               die_errno("failed to close pipe to 'show' for object '%s'",
+               die_errno(_("failed to close pipe to 'show' for object '%s'"),
                          sha1_to_hex(object));
        if (finish_command(&show))
-               die("failed to finish 'show' for object '%s'",
+               die(_("failed to finish 'show' for object '%s'"),
                    sha1_to_hex(object));
 }
 
@@ -172,7 +172,7 @@ static void create_note(const unsigned char *object, struct msg_arg *msg,
                path = git_pathdup("NOTES_EDITMSG");
                fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
                if (fd < 0)
-                       die_errno("could not create file '%s'", path);
+                       die_errno(_("could not create file '%s'"), path);
 
                if (msg->given)
                        write_or_die(fd, msg->buf.buf, msg->buf.len);
@@ -186,8 +186,8 @@ static void create_note(const unsigned char *object, struct msg_arg *msg,
                strbuf_reset(&(msg->buf));
 
                if (launch_editor(path, &(msg->buf), NULL)) {
-                       die("Please supply the note contents using either -m" \
-                           " or -F option");
+                       die(_("Please supply the note contents using either -m" \
+                           " or -F option"));
                }
                stripspace(&(msg->buf), 1);
        }
@@ -207,14 +207,14 @@ static void create_note(const unsigned char *object, struct msg_arg *msg,
        }
 
        if (!msg->buf.len) {
-               fprintf(stderr, "Removing note for object %s\n",
+               fprintf(stderr, _("Removing note for object %s\n"),
                        sha1_to_hex(object));
                hashclr(result);
        } else {
                if (write_sha1_file(msg->buf.buf, msg->buf.len, blob_type, result)) {
-                       error("unable to write note object");
+                       error(_("unable to write note object"));
                        if (path)
-                               error("The note contents has been left in %s",
+                               error(_("The note contents has been left in %s"),
                                      path);
                        exit(128);
                }
@@ -248,9 +248,9 @@ static int parse_file_arg(const struct option *opt, const char *arg, int unset)
                strbuf_addch(&(msg->buf), '\n');
        if (!strcmp(arg, "-")) {
                if (strbuf_read(&(msg->buf), 0, 1024) < 0)
-                       die_errno("cannot read '%s'", arg);
+                       die_errno(_("cannot read '%s'"), arg);
        } else if (strbuf_read_file(&(msg->buf), arg, 1024) < 0)
-               die_errno("could not open or read '%s'", arg);
+               die_errno(_("could not open or read '%s'"), arg);
        stripspace(&(msg->buf), 0);
 
        msg->given = 1;
@@ -269,10 +269,10 @@ static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)
                strbuf_addch(&(msg->buf), '\n');
 
        if (get_sha1(arg, object))
-               die("Failed to resolve '%s' as a valid ref.", arg);
+               die(_("Failed to resolve '%s' as a valid ref."), arg);
        if (!(buf = read_sha1_file(object, &type, &len)) || !len) {
                free(buf);
-               die("Failed to read object '%s'.", arg);;
+               die(_("Failed to read object '%s'."), arg);;
        }
        strbuf_add(&(msg->buf), buf, len);
        free(buf);
@@ -296,7 +296,7 @@ void commit_notes(struct notes_tree *t, const char *msg)
        if (!t)
                t = &default_notes_tree;
        if (!t->initialized || !t->ref || !*t->ref)
-               die("Cannot commit uninitialized/unreferenced notes tree");
+               die(_("Cannot commit uninitialized/unreferenced notes tree"));
        if (!t->dirty)
                return; /* don't have to commit an unchanged tree */
 
@@ -320,6 +320,8 @@ combine_notes_fn parse_combine_notes_fn(const char *v)
                return combine_notes_ignore;
        else if (!strcasecmp(v, "concatenate"))
                return combine_notes_concatenate;
+       else if (!strcasecmp(v, "cat_sort_uniq"))
+               return combine_notes_cat_sort_uniq;
        else
                return NULL;
 }
@@ -335,7 +337,7 @@ static int notes_rewrite_config(const char *k, const char *v, void *cb)
                        config_error_nonbool(k);
                c->combine = parse_combine_notes_fn(v);
                if (!c->combine) {
-                       error("Bad notes.rewriteMode value: '%s'", v);
+                       error(_("Bad notes.rewriteMode value: '%s'"), v);
                        return 1;
                }
                return 0;
@@ -345,8 +347,8 @@ static int notes_rewrite_config(const char *k, const char *v, void *cb)
                if (!prefixcmp(v, "refs/notes/"))
                        string_list_add_refs_by_glob(c->refs, v);
                else
-                       warning("Refusing to rewrite notes in %s"
-                               " (outside of refs/notes/)", v);
+                       warning(_("Refusing to rewrite notes in %s"
+                               " (outside of refs/notes/)"), v);
                return 0;
        }
 
@@ -370,8 +372,10 @@ struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd)
                c->mode_from_env = 1;
                c->combine = parse_combine_notes_fn(rewrite_mode_env);
                if (!c->combine)
-                       error("Bad " GIT_NOTES_REWRITE_MODE_ENVIRONMENT
-                             " value: '%s'", rewrite_mode_env);
+                       /* TRANSLATORS: The first %s is the name of the
+                          environment variable, the second %s is its value */
+                       error(_("Bad %s value: '%s'"), GIT_NOTES_REWRITE_MODE_ENVIRONMENT,
+                                       rewrite_mode_env);
        }
        if (rewrite_refs_env) {
                c->refs_from_env = 1;
@@ -411,7 +415,7 @@ void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c)
        free(c);
 }
 
-int notes_copy_from_stdin(int force, const char *rewrite_cmd)
+static int notes_copy_from_stdin(int force, const char *rewrite_cmd)
 {
        struct strbuf buf = STRBUF_INIT;
        struct notes_rewrite_cfg *c = NULL;
@@ -434,13 +438,13 @@ int notes_copy_from_stdin(int force, const char *rewrite_cmd)
 
                split = strbuf_split(&buf, ' ');
                if (!split[0] || !split[1])
-                       die("Malformed input line: '%s'.", buf.buf);
+                       die(_("Malformed input line: '%s'."), buf.buf);
                strbuf_rtrim(split[0]);
                strbuf_rtrim(split[1]);
                if (get_sha1(split[0]->buf, from_obj))
-                       die("Failed to resolve '%s' as a valid ref.", split[0]->buf);
+                       die(_("Failed to resolve '%s' as a valid ref."), split[0]->buf);
                if (get_sha1(split[1]->buf, to_obj))
-                       die("Failed to resolve '%s' as a valid ref.", split[1]->buf);
+                       die(_("Failed to resolve '%s' as a valid ref."), split[1]->buf);
 
                if (rewrite_cmd)
                        err = copy_note_for_rewrite(c, from_obj, to_obj);
@@ -449,7 +453,7 @@ int notes_copy_from_stdin(int force, const char *rewrite_cmd)
                                        combine_notes_overwrite);
 
                if (err) {
-                       error("Failed to copy notes from '%s' to '%s'",
+                       error(_("Failed to copy notes from '%s' to '%s'"),
                              split[0]->buf, split[1]->buf);
                        ret = 1;
                }
@@ -493,20 +497,20 @@ static int list(int argc, const char **argv, const char *prefix)
                                     git_notes_list_usage, 0);
 
        if (1 < argc) {
-               error("too many parameters");
+               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]);
+                       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.",
+                       retval = error(_("No note found for object %s."),
                                       sha1_to_hex(object));
        } else
                retval = for_each_note(t, 0, list_each_note, NULL);
@@ -515,6 +519,8 @@ static int list(int argc, const char **argv, const char *prefix)
        return retval;
 }
 
+static int append_edit(int argc, const char **argv, const char *prefix);
+
 static int add(int argc, const char **argv, const char *prefix)
 {
        int retval = 0, force = 0;
@@ -525,46 +531,58 @@ static int add(int argc, const char **argv, const char *prefix)
        const unsigned char *note;
        struct msg_arg msg = { 0, 0, STRBUF_INIT };
        struct option options[] = {
-               { OPTION_CALLBACK, 'm', "message", &msg, "MSG",
+               { OPTION_CALLBACK, 'm', "message", &msg, "msg",
                        "note contents as a string", PARSE_OPT_NONEG,
                        parse_msg_arg},
-               { OPTION_CALLBACK, 'F', "file", &msg, "FILE",
+               { OPTION_CALLBACK, 'F', "file", &msg, "file",
                        "note contents in a file", PARSE_OPT_NONEG,
                        parse_file_arg},
-               { OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT",
+               { 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",
+               { OPTION_CALLBACK, 'C', "reuse-message", &msg, "object",
                        "reuse specified note object", PARSE_OPT_NONEG,
                        parse_reuse_arg},
-               OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
+               OPT__FORCE(&force, "replace existing notes"),
                OPT_END()
        };
 
        argc = parse_options(argc, argv, prefix, options, git_notes_add_usage,
-                            0);
+                            PARSE_OPT_KEEP_ARGV0);
 
-       if (1 < argc) {
-               error("too many parameters");
+       if (2 < argc) {
+               error(_("too many parameters"));
                usage_with_options(git_notes_add_usage, options);
        }
 
-       object_ref = argc ? argv[0] : "HEAD";
+       object_ref = argc > 1 ? argv[1] : "HEAD";
 
        if (get_sha1(object_ref, object))
-               die("Failed to resolve '%s' as a valid ref.", object_ref);
+               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 "
+                       if (!msg.given) {
+                               /*
+                                * Redirect to "edit" subcommand.
+                                *
+                                * We only end up here if none of -m/-F/-c/-C
+                                * or -f are given. The original args are
+                                * therefore still in argv[0-1].
+                                */
+                               argv[0] = "edit";
+                               free_notes(t);
+                               return append_edit(argc, argv, prefix);
+                       }
+                       retval = error(_("Cannot add notes. Found existing notes "
                                       "for object %s. Use '-f' to overwrite "
-                                      "existing notes", sha1_to_hex(object));
+                                      "existing notes"), sha1_to_hex(object));
                        goto out;
                }
-               fprintf(stderr, "Overwriting existing notes for object %s\n",
+               fprintf(stderr, _("Overwriting existing notes for object %s\n"),
                        sha1_to_hex(object));
        }
 
@@ -593,7 +611,7 @@ static int copy(int argc, const char **argv, const char *prefix)
        struct notes_tree *t;
        const char *rewrite_cmd = NULL;
        struct option options[] = {
-               OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
+               OPT__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 "
@@ -606,7 +624,7 @@ static int copy(int argc, const char **argv, const char *prefix)
 
        if (from_stdin || rewrite_cmd) {
                if (argc) {
-                       error("too many parameters");
+                       error(_("too many parameters"));
                        usage_with_options(git_notes_copy_usage, options);
                } else {
                        return notes_copy_from_stdin(force, rewrite_cmd);
@@ -614,41 +632,41 @@ static int copy(int argc, const char **argv, const char *prefix)
        }
 
        if (argc < 2) {
-               error("too few parameters");
+               error(_("too few parameters"));
                usage_with_options(git_notes_copy_usage, options);
        }
        if (2 < argc) {
-               error("too many parameters");
+               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]);
+               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);
+               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 "
+                       retval = error(_("Cannot copy notes. Found existing "
                                       "notes for object %s. Use '-f' to "
-                                      "overwrite existing notes",
+                                      "overwrite existing notes"),
                                       sha1_to_hex(object));
                        goto out;
                }
-               fprintf(stderr, "Overwriting existing notes for object %s\n",
+               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));
+               retval = error(_("Missing notes on source object %s. Cannot "
+                              "copy."), sha1_to_hex(from_obj));
                goto out;
        }
 
@@ -670,16 +688,16 @@ static int append_edit(int argc, const char **argv, const char *prefix)
        const char * const *usage;
        struct msg_arg msg = { 0, 0, STRBUF_INIT };
        struct option options[] = {
-               { OPTION_CALLBACK, 'm', "message", &msg, "MSG",
+               { OPTION_CALLBACK, 'm', "message", &msg, "msg",
                        "note contents as a string", PARSE_OPT_NONEG,
                        parse_msg_arg},
-               { OPTION_CALLBACK, 'F', "file", &msg, "FILE",
+               { OPTION_CALLBACK, 'F', "file", &msg, "file",
                        "note contents in a file", PARSE_OPT_NONEG,
                        parse_file_arg},
-               { OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT",
+               { 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",
+               { OPTION_CALLBACK, 'C', "reuse-message", &msg, "object",
                        "reuse specified note object", PARSE_OPT_NONEG,
                        parse_reuse_arg},
                OPT_END()
@@ -691,19 +709,19 @@ static int append_edit(int argc, const char **argv, const char *prefix)
                             PARSE_OPT_KEEP_ARGV0);
 
        if (2 < argc) {
-               error("too many parameters");
+               error(_("too many parameters"));
                usage_with_options(usage, options);
        }
 
        if (msg.given && edit)
-               fprintf(stderr, "The -m/-F/-c/-C options have been deprecated "
+               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");
+                       "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);
+               die(_("Failed to resolve '%s' as a valid ref."), object_ref);
 
        t = init_notes_check(argv[0]);
        note = get_note(t, object);
@@ -738,20 +756,20 @@ static int show(int argc, const char **argv, const char *prefix)
                             0);
 
        if (1 < argc) {
-               error("too many parameters");
+               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);
+               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.",
+               retval = error(_("No note found for object %s."),
                               sha1_to_hex(object));
        else {
                const char *show_args[3] = {"show", sha1_to_hex(note), NULL};
@@ -761,49 +779,178 @@ static int show(int argc, const char **argv, const char *prefix)
        return retval;
 }
 
+static int merge_abort(struct notes_merge_options *o)
+{
+       int ret = 0;
+
+       /*
+        * Remove .git/NOTES_MERGE_PARTIAL and .git/NOTES_MERGE_REF, and call
+        * notes_merge_abort() to remove .git/NOTES_MERGE_WORKTREE.
+        */
+
+       if (delete_ref("NOTES_MERGE_PARTIAL", NULL, 0))
+               ret += error("Failed to delete ref NOTES_MERGE_PARTIAL");
+       if (delete_ref("NOTES_MERGE_REF", NULL, REF_NODEREF))
+               ret += error("Failed to delete ref NOTES_MERGE_REF");
+       if (notes_merge_abort(o))
+               ret += error("Failed to remove 'git notes merge' worktree");
+       return ret;
+}
+
+static int merge_commit(struct notes_merge_options *o)
+{
+       struct strbuf msg = STRBUF_INIT;
+       unsigned char sha1[20], parent_sha1[20];
+       struct notes_tree *t;
+       struct commit *partial;
+       struct pretty_print_context pretty_ctx;
+
+       /*
+        * Read partial merge result from .git/NOTES_MERGE_PARTIAL,
+        * and target notes ref from .git/NOTES_MERGE_REF.
+        */
+
+       if (get_sha1("NOTES_MERGE_PARTIAL", sha1))
+               die("Failed to read ref NOTES_MERGE_PARTIAL");
+       else if (!(partial = lookup_commit_reference(sha1)))
+               die("Could not find commit from NOTES_MERGE_PARTIAL.");
+       else if (parse_commit(partial))
+               die("Could not parse commit from NOTES_MERGE_PARTIAL.");
+
+       if (partial->parents)
+               hashcpy(parent_sha1, partial->parents->item->object.sha1);
+       else
+               hashclr(parent_sha1);
+
+       t = xcalloc(1, sizeof(struct notes_tree));
+       init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0);
+
+       o->local_ref = resolve_ref("NOTES_MERGE_REF", sha1, 0, NULL);
+       if (!o->local_ref)
+               die("Failed to resolve NOTES_MERGE_REF");
+
+       if (notes_merge_commit(o, t, partial, sha1))
+               die("Failed to finalize notes merge");
+
+       /* Reuse existing commit message in reflog message */
+       memset(&pretty_ctx, 0, sizeof(pretty_ctx));
+       format_commit_message(partial, "%s", &msg, &pretty_ctx);
+       strbuf_trim(&msg);
+       strbuf_insert(&msg, 0, "notes: ", 7);
+       update_ref(msg.buf, o->local_ref, sha1,
+                  is_null_sha1(parent_sha1) ? NULL : parent_sha1,
+                  0, DIE_ON_ERR);
+
+       free_notes(t);
+       strbuf_release(&msg);
+       return merge_abort(o);
+}
+
 static int merge(int argc, const char **argv, const char *prefix)
 {
        struct strbuf remote_ref = STRBUF_INIT, msg = STRBUF_INIT;
        unsigned char result_sha1[20];
+       struct notes_tree *t;
        struct notes_merge_options o;
+       int do_merge = 0, do_commit = 0, do_abort = 0;
        int verbosity = 0, result;
+       const char *strategy = NULL;
        struct option options[] = {
+               OPT_GROUP("General options"),
                OPT__VERBOSITY(&verbosity),
+               OPT_GROUP("Merge options"),
+               OPT_STRING('s', "strategy", &strategy, "strategy",
+                          "resolve notes conflicts using the given strategy "
+                          "(manual/ours/theirs/union/cat_sort_uniq)"),
+               OPT_GROUP("Committing unmerged notes"),
+               { OPTION_BOOLEAN, 0, "commit", &do_commit, NULL,
+                       "finalize notes merge by committing unmerged notes",
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG },
+               OPT_GROUP("Aborting notes merge resolution"),
+               { OPTION_BOOLEAN, 0, "abort", &do_abort, NULL,
+                       "abort notes merge",
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG },
                OPT_END()
        };
 
        argc = parse_options(argc, argv, prefix, options,
                             git_notes_merge_usage, 0);
 
-       if (argc != 1) {
+       if (strategy || do_commit + do_abort == 0)
+               do_merge = 1;
+       if (do_merge + do_commit + do_abort != 1) {
+               error("cannot mix --commit, --abort or -s/--strategy");
+               usage_with_options(git_notes_merge_usage, options);
+       }
+
+       if (do_merge && argc != 1) {
                error("Must specify a notes ref to merge");
                usage_with_options(git_notes_merge_usage, options);
+       } else if (!do_merge && argc) {
+               error("too many parameters");
+               usage_with_options(git_notes_merge_usage, options);
        }
 
        init_notes_merge_options(&o);
        o.verbosity = verbosity + NOTES_MERGE_VERBOSITY_DEFAULT;
 
+       if (do_abort)
+               return merge_abort(&o);
+       if (do_commit)
+               return merge_commit(&o);
+
        o.local_ref = default_notes_ref();
        strbuf_addstr(&remote_ref, argv[0]);
        expand_notes_ref(&remote_ref);
        o.remote_ref = remote_ref.buf;
 
-       result = notes_merge(&o, result_sha1);
+       if (strategy) {
+               if (!strcmp(strategy, "manual"))
+                       o.strategy = NOTES_MERGE_RESOLVE_MANUAL;
+               else if (!strcmp(strategy, "ours"))
+                       o.strategy = NOTES_MERGE_RESOLVE_OURS;
+               else if (!strcmp(strategy, "theirs"))
+                       o.strategy = NOTES_MERGE_RESOLVE_THEIRS;
+               else if (!strcmp(strategy, "union"))
+                       o.strategy = NOTES_MERGE_RESOLVE_UNION;
+               else if (!strcmp(strategy, "cat_sort_uniq"))
+                       o.strategy = NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ;
+               else {
+                       error("Unknown -s/--strategy: %s", strategy);
+                       usage_with_options(git_notes_merge_usage, options);
+               }
+       }
+
+       t = init_notes_check("merge");
 
        strbuf_addf(&msg, "notes: Merged notes from %s into %s",
                    remote_ref.buf, default_notes_ref());
-       if (result == 0) { /* Merge resulted (trivially) in result_sha1 */
+       strbuf_add(&(o.commit_msg), msg.buf + 7, msg.len - 7); /* skip "notes: " */
+
+       result = notes_merge(&o, t, result_sha1);
+
+       if (result >= 0) /* Merge resulted (trivially) in result_sha1 */
                /* Update default notes ref with new commit */
                update_ref(msg.buf, default_notes_ref(), result_sha1, NULL,
                           0, DIE_ON_ERR);
-       } else {
-               /* TODO: */
-               die("'git notes merge' cannot yet handle non-trivial merges!");
+       else { /* Merge has unresolved conflicts */
+               /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
+               update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL,
+                          0, DIE_ON_ERR);
+               /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
+               if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
+                       die("Failed to store link to current notes ref (%s)",
+                           default_notes_ref());
+               printf("Automatic notes merge failed. Fix conflicts in %s and "
+                      "commit the result with 'git notes merge --commit', or "
+                      "abort the merge with 'git notes merge --abort'.\n",
+                      git_path(NOTES_MERGE_WORKTREE));
        }
 
+       free_notes(t);
        strbuf_release(&remote_ref);
        strbuf_release(&msg);
-       return 0;
+       return result < 0; /* return non-zero on conflicts */
 }
 
 static int remove_cmd(int argc, const char **argv, const char *prefix)
@@ -814,28 +961,34 @@ static int remove_cmd(int argc, const char **argv, const char *prefix)
        const char *object_ref;
        struct notes_tree *t;
        unsigned char object[20];
+       int retval;
 
        argc = parse_options(argc, argv, prefix, options,
                             git_notes_remove_usage, 0);
 
        if (1 < argc) {
-               error("too many parameters");
+               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);
+               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);
+       retval = remove_note(t, object);
+       if (retval)
+               fprintf(stderr, _("Object %s has no note\n"), sha1_to_hex(object));
+       else {
+               fprintf(stderr, _("Removing note for object %s\n"),
+                       sha1_to_hex(object));
 
-       commit_notes(t, "Notes removed by 'git notes remove'");
+               commit_notes(t, "Notes removed by 'git notes remove'");
+       }
        free_notes(t);
-       return 0;
+       return retval;
 }
 
 static int prune(int argc, const char **argv, const char *prefix)
@@ -843,9 +996,8 @@ static int prune(int argc, const char **argv, const char *prefix)
        struct notes_tree *t;
        int show_only = 0, verbose = 0;
        struct option options[] = {
-               OPT_BOOLEAN('n', "dry-run", &show_only,
-                           "do not remove, show only"),
-               OPT_BOOLEAN('v', "verbose", &verbose, "report pruned notes"),
+               OPT__DRY_RUN(&show_only, "do not remove, show only"),
+               OPT__VERBOSE(&verbose, "report pruned notes"),
                OPT_END()
        };
 
@@ -853,7 +1005,7 @@ static int prune(int argc, const char **argv, const char *prefix)
                             0);
 
        if (argc) {
-               error("too many parameters");
+               error(_("too many parameters"));
                usage_with_options(git_notes_prune_usage, options);
        }
 
@@ -867,6 +1019,21 @@ static int prune(int argc, const char **argv, const char *prefix)
        return 0;
 }
 
+static int get_ref(int argc, const char **argv, const char *prefix)
+{
+       struct option options[] = { OPT_END() };
+       argc = parse_options(argc, argv, prefix, options,
+                            git_notes_get_ref_usage, 0);
+
+       if (argc) {
+               error("too many parameters");
+               usage_with_options(git_notes_get_ref_usage, options);
+       }
+
+       puts(default_notes_ref());
+       return 0;
+}
+
 int cmd_notes(int argc, const char **argv, const char *prefix)
 {
        int result;
@@ -905,8 +1072,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
                result = remove_cmd(argc, argv, prefix);
        else if (!strcmp(argv[0], "prune"))
                result = prune(argc, argv, prefix);
+       else if (!strcmp(argv[0], "get-ref"))
+               result = get_ref(argc, argv, prefix);
        else {
-               result = error("Unknown subcommand: %s", argv[0]);
+               result = error(_("Unknown subcommand: %s"), argv[0]);
                usage_with_options(git_notes_usage, options);
        }