builtin / tag.con commit submodule: port submodule subcommand 'status' from shell to C (a9f8a37)
   1/*
   2 * Builtin "git tag"
   3 *
   4 * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>,
   5 *                    Carlos Rica <jasampler@gmail.com>
   6 * Based on git-tag.sh and mktag.c by Linus Torvalds.
   7 */
   8
   9#include "cache.h"
  10#include "config.h"
  11#include "builtin.h"
  12#include "refs.h"
  13#include "tag.h"
  14#include "run-command.h"
  15#include "parse-options.h"
  16#include "diff.h"
  17#include "revision.h"
  18#include "gpg-interface.h"
  19#include "sha1-array.h"
  20#include "column.h"
  21#include "ref-filter.h"
  22
  23static const char * const git_tag_usage[] = {
  24        N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"),
  25        N_("git tag -d <tagname>..."),
  26        N_("git tag -l [-n[<num>]] [--contains <commit>] [--no-contains <commit>] [--points-at <object>]"
  27                "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"),
  28        N_("git tag -v [--format=<format>] <tagname>..."),
  29        NULL
  30};
  31
  32static unsigned int colopts;
  33static int force_sign_annotate;
  34
  35static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting,
  36                     struct ref_format *format)
  37{
  38        struct ref_array array;
  39        char *to_free = NULL;
  40        int i;
  41
  42        memset(&array, 0, sizeof(array));
  43
  44        if (filter->lines == -1)
  45                filter->lines = 0;
  46
  47        if (!format->format) {
  48                if (filter->lines) {
  49                        to_free = xstrfmt("%s %%(contents:lines=%d)",
  50                                          "%(align:15)%(refname:lstrip=2)%(end)",
  51                                          filter->lines);
  52                        format->format = to_free;
  53                } else
  54                        format->format = "%(refname:lstrip=2)";
  55        }
  56
  57        if (verify_ref_format(format))
  58                die(_("unable to parse format string"));
  59        filter->with_commit_tag_algo = 1;
  60        filter_refs(&array, filter, FILTER_REFS_TAGS);
  61        ref_array_sort(sorting, &array);
  62
  63        for (i = 0; i < array.nr; i++)
  64                show_ref_array_item(array.items[i], format);
  65        ref_array_clear(&array);
  66        free(to_free);
  67
  68        return 0;
  69}
  70
  71typedef int (*each_tag_name_fn)(const char *name, const char *ref,
  72                                const struct object_id *oid, const void *cb_data);
  73
  74static int for_each_tag_name(const char **argv, each_tag_name_fn fn,
  75                             const void *cb_data)
  76{
  77        const char **p;
  78        struct strbuf ref = STRBUF_INIT;
  79        int had_error = 0;
  80        struct object_id oid;
  81
  82        for (p = argv; *p; p++) {
  83                strbuf_reset(&ref);
  84                strbuf_addf(&ref, "refs/tags/%s", *p);
  85                if (read_ref(ref.buf, oid.hash)) {
  86                        error(_("tag '%s' not found."), *p);
  87                        had_error = 1;
  88                        continue;
  89                }
  90                if (fn(*p, ref.buf, &oid, cb_data))
  91                        had_error = 1;
  92        }
  93        strbuf_release(&ref);
  94        return had_error;
  95}
  96
  97static int delete_tag(const char *name, const char *ref,
  98                      const struct object_id *oid, const void *cb_data)
  99{
 100        if (delete_ref(NULL, ref, oid->hash, 0))
 101                return 1;
 102        printf(_("Deleted tag '%s' (was %s)\n"), name, find_unique_abbrev(oid->hash, DEFAULT_ABBREV));
 103        return 0;
 104}
 105
 106static int verify_tag(const char *name, const char *ref,
 107                      const struct object_id *oid, const void *cb_data)
 108{
 109        int flags;
 110        const struct ref_format *format = cb_data;
 111        flags = GPG_VERIFY_VERBOSE;
 112
 113        if (format->format)
 114                flags = GPG_VERIFY_OMIT_STATUS;
 115
 116        if (gpg_verify_tag(oid, name, flags))
 117                return -1;
 118
 119        if (format->format)
 120                pretty_print_ref(name, oid->hash, format);
 121
 122        return 0;
 123}
 124
 125static int do_sign(struct strbuf *buffer)
 126{
 127        return sign_buffer(buffer, buffer, get_signing_key());
 128}
 129
 130static const char tag_template[] =
 131        N_("\nWrite a message for tag:\n  %s\n"
 132        "Lines starting with '%c' will be ignored.\n");
 133
 134static const char tag_template_nocleanup[] =
 135        N_("\nWrite a message for tag:\n  %s\n"
 136        "Lines starting with '%c' will be kept; you may remove them"
 137        " yourself if you want to.\n");
 138
 139static int git_tag_config(const char *var, const char *value, void *cb)
 140{
 141        int status;
 142        struct ref_sorting **sorting_tail = (struct ref_sorting **)cb;
 143
 144        if (!strcmp(var, "tag.sort")) {
 145                if (!value)
 146                        return config_error_nonbool(var);
 147                parse_ref_sorting(sorting_tail, value);
 148                return 0;
 149        }
 150
 151        status = git_gpg_config(var, value, cb);
 152        if (status)
 153                return status;
 154        if (!strcmp(var, "tag.forcesignannotated")) {
 155                force_sign_annotate = git_config_bool(var, value);
 156                return 0;
 157        }
 158
 159        if (starts_with(var, "column."))
 160                return git_column_config(var, value, "tag", &colopts);
 161        return git_default_config(var, value, cb);
 162}
 163
 164static void write_tag_body(int fd, const struct object_id *oid)
 165{
 166        unsigned long size;
 167        enum object_type type;
 168        char *buf, *sp;
 169
 170        buf = read_sha1_file(oid->hash, &type, &size);
 171        if (!buf)
 172                return;
 173        /* skip header */
 174        sp = strstr(buf, "\n\n");
 175
 176        if (!sp || !size || type != OBJ_TAG) {
 177                free(buf);
 178                return;
 179        }
 180        sp += 2; /* skip the 2 LFs */
 181        write_or_die(fd, sp, parse_signature(sp, buf + size - sp));
 182
 183        free(buf);
 184}
 185
 186static int build_tag_object(struct strbuf *buf, int sign, struct object_id *result)
 187{
 188        if (sign && do_sign(buf) < 0)
 189                return error(_("unable to sign the tag"));
 190        if (write_sha1_file(buf->buf, buf->len, tag_type, result->hash) < 0)
 191                return error(_("unable to write tag file"));
 192        return 0;
 193}
 194
 195struct create_tag_options {
 196        unsigned int message_given:1;
 197        unsigned int sign;
 198        enum {
 199                CLEANUP_NONE,
 200                CLEANUP_SPACE,
 201                CLEANUP_ALL
 202        } cleanup_mode;
 203};
 204
 205static void create_tag(const struct object_id *object, const char *tag,
 206                       struct strbuf *buf, struct create_tag_options *opt,
 207                       struct object_id *prev, struct object_id *result)
 208{
 209        enum object_type type;
 210        struct strbuf header = STRBUF_INIT;
 211        char *path = NULL;
 212
 213        type = sha1_object_info(object->hash, NULL);
 214        if (type <= OBJ_NONE)
 215            die(_("bad object type."));
 216
 217        strbuf_addf(&header,
 218                    "object %s\n"
 219                    "type %s\n"
 220                    "tag %s\n"
 221                    "tagger %s\n\n",
 222                    oid_to_hex(object),
 223                    typename(type),
 224                    tag,
 225                    git_committer_info(IDENT_STRICT));
 226
 227        if (!opt->message_given) {
 228                int fd;
 229
 230                /* write the template message before editing: */
 231                path = git_pathdup("TAG_EDITMSG");
 232                fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
 233                if (fd < 0)
 234                        die_errno(_("could not create file '%s'"), path);
 235
 236                if (!is_null_oid(prev)) {
 237                        write_tag_body(fd, prev);
 238                } else {
 239                        struct strbuf buf = STRBUF_INIT;
 240                        strbuf_addch(&buf, '\n');
 241                        if (opt->cleanup_mode == CLEANUP_ALL)
 242                                strbuf_commented_addf(&buf, _(tag_template), tag, comment_line_char);
 243                        else
 244                                strbuf_commented_addf(&buf, _(tag_template_nocleanup), tag, comment_line_char);
 245                        write_or_die(fd, buf.buf, buf.len);
 246                        strbuf_release(&buf);
 247                }
 248                close(fd);
 249
 250                if (launch_editor(path, buf, NULL)) {
 251                        fprintf(stderr,
 252                        _("Please supply the message using either -m or -F option.\n"));
 253                        exit(1);
 254                }
 255        }
 256
 257        if (opt->cleanup_mode != CLEANUP_NONE)
 258                strbuf_stripspace(buf, opt->cleanup_mode == CLEANUP_ALL);
 259
 260        if (!opt->message_given && !buf->len)
 261                die(_("no tag message?"));
 262
 263        strbuf_insert(buf, 0, header.buf, header.len);
 264        strbuf_release(&header);
 265
 266        if (build_tag_object(buf, opt->sign, result) < 0) {
 267                if (path)
 268                        fprintf(stderr, _("The tag message has been left in %s\n"),
 269                                path);
 270                exit(128);
 271        }
 272        if (path) {
 273                unlink_or_warn(path);
 274                free(path);
 275        }
 276}
 277
 278static void create_reflog_msg(const struct object_id *oid, struct strbuf *sb)
 279{
 280        enum object_type type;
 281        struct commit *c;
 282        char *buf;
 283        unsigned long size;
 284        int subject_len = 0;
 285        const char *subject_start;
 286
 287        char *rla = getenv("GIT_REFLOG_ACTION");
 288        if (rla) {
 289                strbuf_addstr(sb, rla);
 290        } else {
 291                strbuf_addstr(sb, "tag: tagging ");
 292                strbuf_add_unique_abbrev(sb, oid->hash, DEFAULT_ABBREV);
 293        }
 294
 295        strbuf_addstr(sb, " (");
 296        type = sha1_object_info(oid->hash, NULL);
 297        switch (type) {
 298        default:
 299                strbuf_addstr(sb, "object of unknown type");
 300                break;
 301        case OBJ_COMMIT:
 302                if ((buf = read_sha1_file(oid->hash, &type, &size)) != NULL) {
 303                        subject_len = find_commit_subject(buf, &subject_start);
 304                        strbuf_insert(sb, sb->len, subject_start, subject_len);
 305                } else {
 306                        strbuf_addstr(sb, "commit object");
 307                }
 308                free(buf);
 309
 310                if ((c = lookup_commit_reference(oid)) != NULL)
 311                        strbuf_addf(sb, ", %s", show_date(c->date, 0, DATE_MODE(SHORT)));
 312                break;
 313        case OBJ_TREE:
 314                strbuf_addstr(sb, "tree object");
 315                break;
 316        case OBJ_BLOB:
 317                strbuf_addstr(sb, "blob object");
 318                break;
 319        case OBJ_TAG:
 320                strbuf_addstr(sb, "other tag object");
 321                break;
 322        }
 323        strbuf_addch(sb, ')');
 324}
 325
 326struct msg_arg {
 327        int given;
 328        struct strbuf buf;
 329};
 330
 331static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
 332{
 333        struct msg_arg *msg = opt->value;
 334
 335        if (!arg)
 336                return -1;
 337        if (msg->buf.len)
 338                strbuf_addstr(&(msg->buf), "\n\n");
 339        strbuf_addstr(&(msg->buf), arg);
 340        msg->given = 1;
 341        return 0;
 342}
 343
 344static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)
 345{
 346        if (name[0] == '-')
 347                return -1;
 348
 349        strbuf_reset(sb);
 350        strbuf_addf(sb, "refs/tags/%s", name);
 351
 352        return check_refname_format(sb->buf, 0);
 353}
 354
 355int cmd_tag(int argc, const char **argv, const char *prefix)
 356{
 357        struct strbuf buf = STRBUF_INIT;
 358        struct strbuf ref = STRBUF_INIT;
 359        struct strbuf reflog_msg = STRBUF_INIT;
 360        struct object_id object, prev;
 361        const char *object_ref, *tag;
 362        struct create_tag_options opt;
 363        char *cleanup_arg = NULL;
 364        int create_reflog = 0;
 365        int annotate = 0, force = 0;
 366        int cmdmode = 0, create_tag_object = 0;
 367        const char *msgfile = NULL, *keyid = NULL;
 368        struct msg_arg msg = { 0, STRBUF_INIT };
 369        struct ref_transaction *transaction;
 370        struct strbuf err = STRBUF_INIT;
 371        struct ref_filter filter;
 372        static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
 373        struct ref_format format = REF_FORMAT_INIT;
 374        int icase = 0;
 375        struct option options[] = {
 376                OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
 377                { OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"),
 378                                N_("print <n> lines of each tag message"),
 379                                PARSE_OPT_OPTARG, NULL, 1 },
 380                OPT_CMDMODE('d', "delete", &cmdmode, N_("delete tags"), 'd'),
 381                OPT_CMDMODE('v', "verify", &cmdmode, N_("verify tags"), 'v'),
 382
 383                OPT_GROUP(N_("Tag creation options")),
 384                OPT_BOOL('a', "annotate", &annotate,
 385                                        N_("annotated tag, needs a message")),
 386                OPT_CALLBACK('m', "message", &msg, N_("message"),
 387                             N_("tag message"), parse_msg_arg),
 388                OPT_FILENAME('F', "file", &msgfile, N_("read message from file")),
 389                OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")),
 390                OPT_STRING(0, "cleanup", &cleanup_arg, N_("mode"),
 391                        N_("how to strip spaces and #comments from message")),
 392                OPT_STRING('u', "local-user", &keyid, N_("key-id"),
 393                                        N_("use another key to sign the tag")),
 394                OPT__FORCE(&force, N_("replace the tag if exists")),
 395                OPT_BOOL(0, "create-reflog", &create_reflog, N_("create a reflog")),
 396
 397                OPT_GROUP(N_("Tag listing options")),
 398                OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
 399                OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")),
 400                OPT_NO_CONTAINS(&filter.no_commit, N_("print only tags that don't contain the commit")),
 401                OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")),
 402                OPT_WITHOUT(&filter.no_commit, N_("print only tags that don't contain the commit")),
 403                OPT_MERGED(&filter, N_("print only tags that are merged")),
 404                OPT_NO_MERGED(&filter, N_("print only tags that are not merged")),
 405                OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
 406                             N_("field name to sort on"), &parse_opt_ref_sorting),
 407                {
 408                        OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
 409                        N_("print only tags of the object"), PARSE_OPT_LASTARG_DEFAULT,
 410                        parse_opt_object_name, (intptr_t) "HEAD"
 411                },
 412                OPT_STRING(  0 , "format", &format.format, N_("format"),
 413                           N_("format to use for the output")),
 414                OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
 415                OPT_END()
 416        };
 417
 418        setup_ref_filter_porcelain_msg();
 419
 420        git_config(git_tag_config, sorting_tail);
 421
 422        memset(&opt, 0, sizeof(opt));
 423        memset(&filter, 0, sizeof(filter));
 424        filter.lines = -1;
 425
 426        argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
 427
 428        if (keyid) {
 429                opt.sign = 1;
 430                set_signing_key(keyid);
 431        }
 432        create_tag_object = (opt.sign || annotate || msg.given || msgfile);
 433
 434        if (!cmdmode) {
 435                if (argc == 0)
 436                        cmdmode = 'l';
 437                else if (filter.with_commit || filter.no_commit ||
 438                         filter.points_at.nr || filter.merge_commit ||
 439                         filter.lines != -1)
 440                        cmdmode = 'l';
 441        }
 442
 443        if (cmdmode == 'l')
 444                setup_auto_pager("tag", 1);
 445
 446        if ((create_tag_object || force) && (cmdmode != 0))
 447                usage_with_options(git_tag_usage, options);
 448
 449        finalize_colopts(&colopts, -1);
 450        if (cmdmode == 'l' && filter.lines != -1) {
 451                if (explicitly_enable_column(colopts))
 452                        die(_("--column and -n are incompatible"));
 453                colopts = 0;
 454        }
 455        if (!sorting)
 456                sorting = ref_default_sorting();
 457        sorting->ignore_case = icase;
 458        filter.ignore_case = icase;
 459        if (cmdmode == 'l') {
 460                int ret;
 461                if (column_active(colopts)) {
 462                        struct column_options copts;
 463                        memset(&copts, 0, sizeof(copts));
 464                        copts.padding = 2;
 465                        run_column_filter(colopts, &copts);
 466                }
 467                filter.name_patterns = argv;
 468                ret = list_tags(&filter, sorting, &format);
 469                if (column_active(colopts))
 470                        stop_column_filter();
 471                return ret;
 472        }
 473        if (filter.lines != -1)
 474                die(_("-n option is only allowed in list mode"));
 475        if (filter.with_commit)
 476                die(_("--contains option is only allowed in list mode"));
 477        if (filter.no_commit)
 478                die(_("--no-contains option is only allowed in list mode"));
 479        if (filter.points_at.nr)
 480                die(_("--points-at option is only allowed in list mode"));
 481        if (filter.merge_commit)
 482                die(_("--merged and --no-merged options are only allowed in list mode"));
 483        if (cmdmode == 'd')
 484                return for_each_tag_name(argv, delete_tag, NULL);
 485        if (cmdmode == 'v') {
 486                if (format.format && verify_ref_format(&format))
 487                        usage_with_options(git_tag_usage, options);
 488                return for_each_tag_name(argv, verify_tag, &format);
 489        }
 490
 491        if (msg.given || msgfile) {
 492                if (msg.given && msgfile)
 493                        die(_("only one -F or -m option is allowed."));
 494                if (msg.given)
 495                        strbuf_addbuf(&buf, &(msg.buf));
 496                else {
 497                        if (!strcmp(msgfile, "-")) {
 498                                if (strbuf_read(&buf, 0, 1024) < 0)
 499                                        die_errno(_("cannot read '%s'"), msgfile);
 500                        } else {
 501                                if (strbuf_read_file(&buf, msgfile, 1024) < 0)
 502                                        die_errno(_("could not open or read '%s'"),
 503                                                msgfile);
 504                        }
 505                }
 506        }
 507
 508        tag = argv[0];
 509
 510        object_ref = argc == 2 ? argv[1] : "HEAD";
 511        if (argc > 2)
 512                die(_("too many params"));
 513
 514        if (get_oid(object_ref, &object))
 515                die(_("Failed to resolve '%s' as a valid ref."), object_ref);
 516
 517        if (strbuf_check_tag_ref(&ref, tag))
 518                die(_("'%s' is not a valid tag name."), tag);
 519
 520        if (read_ref(ref.buf, prev.hash))
 521                oidclr(&prev);
 522        else if (!force)
 523                die(_("tag '%s' already exists"), tag);
 524
 525        opt.message_given = msg.given || msgfile;
 526
 527        if (!cleanup_arg || !strcmp(cleanup_arg, "strip"))
 528                opt.cleanup_mode = CLEANUP_ALL;
 529        else if (!strcmp(cleanup_arg, "verbatim"))
 530                opt.cleanup_mode = CLEANUP_NONE;
 531        else if (!strcmp(cleanup_arg, "whitespace"))
 532                opt.cleanup_mode = CLEANUP_SPACE;
 533        else
 534                die(_("Invalid cleanup mode %s"), cleanup_arg);
 535
 536        create_reflog_msg(&object, &reflog_msg);
 537
 538        if (create_tag_object) {
 539                if (force_sign_annotate && !annotate)
 540                        opt.sign = 1;
 541                create_tag(&object, tag, &buf, &opt, &prev, &object);
 542        }
 543
 544        transaction = ref_transaction_begin(&err);
 545        if (!transaction ||
 546            ref_transaction_update(transaction, ref.buf, object.hash, prev.hash,
 547                                   create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
 548                                   reflog_msg.buf, &err) ||
 549            ref_transaction_commit(transaction, &err))
 550                die("%s", err.buf);
 551        ref_transaction_free(transaction);
 552        if (force && !is_null_oid(&prev) && oidcmp(&prev, &object))
 553                printf(_("Updated tag '%s' (was %s)\n"), tag, find_unique_abbrev(prev.hash, DEFAULT_ABBREV));
 554
 555        strbuf_release(&err);
 556        strbuf_release(&buf);
 557        strbuf_release(&ref);
 558        strbuf_release(&reflog_msg);
 559        return 0;
 560}