builtin-notes.con commit builtin-notes: Add "append" subcommand for appending to note objects (2347fae)
   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
  20static const char * const git_notes_usage[] = {
  21        "git notes [list [<object>]]",
  22        "git notes add [-f] [-m <msg> | -F <file>] [<object>]",
  23        "git notes append [-m <msg> | -F <file>] [<object>]",
  24        "git notes edit [-m <msg> | -F <file>] [<object>]",
  25        "git notes show [<object>]",
  26        "git notes remove [<object>]",
  27        "git notes prune",
  28        NULL
  29};
  30
  31static const char note_template[] =
  32        "\n"
  33        "#\n"
  34        "# Write/edit the notes for the following object:\n"
  35        "#\n";
  36
  37static int list_each_note(const unsigned char *object_sha1,
  38                const unsigned char *note_sha1, char *note_path,
  39                void *cb_data)
  40{
  41        printf("%s %s\n", sha1_to_hex(note_sha1), sha1_to_hex(object_sha1));
  42        return 0;
  43}
  44
  45static void write_note_data(int fd, const unsigned char *sha1)
  46{
  47        unsigned long size;
  48        enum object_type type;
  49        char *buf = read_sha1_file(sha1, &type, &size);
  50        if (buf) {
  51                if (size)
  52                        write_or_die(fd, buf, size);
  53                free(buf);
  54        }
  55}
  56
  57static void write_commented_object(int fd, const unsigned char *object)
  58{
  59        const char *show_args[5] =
  60                {"show", "--stat", "--no-notes", sha1_to_hex(object), NULL};
  61        struct child_process show;
  62        struct strbuf buf = STRBUF_INIT;
  63        FILE *show_out;
  64
  65        /* Invoke "git show --stat --no-notes $object" */
  66        memset(&show, 0, sizeof(show));
  67        show.argv = show_args;
  68        show.no_stdin = 1;
  69        show.out = -1;
  70        show.err = 0;
  71        show.git_cmd = 1;
  72        if (start_command(&show))
  73                die("unable to start 'show' for object '%s'",
  74                    sha1_to_hex(object));
  75
  76        /* Open the output as FILE* so strbuf_getline() can be used. */
  77        show_out = xfdopen(show.out, "r");
  78        if (show_out == NULL)
  79                die_errno("can't fdopen 'show' output fd");
  80
  81        /* Prepend "# " to each output line and write result to 'fd' */
  82        while (strbuf_getline(&buf, show_out, '\n') != EOF) {
  83                write_or_die(fd, "# ", 2);
  84                write_or_die(fd, buf.buf, buf.len);
  85                write_or_die(fd, "\n", 1);
  86        }
  87        strbuf_release(&buf);
  88        if (fclose(show_out))
  89                die_errno("failed to close pipe to 'show' for object '%s'",
  90                          sha1_to_hex(object));
  91        if (finish_command(&show))
  92                die("failed to finish 'show' for object '%s'",
  93                    sha1_to_hex(object));
  94}
  95
  96static void create_note(const unsigned char *object,
  97                        struct strbuf *buf,
  98                        int skip_editor, int append_only,
  99                        const unsigned char *prev,
 100                        unsigned char *result)
 101{
 102        char *path = NULL;
 103
 104        if (!skip_editor) {
 105                int fd;
 106
 107                /* write the template message before editing: */
 108                path = git_pathdup("NOTES_EDITMSG");
 109                fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
 110                if (fd < 0)
 111                        die_errno("could not create file '%s'", path);
 112
 113                if (prev && !append_only)
 114                        write_note_data(fd, prev);
 115                write_or_die(fd, note_template, strlen(note_template));
 116
 117                write_commented_object(fd, object);
 118
 119                close(fd);
 120
 121                if (launch_editor(path, buf, NULL)) {
 122                        die("Please supply the note contents using either -m" \
 123                            " or -F option");
 124                }
 125        }
 126
 127        stripspace(buf, 1);
 128
 129        if (prev && append_only) {
 130                /* Append buf to previous note contents */
 131                unsigned long size;
 132                enum object_type type;
 133                char *prev_buf = read_sha1_file(prev, &type, &size);
 134
 135                strbuf_grow(buf, size + 1);
 136                if (buf->len && prev_buf && size)
 137                        strbuf_insert(buf, 0, "\n", 1);
 138                if (prev_buf && size)
 139                        strbuf_insert(buf, 0, prev_buf, size);
 140                free(prev_buf);
 141        }
 142
 143        if (!buf->len) {
 144                fprintf(stderr, "Removing note for object %s\n",
 145                        sha1_to_hex(object));
 146                hashclr(result);
 147        } else {
 148                if (write_sha1_file(buf->buf, buf->len, blob_type, result)) {
 149                        error("unable to write note object");
 150                        if (path)
 151                                error("The note contents has been left in %s",
 152                                      path);
 153                        exit(128);
 154                }
 155        }
 156
 157        if (path) {
 158                unlink_or_warn(path);
 159                free(path);
 160        }
 161}
 162
 163struct msg_arg {
 164        int given;
 165        struct strbuf buf;
 166};
 167
 168static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
 169{
 170        struct msg_arg *msg = opt->value;
 171
 172        if (!arg)
 173                return -1;
 174        if (msg->buf.len)
 175                strbuf_addstr(&(msg->buf), "\n\n");
 176        strbuf_addstr(&(msg->buf), arg);
 177        msg->given = 1;
 178        return 0;
 179}
 180
 181int commit_notes(struct notes_tree *t, const char *msg)
 182{
 183        struct commit_list *parent;
 184        unsigned char tree_sha1[20], prev_commit[20], new_commit[20];
 185        struct strbuf buf = STRBUF_INIT;
 186
 187        if (!t)
 188                t = &default_notes_tree;
 189        if (!t->initialized || !t->ref || !*t->ref)
 190                die("Cannot commit uninitialized/unreferenced notes tree");
 191
 192        /* Prepare commit message and reflog message */
 193        strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */
 194        strbuf_addstr(&buf, msg);
 195        if (buf.buf[buf.len - 1] != '\n')
 196                strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */
 197
 198        /* Convert notes tree to tree object */
 199        if (write_notes_tree(t, tree_sha1))
 200                die("Failed to write current notes tree to database");
 201
 202        /* Create new commit for the tree object */
 203        if (!read_ref(t->ref, prev_commit)) { /* retrieve parent commit */
 204                parent = xmalloc(sizeof(*parent));
 205                parent->item = lookup_commit(prev_commit);
 206                parent->next = NULL;
 207        } else {
 208                hashclr(prev_commit);
 209                parent = NULL;
 210        }
 211        if (commit_tree(buf.buf + 7, tree_sha1, parent, new_commit, NULL))
 212                die("Failed to commit notes tree to database");
 213
 214        /* Update notes ref with new commit */
 215        update_ref(buf.buf, t->ref, new_commit, prev_commit, 0, DIE_ON_ERR);
 216
 217        strbuf_release(&buf);
 218        return 0;
 219}
 220
 221int cmd_notes(int argc, const char **argv, const char *prefix)
 222{
 223        struct strbuf buf = STRBUF_INIT;
 224        struct notes_tree *t;
 225        unsigned char object[20], new_note[20];
 226        const unsigned char *note;
 227        const char *object_ref;
 228        char logmsg[100];
 229
 230        int list = 0, add = 0, append = 0, edit = 0, show = 0, remove = 0,
 231            prune = 0, force = 0;
 232        int given_object;
 233        const char *msgfile = NULL;
 234        struct msg_arg msg = { 0, STRBUF_INIT };
 235        struct option options[] = {
 236                OPT_GROUP("Notes edit options"),
 237                OPT_CALLBACK('m', "message", &msg, "msg",
 238                             "note contents as a string", parse_msg_arg),
 239                OPT_FILENAME('F', "file", &msgfile, "note contents in a file"),
 240                OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
 241                OPT_END()
 242        };
 243
 244        git_config(git_default_config, NULL);
 245
 246        argc = parse_options(argc, argv, prefix, options, git_notes_usage, 0);
 247
 248        if (argc && !strcmp(argv[0], "list"))
 249                list = 1;
 250        else if (argc && !strcmp(argv[0], "add"))
 251                add = 1;
 252        else if (argc && !strcmp(argv[0], "append"))
 253                append = 1;
 254        else if (argc && !strcmp(argv[0], "edit"))
 255                edit = 1;
 256        else if (argc && !strcmp(argv[0], "show"))
 257                show = 1;
 258        else if (argc && !strcmp(argv[0], "remove"))
 259                remove = 1;
 260        else if (argc && !strcmp(argv[0], "prune"))
 261                prune = 1;
 262        else if (!argc)
 263                list = 1; /* Default to 'list' if no other subcommand given */
 264
 265        if (list + add + append + edit + show + remove + prune != 1)
 266                usage_with_options(git_notes_usage, options);
 267
 268        if ((msg.given || msgfile) && !(add || append || edit)) {
 269                error("cannot use -m/-F options with %s subcommand.", argv[0]);
 270                usage_with_options(git_notes_usage, options);
 271        }
 272
 273        if (msg.given && msgfile) {
 274                error("mixing -m and -F options is not allowed.");
 275                usage_with_options(git_notes_usage, options);
 276        }
 277
 278        if (force && !add) {
 279                error("cannot use -f option with %s subcommand.", argv[0]);
 280                usage_with_options(git_notes_usage, options);
 281        }
 282
 283        given_object = argc == 2;
 284        object_ref = given_object ? argv[1] : "HEAD";
 285        if (argc > 2 || (prune && argc > 1)) {
 286                error("too many parameters");
 287                usage_with_options(git_notes_usage, options);
 288        }
 289
 290        if (get_sha1(object_ref, object))
 291                die("Failed to resolve '%s' as a valid ref.", object_ref);
 292
 293        init_notes(NULL, NULL, NULL, 0);
 294        t = &default_notes_tree;
 295
 296        if (prefixcmp(t->ref, "refs/notes/"))
 297                die("Refusing to %s notes in %s (outside of refs/notes/)",
 298                    argv[0], t->ref);
 299
 300        note = get_note(t, object);
 301
 302        /* list command */
 303
 304        if (list) {
 305                if (given_object) {
 306                        if (note) {
 307                                puts(sha1_to_hex(note));
 308                                return 0;
 309                        }
 310                } else
 311                        return for_each_note(t, 0, list_each_note, NULL);
 312        }
 313
 314        /* show command */
 315
 316        if ((list || show) && !note) {
 317                error("No note found for object %s.", sha1_to_hex(object));
 318                return 1;
 319        } else if (show) {
 320                const char *show_args[3] = {"show", sha1_to_hex(note), NULL};
 321                return execv_git_cmd(show_args);
 322        }
 323
 324        /* add/append/edit/remove/prune command */
 325
 326        if (add && note) {
 327                if (force)
 328                        fprintf(stderr, "Overwriting existing notes for object %s\n",
 329                                sha1_to_hex(object));
 330                else {
 331                        error("Cannot add notes. Found existing notes for object %s. "
 332                              "Use '-f' to overwrite existing notes",
 333                              sha1_to_hex(object));
 334                        return 1;
 335                }
 336        }
 337
 338        if (remove)
 339                strbuf_reset(&buf);
 340        else if (msg.given)
 341                strbuf_addbuf(&buf, &(msg.buf));
 342        else if (msgfile) {
 343                if (!strcmp(msgfile, "-")) {
 344                        if (strbuf_read(&buf, 0, 1024) < 0)
 345                                die_errno("cannot read '%s'", msgfile);
 346                } else if (strbuf_read_file(&buf, msgfile, 1024) < 0)
 347                        die_errno("could not open or read '%s'", msgfile);
 348        }
 349
 350        if (prune) {
 351                hashclr(new_note);
 352                prune_notes(t);
 353        } else {
 354                create_note(object, &buf, msg.given || msgfile || remove,
 355                            append, note, new_note);
 356                if (is_null_sha1(new_note))
 357                        remove_note(t, object);
 358                else
 359                        add_note(t, object, new_note, combine_notes_overwrite);
 360        }
 361        snprintf(logmsg, sizeof(logmsg), "Note %s by 'git notes %s'",
 362                 is_null_sha1(new_note) ? "removed" : "added", argv[0]);
 363        commit_notes(t, logmsg);
 364
 365        free_notes(t);
 366        strbuf_release(&buf);
 367        return 0;
 368}