builtin / notes.con commit git notes merge: Initial implementation handling trivial merges only (75ef3f4)
   1/*
   2 * Builtin "git notes"
   3 *
   4 * Copyright (c) 2010 Johan Herland <johan@herland.net>
   5 *
   6 * Based on git-notes.sh by Johannes Schindelin,
   7 * and builtin-tag.c by Kristian Høgsberg and Carlos Rica.
   8 */
   9
  10#include "cache.h"
  11#include "builtin.h"
  12#include "notes.h"
  13#include "blob.h"
  14#include "commit.h"
  15#include "refs.h"
  16#include "exec_cmd.h"
  17#include "run-command.h"
  18#include "parse-options.h"
  19#include "string-list.h"
  20#include "notes-merge.h"
  21
  22static const char * const git_notes_usage[] = {
  23        "git notes [--ref <notes_ref>] [list [<object>]]",
  24        "git notes [--ref <notes_ref>] add [-f] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
  25        "git notes [--ref <notes_ref>] copy [-f] <from-object> <to-object>",
  26        "git notes [--ref <notes_ref>] append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
  27        "git notes [--ref <notes_ref>] edit [<object>]",
  28        "git notes [--ref <notes_ref>] show [<object>]",
  29        "git notes [--ref <notes_ref>] merge [-v | -q] <notes_ref>",
  30        "git notes [--ref <notes_ref>] remove [<object>]",
  31        "git notes [--ref <notes_ref>] prune [-n | -v]",
  32        NULL
  33};
  34
  35static const char * const git_notes_list_usage[] = {
  36        "git notes [list [<object>]]",
  37        NULL
  38};
  39
  40static const char * const git_notes_add_usage[] = {
  41        "git notes add [<options>] [<object>]",
  42        NULL
  43};
  44
  45static const char * const git_notes_copy_usage[] = {
  46        "git notes copy [<options>] <from-object> <to-object>",
  47        "git notes copy --stdin [<from-object> <to-object>]...",
  48        NULL
  49};
  50
  51static const char * const git_notes_append_usage[] = {
  52        "git notes append [<options>] [<object>]",
  53        NULL
  54};
  55
  56static const char * const git_notes_edit_usage[] = {
  57        "git notes edit [<object>]",
  58        NULL
  59};
  60
  61static const char * const git_notes_show_usage[] = {
  62        "git notes show [<object>]",
  63        NULL
  64};
  65
  66static const char * const git_notes_merge_usage[] = {
  67        "git notes merge [<options>] <notes_ref>",
  68        NULL
  69};
  70
  71static const char * const git_notes_remove_usage[] = {
  72        "git notes remove [<object>]",
  73        NULL
  74};
  75
  76static const char * const git_notes_prune_usage[] = {
  77        "git notes prune [<options>]",
  78        NULL
  79};
  80
  81static const char note_template[] =
  82        "\n"
  83        "#\n"
  84        "# Write/edit the notes for the following object:\n"
  85        "#\n";
  86
  87struct msg_arg {
  88        int given;
  89        int use_editor;
  90        struct strbuf buf;
  91};
  92
  93static void expand_notes_ref(struct strbuf *sb)
  94{
  95        if (!prefixcmp(sb->buf, "refs/notes/"))
  96                return; /* we're happy */
  97        else if (!prefixcmp(sb->buf, "notes/"))
  98                strbuf_insert(sb, 0, "refs/", 5);
  99        else
 100                strbuf_insert(sb, 0, "refs/notes/", 11);
 101}
 102
 103static int list_each_note(const unsigned char *object_sha1,
 104                const unsigned char *note_sha1, char *note_path,
 105                void *cb_data)
 106{
 107        printf("%s %s\n", sha1_to_hex(note_sha1), sha1_to_hex(object_sha1));
 108        return 0;
 109}
 110
 111static void write_note_data(int fd, const unsigned char *sha1)
 112{
 113        unsigned long size;
 114        enum object_type type;
 115        char *buf = read_sha1_file(sha1, &type, &size);
 116        if (buf) {
 117                if (size)
 118                        write_or_die(fd, buf, size);
 119                free(buf);
 120        }
 121}
 122
 123static void write_commented_object(int fd, const unsigned char *object)
 124{
 125        const char *show_args[5] =
 126                {"show", "--stat", "--no-notes", sha1_to_hex(object), NULL};
 127        struct child_process show;
 128        struct strbuf buf = STRBUF_INIT;
 129        FILE *show_out;
 130
 131        /* Invoke "git show --stat --no-notes $object" */
 132        memset(&show, 0, sizeof(show));
 133        show.argv = show_args;
 134        show.no_stdin = 1;
 135        show.out = -1;
 136        show.err = 0;
 137        show.git_cmd = 1;
 138        if (start_command(&show))
 139                die("unable to start 'show' for object '%s'",
 140                    sha1_to_hex(object));
 141
 142        /* Open the output as FILE* so strbuf_getline() can be used. */
 143        show_out = xfdopen(show.out, "r");
 144        if (show_out == NULL)
 145                die_errno("can't fdopen 'show' output fd");
 146
 147        /* Prepend "# " to each output line and write result to 'fd' */
 148        while (strbuf_getline(&buf, show_out, '\n') != EOF) {
 149                write_or_die(fd, "# ", 2);
 150                write_or_die(fd, buf.buf, buf.len);
 151                write_or_die(fd, "\n", 1);
 152        }
 153        strbuf_release(&buf);
 154        if (fclose(show_out))
 155                die_errno("failed to close pipe to 'show' for object '%s'",
 156                          sha1_to_hex(object));
 157        if (finish_command(&show))
 158                die("failed to finish 'show' for object '%s'",
 159                    sha1_to_hex(object));
 160}
 161
 162static void create_note(const unsigned char *object, struct msg_arg *msg,
 163                        int append_only, const unsigned char *prev,
 164                        unsigned char *result)
 165{
 166        char *path = NULL;
 167
 168        if (msg->use_editor || !msg->given) {
 169                int fd;
 170
 171                /* write the template message before editing: */
 172                path = git_pathdup("NOTES_EDITMSG");
 173                fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
 174                if (fd < 0)
 175                        die_errno("could not create file '%s'", path);
 176
 177                if (msg->given)
 178                        write_or_die(fd, msg->buf.buf, msg->buf.len);
 179                else if (prev && !append_only)
 180                        write_note_data(fd, prev);
 181                write_or_die(fd, note_template, strlen(note_template));
 182
 183                write_commented_object(fd, object);
 184
 185                close(fd);
 186                strbuf_reset(&(msg->buf));
 187
 188                if (launch_editor(path, &(msg->buf), NULL)) {
 189                        die("Please supply the note contents using either -m" \
 190                            " or -F option");
 191                }
 192                stripspace(&(msg->buf), 1);
 193        }
 194
 195        if (prev && append_only) {
 196                /* Append buf to previous note contents */
 197                unsigned long size;
 198                enum object_type type;
 199                char *prev_buf = read_sha1_file(prev, &type, &size);
 200
 201                strbuf_grow(&(msg->buf), size + 1);
 202                if (msg->buf.len && prev_buf && size)
 203                        strbuf_insert(&(msg->buf), 0, "\n", 1);
 204                if (prev_buf && size)
 205                        strbuf_insert(&(msg->buf), 0, prev_buf, size);
 206                free(prev_buf);
 207        }
 208
 209        if (!msg->buf.len) {
 210                fprintf(stderr, "Removing note for object %s\n",
 211                        sha1_to_hex(object));
 212                hashclr(result);
 213        } else {
 214                if (write_sha1_file(msg->buf.buf, msg->buf.len, blob_type, result)) {
 215                        error("unable to write note object");
 216                        if (path)
 217                                error("The note contents has been left in %s",
 218                                      path);
 219                        exit(128);
 220                }
 221        }
 222
 223        if (path) {
 224                unlink_or_warn(path);
 225                free(path);
 226        }
 227}
 228
 229static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
 230{
 231        struct msg_arg *msg = opt->value;
 232
 233        strbuf_grow(&(msg->buf), strlen(arg) + 2);
 234        if (msg->buf.len)
 235                strbuf_addch(&(msg->buf), '\n');
 236        strbuf_addstr(&(msg->buf), arg);
 237        stripspace(&(msg->buf), 0);
 238
 239        msg->given = 1;
 240        return 0;
 241}
 242
 243static int parse_file_arg(const struct option *opt, const char *arg, int unset)
 244{
 245        struct msg_arg *msg = opt->value;
 246
 247        if (msg->buf.len)
 248                strbuf_addch(&(msg->buf), '\n');
 249        if (!strcmp(arg, "-")) {
 250                if (strbuf_read(&(msg->buf), 0, 1024) < 0)
 251                        die_errno("cannot read '%s'", arg);
 252        } else if (strbuf_read_file(&(msg->buf), arg, 1024) < 0)
 253                die_errno("could not open or read '%s'", arg);
 254        stripspace(&(msg->buf), 0);
 255
 256        msg->given = 1;
 257        return 0;
 258}
 259
 260static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)
 261{
 262        struct msg_arg *msg = opt->value;
 263        char *buf;
 264        unsigned char object[20];
 265        enum object_type type;
 266        unsigned long len;
 267
 268        if (msg->buf.len)
 269                strbuf_addch(&(msg->buf), '\n');
 270
 271        if (get_sha1(arg, object))
 272                die("Failed to resolve '%s' as a valid ref.", arg);
 273        if (!(buf = read_sha1_file(object, &type, &len)) || !len) {
 274                free(buf);
 275                die("Failed to read object '%s'.", arg);;
 276        }
 277        strbuf_add(&(msg->buf), buf, len);
 278        free(buf);
 279
 280        msg->given = 1;
 281        return 0;
 282}
 283
 284static int parse_reedit_arg(const struct option *opt, const char *arg, int unset)
 285{
 286        struct msg_arg *msg = opt->value;
 287        msg->use_editor = 1;
 288        return parse_reuse_arg(opt, arg, unset);
 289}
 290
 291int commit_notes(struct notes_tree *t, const char *msg)
 292{
 293        struct commit_list *parent;
 294        unsigned char tree_sha1[20], prev_commit[20], new_commit[20];
 295        struct strbuf buf = STRBUF_INIT;
 296
 297        if (!t)
 298                t = &default_notes_tree;
 299        if (!t->initialized || !t->ref || !*t->ref)
 300                die("Cannot commit uninitialized/unreferenced notes tree");
 301        if (!t->dirty)
 302                return 0; /* don't have to commit an unchanged tree */
 303
 304        /* Prepare commit message and reflog message */
 305        strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */
 306        strbuf_addstr(&buf, msg);
 307        if (buf.buf[buf.len - 1] != '\n')
 308                strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */
 309
 310        /* Convert notes tree to tree object */
 311        if (write_notes_tree(t, tree_sha1))
 312                die("Failed to write current notes tree to database");
 313
 314        /* Create new commit for the tree object */
 315        if (!read_ref(t->ref, prev_commit)) { /* retrieve parent commit */
 316                parent = xmalloc(sizeof(*parent));
 317                parent->item = lookup_commit(prev_commit);
 318                parent->next = NULL;
 319        } else {
 320                hashclr(prev_commit);
 321                parent = NULL;
 322        }
 323        if (commit_tree(buf.buf + 7, tree_sha1, parent, new_commit, NULL))
 324                die("Failed to commit notes tree to database");
 325
 326        /* Update notes ref with new commit */
 327        update_ref(buf.buf, t->ref, new_commit, prev_commit, 0, DIE_ON_ERR);
 328
 329        strbuf_release(&buf);
 330        return 0;
 331}
 332
 333combine_notes_fn parse_combine_notes_fn(const char *v)
 334{
 335        if (!strcasecmp(v, "overwrite"))
 336                return combine_notes_overwrite;
 337        else if (!strcasecmp(v, "ignore"))
 338                return combine_notes_ignore;
 339        else if (!strcasecmp(v, "concatenate"))
 340                return combine_notes_concatenate;
 341        else
 342                return NULL;
 343}
 344
 345static int notes_rewrite_config(const char *k, const char *v, void *cb)
 346{
 347        struct notes_rewrite_cfg *c = cb;
 348        if (!prefixcmp(k, "notes.rewrite.") && !strcmp(k+14, c->cmd)) {
 349                c->enabled = git_config_bool(k, v);
 350                return 0;
 351        } else if (!c->mode_from_env && !strcmp(k, "notes.rewritemode")) {
 352                if (!v)
 353                        config_error_nonbool(k);
 354                c->combine = parse_combine_notes_fn(v);
 355                if (!c->combine) {
 356                        error("Bad notes.rewriteMode value: '%s'", v);
 357                        return 1;
 358                }
 359                return 0;
 360        } else if (!c->refs_from_env && !strcmp(k, "notes.rewriteref")) {
 361                /* note that a refs/ prefix is implied in the
 362                 * underlying for_each_glob_ref */
 363                if (!prefixcmp(v, "refs/notes/"))
 364                        string_list_add_refs_by_glob(c->refs, v);
 365                else
 366                        warning("Refusing to rewrite notes in %s"
 367                                " (outside of refs/notes/)", v);
 368                return 0;
 369        }
 370
 371        return 0;
 372}
 373
 374
 375struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd)
 376{
 377        struct notes_rewrite_cfg *c = xmalloc(sizeof(struct notes_rewrite_cfg));
 378        const char *rewrite_mode_env = getenv(GIT_NOTES_REWRITE_MODE_ENVIRONMENT);
 379        const char *rewrite_refs_env = getenv(GIT_NOTES_REWRITE_REF_ENVIRONMENT);
 380        c->cmd = cmd;
 381        c->enabled = 1;
 382        c->combine = combine_notes_concatenate;
 383        c->refs = xcalloc(1, sizeof(struct string_list));
 384        c->refs->strdup_strings = 1;
 385        c->refs_from_env = 0;
 386        c->mode_from_env = 0;
 387        if (rewrite_mode_env) {
 388                c->mode_from_env = 1;
 389                c->combine = parse_combine_notes_fn(rewrite_mode_env);
 390                if (!c->combine)
 391                        error("Bad " GIT_NOTES_REWRITE_MODE_ENVIRONMENT
 392                              " value: '%s'", rewrite_mode_env);
 393        }
 394        if (rewrite_refs_env) {
 395                c->refs_from_env = 1;
 396                string_list_add_refs_from_colon_sep(c->refs, rewrite_refs_env);
 397        }
 398        git_config(notes_rewrite_config, c);
 399        if (!c->enabled || !c->refs->nr) {
 400                string_list_clear(c->refs, 0);
 401                free(c->refs);
 402                free(c);
 403                return NULL;
 404        }
 405        c->trees = load_notes_trees(c->refs);
 406        string_list_clear(c->refs, 0);
 407        free(c->refs);
 408        return c;
 409}
 410
 411int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
 412                          const unsigned char *from_obj, const unsigned char *to_obj)
 413{
 414        int ret = 0;
 415        int i;
 416        for (i = 0; c->trees[i]; i++)
 417                ret = copy_note(c->trees[i], from_obj, to_obj, 1, c->combine) || ret;
 418        return ret;
 419}
 420
 421void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c)
 422{
 423        int i;
 424        for (i = 0; c->trees[i]; i++) {
 425                commit_notes(c->trees[i], "Notes added by 'git notes copy'");
 426                free_notes(c->trees[i]);
 427        }
 428        free(c->trees);
 429        free(c);
 430}
 431
 432int notes_copy_from_stdin(int force, const char *rewrite_cmd)
 433{
 434        struct strbuf buf = STRBUF_INIT;
 435        struct notes_rewrite_cfg *c = NULL;
 436        struct notes_tree *t = NULL;
 437        int ret = 0;
 438
 439        if (rewrite_cmd) {
 440                c = init_copy_notes_for_rewrite(rewrite_cmd);
 441                if (!c)
 442                        return 0;
 443        } else {
 444                init_notes(NULL, NULL, NULL, 0);
 445                t = &default_notes_tree;
 446        }
 447
 448        while (strbuf_getline(&buf, stdin, '\n') != EOF) {
 449                unsigned char from_obj[20], to_obj[20];
 450                struct strbuf **split;
 451                int err;
 452
 453                split = strbuf_split(&buf, ' ');
 454                if (!split[0] || !split[1])
 455                        die("Malformed input line: '%s'.", buf.buf);
 456                strbuf_rtrim(split[0]);
 457                strbuf_rtrim(split[1]);
 458                if (get_sha1(split[0]->buf, from_obj))
 459                        die("Failed to resolve '%s' as a valid ref.", split[0]->buf);
 460                if (get_sha1(split[1]->buf, to_obj))
 461                        die("Failed to resolve '%s' as a valid ref.", split[1]->buf);
 462
 463                if (rewrite_cmd)
 464                        err = copy_note_for_rewrite(c, from_obj, to_obj);
 465                else
 466                        err = copy_note(t, from_obj, to_obj, force,
 467                                        combine_notes_overwrite);
 468
 469                if (err) {
 470                        error("Failed to copy notes from '%s' to '%s'",
 471                              split[0]->buf, split[1]->buf);
 472                        ret = 1;
 473                }
 474
 475                strbuf_list_free(split);
 476        }
 477
 478        if (!rewrite_cmd) {
 479                commit_notes(t, "Notes added by 'git notes copy'");
 480                free_notes(t);
 481        } else {
 482                finish_copy_notes_for_rewrite(c);
 483        }
 484        return ret;
 485}
 486
 487static struct notes_tree *init_notes_check(const char *subcommand)
 488{
 489        struct notes_tree *t;
 490        init_notes(NULL, NULL, NULL, 0);
 491        t = &default_notes_tree;
 492
 493        if (prefixcmp(t->ref, "refs/notes/"))
 494                die("Refusing to %s notes in %s (outside of refs/notes/)",
 495                    subcommand, t->ref);
 496        return t;
 497}
 498
 499static int list(int argc, const char **argv, const char *prefix)
 500{
 501        struct notes_tree *t;
 502        unsigned char object[20];
 503        const unsigned char *note;
 504        int retval = -1;
 505        struct option options[] = {
 506                OPT_END()
 507        };
 508
 509        if (argc)
 510                argc = parse_options(argc, argv, prefix, options,
 511                                     git_notes_list_usage, 0);
 512
 513        if (1 < argc) {
 514                error("too many parameters");
 515                usage_with_options(git_notes_list_usage, options);
 516        }
 517
 518        t = init_notes_check("list");
 519        if (argc) {
 520                if (get_sha1(argv[0], object))
 521                        die("Failed to resolve '%s' as a valid ref.", argv[0]);
 522                note = get_note(t, object);
 523                if (note) {
 524                        puts(sha1_to_hex(note));
 525                        retval = 0;
 526                } else
 527                        retval = error("No note found for object %s.",
 528                                       sha1_to_hex(object));
 529        } else
 530                retval = for_each_note(t, 0, list_each_note, NULL);
 531
 532        free_notes(t);
 533        return retval;
 534}
 535
 536static int add(int argc, const char **argv, const char *prefix)
 537{
 538        int retval = 0, force = 0;
 539        const char *object_ref;
 540        struct notes_tree *t;
 541        unsigned char object[20], new_note[20];
 542        char logmsg[100];
 543        const unsigned char *note;
 544        struct msg_arg msg = { 0, 0, STRBUF_INIT };
 545        struct option options[] = {
 546                { OPTION_CALLBACK, 'm', "message", &msg, "MSG",
 547                        "note contents as a string", PARSE_OPT_NONEG,
 548                        parse_msg_arg},
 549                { OPTION_CALLBACK, 'F', "file", &msg, "FILE",
 550                        "note contents in a file", PARSE_OPT_NONEG,
 551                        parse_file_arg},
 552                { OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT",
 553                        "reuse and edit specified note object", PARSE_OPT_NONEG,
 554                        parse_reedit_arg},
 555                { OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT",
 556                        "reuse specified note object", PARSE_OPT_NONEG,
 557                        parse_reuse_arg},
 558                OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
 559                OPT_END()
 560        };
 561
 562        argc = parse_options(argc, argv, prefix, options, git_notes_add_usage,
 563                             0);
 564
 565        if (1 < argc) {
 566                error("too many parameters");
 567                usage_with_options(git_notes_add_usage, options);
 568        }
 569
 570        object_ref = argc ? argv[0] : "HEAD";
 571
 572        if (get_sha1(object_ref, object))
 573                die("Failed to resolve '%s' as a valid ref.", object_ref);
 574
 575        t = init_notes_check("add");
 576        note = get_note(t, object);
 577
 578        if (note) {
 579                if (!force) {
 580                        retval = error("Cannot add notes. Found existing notes "
 581                                       "for object %s. Use '-f' to overwrite "
 582                                       "existing notes", sha1_to_hex(object));
 583                        goto out;
 584                }
 585                fprintf(stderr, "Overwriting existing notes for object %s\n",
 586                        sha1_to_hex(object));
 587        }
 588
 589        create_note(object, &msg, 0, note, new_note);
 590
 591        if (is_null_sha1(new_note))
 592                remove_note(t, object);
 593        else if (add_note(t, object, new_note, combine_notes_overwrite))
 594                die("BUG: combine_notes_overwrite failed");
 595
 596        snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
 597                 is_null_sha1(new_note) ? "removed" : "added", "add");
 598        commit_notes(t, logmsg);
 599out:
 600        free_notes(t);
 601        strbuf_release(&(msg.buf));
 602        return retval;
 603}
 604
 605static int copy(int argc, const char **argv, const char *prefix)
 606{
 607        int retval = 0, force = 0, from_stdin = 0;
 608        const unsigned char *from_note, *note;
 609        const char *object_ref;
 610        unsigned char object[20], from_obj[20];
 611        struct notes_tree *t;
 612        const char *rewrite_cmd = NULL;
 613        struct option options[] = {
 614                OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
 615                OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
 616                OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
 617                           "load rewriting config for <command> (implies "
 618                           "--stdin)"),
 619                OPT_END()
 620        };
 621
 622        argc = parse_options(argc, argv, prefix, options, git_notes_copy_usage,
 623                             0);
 624
 625        if (from_stdin || rewrite_cmd) {
 626                if (argc) {
 627                        error("too many parameters");
 628                        usage_with_options(git_notes_copy_usage, options);
 629                } else {
 630                        return notes_copy_from_stdin(force, rewrite_cmd);
 631                }
 632        }
 633
 634        if (argc < 2) {
 635                error("too few parameters");
 636                usage_with_options(git_notes_copy_usage, options);
 637        }
 638        if (2 < argc) {
 639                error("too many parameters");
 640                usage_with_options(git_notes_copy_usage, options);
 641        }
 642
 643        if (get_sha1(argv[0], from_obj))
 644                die("Failed to resolve '%s' as a valid ref.", argv[0]);
 645
 646        object_ref = 1 < argc ? argv[1] : "HEAD";
 647
 648        if (get_sha1(object_ref, object))
 649                die("Failed to resolve '%s' as a valid ref.", object_ref);
 650
 651        t = init_notes_check("copy");
 652        note = get_note(t, object);
 653
 654        if (note) {
 655                if (!force) {
 656                        retval = error("Cannot copy notes. Found existing "
 657                                       "notes for object %s. Use '-f' to "
 658                                       "overwrite existing notes",
 659                                       sha1_to_hex(object));
 660                        goto out;
 661                }
 662                fprintf(stderr, "Overwriting existing notes for object %s\n",
 663                        sha1_to_hex(object));
 664        }
 665
 666        from_note = get_note(t, from_obj);
 667        if (!from_note) {
 668                retval = error("Missing notes on source object %s. Cannot "
 669                               "copy.", sha1_to_hex(from_obj));
 670                goto out;
 671        }
 672
 673        if (add_note(t, object, from_note, combine_notes_overwrite))
 674                die("BUG: combine_notes_overwrite failed");
 675        commit_notes(t, "Notes added by 'git notes copy'");
 676out:
 677        free_notes(t);
 678        return retval;
 679}
 680
 681static int append_edit(int argc, const char **argv, const char *prefix)
 682{
 683        const char *object_ref;
 684        struct notes_tree *t;
 685        unsigned char object[20], new_note[20];
 686        const unsigned char *note;
 687        char logmsg[100];
 688        const char * const *usage;
 689        struct msg_arg msg = { 0, 0, STRBUF_INIT };
 690        struct option options[] = {
 691                { OPTION_CALLBACK, 'm', "message", &msg, "MSG",
 692                        "note contents as a string", PARSE_OPT_NONEG,
 693                        parse_msg_arg},
 694                { OPTION_CALLBACK, 'F', "file", &msg, "FILE",
 695                        "note contents in a file", PARSE_OPT_NONEG,
 696                        parse_file_arg},
 697                { OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT",
 698                        "reuse and edit specified note object", PARSE_OPT_NONEG,
 699                        parse_reedit_arg},
 700                { OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT",
 701                        "reuse specified note object", PARSE_OPT_NONEG,
 702                        parse_reuse_arg},
 703                OPT_END()
 704        };
 705        int edit = !strcmp(argv[0], "edit");
 706
 707        usage = edit ? git_notes_edit_usage : git_notes_append_usage;
 708        argc = parse_options(argc, argv, prefix, options, usage,
 709                             PARSE_OPT_KEEP_ARGV0);
 710
 711        if (2 < argc) {
 712                error("too many parameters");
 713                usage_with_options(usage, options);
 714        }
 715
 716        if (msg.given && edit)
 717                fprintf(stderr, "The -m/-F/-c/-C options have been deprecated "
 718                        "for the 'edit' subcommand.\n"
 719                        "Please use 'git notes add -f -m/-F/-c/-C' instead.\n");
 720
 721        object_ref = 1 < argc ? argv[1] : "HEAD";
 722
 723        if (get_sha1(object_ref, object))
 724                die("Failed to resolve '%s' as a valid ref.", object_ref);
 725
 726        t = init_notes_check(argv[0]);
 727        note = get_note(t, object);
 728
 729        create_note(object, &msg, !edit, note, new_note);
 730
 731        if (is_null_sha1(new_note))
 732                remove_note(t, object);
 733        else if (add_note(t, object, new_note, combine_notes_overwrite))
 734                die("BUG: combine_notes_overwrite failed");
 735
 736        snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
 737                 is_null_sha1(new_note) ? "removed" : "added", argv[0]);
 738        commit_notes(t, logmsg);
 739        free_notes(t);
 740        strbuf_release(&(msg.buf));
 741        return 0;
 742}
 743
 744static int show(int argc, const char **argv, const char *prefix)
 745{
 746        const char *object_ref;
 747        struct notes_tree *t;
 748        unsigned char object[20];
 749        const unsigned char *note;
 750        int retval;
 751        struct option options[] = {
 752                OPT_END()
 753        };
 754
 755        argc = parse_options(argc, argv, prefix, options, git_notes_show_usage,
 756                             0);
 757
 758        if (1 < argc) {
 759                error("too many parameters");
 760                usage_with_options(git_notes_show_usage, options);
 761        }
 762
 763        object_ref = argc ? argv[0] : "HEAD";
 764
 765        if (get_sha1(object_ref, object))
 766                die("Failed to resolve '%s' as a valid ref.", object_ref);
 767
 768        t = init_notes_check("show");
 769        note = get_note(t, object);
 770
 771        if (!note)
 772                retval = error("No note found for object %s.",
 773                               sha1_to_hex(object));
 774        else {
 775                const char *show_args[3] = {"show", sha1_to_hex(note), NULL};
 776                retval = execv_git_cmd(show_args);
 777        }
 778        free_notes(t);
 779        return retval;
 780}
 781
 782static int merge(int argc, const char **argv, const char *prefix)
 783{
 784        struct strbuf remote_ref = STRBUF_INIT, msg = STRBUF_INIT;
 785        unsigned char result_sha1[20];
 786        struct notes_merge_options o;
 787        int verbosity = 0, result;
 788        struct option options[] = {
 789                OPT__VERBOSITY(&verbosity),
 790                OPT_END()
 791        };
 792
 793        argc = parse_options(argc, argv, prefix, options,
 794                             git_notes_merge_usage, 0);
 795
 796        if (argc != 1) {
 797                error("Must specify a notes ref to merge");
 798                usage_with_options(git_notes_merge_usage, options);
 799        }
 800
 801        init_notes_merge_options(&o);
 802        o.verbosity = verbosity + NOTES_MERGE_VERBOSITY_DEFAULT;
 803
 804        o.local_ref = default_notes_ref();
 805        strbuf_addstr(&remote_ref, argv[0]);
 806        expand_notes_ref(&remote_ref);
 807        o.remote_ref = remote_ref.buf;
 808
 809        result = notes_merge(&o, result_sha1);
 810
 811        strbuf_addf(&msg, "notes: Merged notes from %s into %s",
 812                    remote_ref.buf, default_notes_ref());
 813        if (result == 0) { /* Merge resulted (trivially) in result_sha1 */
 814                /* Update default notes ref with new commit */
 815                update_ref(msg.buf, default_notes_ref(), result_sha1, NULL,
 816                           0, DIE_ON_ERR);
 817        } else {
 818                /* TODO: */
 819                die("'git notes merge' cannot yet handle non-trivial merges!");
 820        }
 821
 822        strbuf_release(&remote_ref);
 823        strbuf_release(&msg);
 824        return 0;
 825}
 826
 827static int remove_cmd(int argc, const char **argv, const char *prefix)
 828{
 829        struct option options[] = {
 830                OPT_END()
 831        };
 832        const char *object_ref;
 833        struct notes_tree *t;
 834        unsigned char object[20];
 835
 836        argc = parse_options(argc, argv, prefix, options,
 837                             git_notes_remove_usage, 0);
 838
 839        if (1 < argc) {
 840                error("too many parameters");
 841                usage_with_options(git_notes_remove_usage, options);
 842        }
 843
 844        object_ref = argc ? argv[0] : "HEAD";
 845
 846        if (get_sha1(object_ref, object))
 847                die("Failed to resolve '%s' as a valid ref.", object_ref);
 848
 849        t = init_notes_check("remove");
 850
 851        fprintf(stderr, "Removing note for object %s\n", sha1_to_hex(object));
 852        remove_note(t, object);
 853
 854        commit_notes(t, "Notes removed by 'git notes remove'");
 855        free_notes(t);
 856        return 0;
 857}
 858
 859static int prune(int argc, const char **argv, const char *prefix)
 860{
 861        struct notes_tree *t;
 862        int show_only = 0, verbose = 0;
 863        struct option options[] = {
 864                OPT_BOOLEAN('n', "dry-run", &show_only,
 865                            "do not remove, show only"),
 866                OPT_BOOLEAN('v', "verbose", &verbose, "report pruned notes"),
 867                OPT_END()
 868        };
 869
 870        argc = parse_options(argc, argv, prefix, options, git_notes_prune_usage,
 871                             0);
 872
 873        if (argc) {
 874                error("too many parameters");
 875                usage_with_options(git_notes_prune_usage, options);
 876        }
 877
 878        t = init_notes_check("prune");
 879
 880        prune_notes(t, (verbose ? NOTES_PRUNE_VERBOSE : 0) |
 881                (show_only ? NOTES_PRUNE_VERBOSE|NOTES_PRUNE_DRYRUN : 0) );
 882        if (!show_only)
 883                commit_notes(t, "Notes removed by 'git notes prune'");
 884        free_notes(t);
 885        return 0;
 886}
 887
 888int cmd_notes(int argc, const char **argv, const char *prefix)
 889{
 890        int result;
 891        const char *override_notes_ref = NULL;
 892        struct option options[] = {
 893                OPT_STRING(0, "ref", &override_notes_ref, "notes_ref",
 894                           "use notes from <notes_ref>"),
 895                OPT_END()
 896        };
 897
 898        git_config(git_default_config, NULL);
 899        argc = parse_options(argc, argv, prefix, options, git_notes_usage,
 900                             PARSE_OPT_STOP_AT_NON_OPTION);
 901
 902        if (override_notes_ref) {
 903                struct strbuf sb = STRBUF_INIT;
 904                strbuf_addstr(&sb, override_notes_ref);
 905                expand_notes_ref(&sb);
 906                setenv("GIT_NOTES_REF", sb.buf, 1);
 907                strbuf_release(&sb);
 908        }
 909
 910        if (argc < 1 || !strcmp(argv[0], "list"))
 911                result = list(argc, argv, prefix);
 912        else if (!strcmp(argv[0], "add"))
 913                result = add(argc, argv, prefix);
 914        else if (!strcmp(argv[0], "copy"))
 915                result = copy(argc, argv, prefix);
 916        else if (!strcmp(argv[0], "append") || !strcmp(argv[0], "edit"))
 917                result = append_edit(argc, argv, prefix);
 918        else if (!strcmp(argv[0], "show"))
 919                result = show(argc, argv, prefix);
 920        else if (!strcmp(argv[0], "merge"))
 921                result = merge(argc, argv, prefix);
 922        else if (!strcmp(argv[0], "remove"))
 923                result = remove_cmd(argc, argv, prefix);
 924        else if (!strcmp(argv[0], "prune"))
 925                result = prune(argc, argv, prefix);
 926        else {
 927                result = error("Unknown subcommand: %s", argv[0]);
 928                usage_with_options(git_notes_usage, options);
 929        }
 930
 931        return result ? 1 : 0;
 932}