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