Merge branch 'bw/pathspec-sans-the-index'
[gitweb.git] / builtin / tag.c
index 1705c9466546c7de52b12d3fbafbbe7b6d43cf4e..1f74a56db749a5e1ffa06715e347cdc1b041033b 100644 (file)
 static const char * const git_tag_usage[] = {
        N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"),
        N_("git tag -d <tagname>..."),
-       N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]"
+       N_("git tag -l [-n[<num>]] [--contains <commit>] [--no-contains <commit>] [--points-at <object>]"
                "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"),
-       N_("git tag -v <tagname>..."),
+       N_("git tag -v [--format=<format>] <tagname>..."),
        NULL
 };
 
 static unsigned int colopts;
+static int force_sign_annotate;
 
 static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format)
 {
@@ -44,11 +45,11 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, con
        if (!format) {
                if (filter->lines) {
                        to_free = xstrfmt("%s %%(contents:lines=%d)",
-                                         "%(align:15)%(refname:strip=2)%(end)",
+                                         "%(align:15)%(refname:lstrip=2)%(end)",
                                          filter->lines);
                        format = to_free;
                } else
-                       format = "%(refname:strip=2)";
+                       format = "%(refname:lstrip=2)";
        }
 
        verify_ref_format(format);
@@ -65,51 +66,56 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, con
 }
 
 typedef int (*each_tag_name_fn)(const char *name, const char *ref,
-                               const unsigned char *sha1);
+                               const struct object_id *oid, const void *cb_data);
 
-static int for_each_tag_name(const char **argv, each_tag_name_fn fn)
+static int for_each_tag_name(const char **argv, each_tag_name_fn fn,
+                            const void *cb_data)
 {
        const char **p;
-       char ref[PATH_MAX];
+       struct strbuf ref = STRBUF_INIT;
        int had_error = 0;
-       unsigned char sha1[20];
+       struct object_id oid;
 
        for (p = argv; *p; p++) {
-               if (snprintf(ref, sizeof(ref), "refs/tags/%s", *p)
-                                       >= sizeof(ref)) {
-                       error(_("tag name too long: %.*s..."), 50, *p);
-                       had_error = 1;
-                       continue;
-               }
-               if (read_ref(ref, sha1)) {
+               strbuf_reset(&ref);
+               strbuf_addf(&ref, "refs/tags/%s", *p);
+               if (read_ref(ref.buf, oid.hash)) {
                        error(_("tag '%s' not found."), *p);
                        had_error = 1;
                        continue;
                }
-               if (fn(*p, ref, sha1))
+               if (fn(*p, ref.buf, &oid, cb_data))
                        had_error = 1;
        }
+       strbuf_release(&ref);
        return had_error;
 }
 
 static int delete_tag(const char *name, const char *ref,
-                               const unsigned char *sha1)
+                     const struct object_id *oid, const void *cb_data)
 {
-       if (delete_ref(ref, sha1, 0))
+       if (delete_ref(NULL, ref, oid->hash, 0))
                return 1;
-       printf(_("Deleted tag '%s' (was %s)\n"), name, find_unique_abbrev(sha1, DEFAULT_ABBREV));
+       printf(_("Deleted tag '%s' (was %s)\n"), name, find_unique_abbrev(oid->hash, DEFAULT_ABBREV));
        return 0;
 }
 
 static int verify_tag(const char *name, const char *ref,
-                               const unsigned char *sha1)
+                     const struct object_id *oid, const void *cb_data)
 {
-       const char *argv_verify_tag[] = {"verify-tag",
-                                       "-v", "SHA1_HEX", NULL};
-       argv_verify_tag[2] = sha1_to_hex(sha1);
+       int flags;
+       const char *fmt_pretty = cb_data;
+       flags = GPG_VERIFY_VERBOSE;
+
+       if (fmt_pretty)
+               flags = GPG_VERIFY_OMIT_STATUS;
+
+       if (gpg_verify_tag(oid->hash, name, flags))
+               return -1;
+
+       if (fmt_pretty)
+               pretty_print_ref(name, oid->hash, fmt_pretty);
 
-       if (run_command_v_opt(argv_verify_tag, RUN_GIT_CMD))
-               return error(_("could not verify the tag '%s'"), name);
        return 0;
 }
 
@@ -166,18 +172,23 @@ static int git_tag_config(const char *var, const char *value, void *cb)
        status = git_gpg_config(var, value, cb);
        if (status)
                return status;
+       if (!strcmp(var, "tag.forcesignannotated")) {
+               force_sign_annotate = git_config_bool(var, value);
+               return 0;
+       }
+
        if (starts_with(var, "column."))
                return git_column_config(var, value, "tag", &colopts);
        return git_default_config(var, value, cb);
 }
 
-static void write_tag_body(int fd, const unsigned char *sha1)
+static void write_tag_body(int fd, const struct object_id *oid)
 {
        unsigned long size;
        enum object_type type;
        char *buf, *sp;
 
-       buf = read_sha1_file(sha1, &type, &size);
+       buf = read_sha1_file(oid->hash, &type, &size);
        if (!buf)
                return;
        /* skip header */
@@ -193,11 +204,11 @@ static void write_tag_body(int fd, const unsigned char *sha1)
        free(buf);
 }
 
-static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result)
+static int build_tag_object(struct strbuf *buf, int sign, struct object_id *result)
 {
        if (sign && do_sign(buf) < 0)
                return error(_("unable to sign the tag"));
-       if (write_sha1_file(buf->buf, buf->len, tag_type, result) < 0)
+       if (write_sha1_file(buf->buf, buf->len, tag_type, result->hash) < 0)
                return error(_("unable to write tag file"));
        return 0;
 }
@@ -212,31 +223,27 @@ struct create_tag_options {
        } cleanup_mode;
 };
 
-static void create_tag(const unsigned char *object, const char *tag,
+static void create_tag(const struct object_id *object, const char *tag,
                       struct strbuf *buf, struct create_tag_options *opt,
-                      unsigned char *prev, unsigned char *result)
+                      struct object_id *prev, struct object_id *result)
 {
        enum object_type type;
-       char header_buf[1024];
-       int header_len;
+       struct strbuf header = STRBUF_INIT;
        char *path = NULL;
 
-       type = sha1_object_info(object, NULL);
+       type = sha1_object_info(object->hash, NULL);
        if (type <= OBJ_NONE)
            die(_("bad object type."));
 
-       header_len = snprintf(header_buf, sizeof(header_buf),
-                         "object %s\n"
-                         "type %s\n"
-                         "tag %s\n"
-                         "tagger %s\n\n",
-                         sha1_to_hex(object),
-                         typename(type),
-                         tag,
-                         git_committer_info(IDENT_STRICT));
-
-       if (header_len > sizeof(header_buf) - 1)
-               die(_("tag header too big."));
+       strbuf_addf(&header,
+                   "object %s\n"
+                   "type %s\n"
+                   "tag %s\n"
+                   "tagger %s\n\n",
+                   oid_to_hex(object),
+                   typename(type),
+                   tag,
+                   git_committer_info(IDENT_STRICT));
 
        if (!opt->message_given) {
                int fd;
@@ -247,7 +254,7 @@ static void create_tag(const unsigned char *object, const char *tag,
                if (fd < 0)
                        die_errno(_("could not create file '%s'"), path);
 
-               if (!is_null_sha1(prev)) {
+               if (!is_null_oid(prev)) {
                        write_tag_body(fd, prev);
                } else {
                        struct strbuf buf = STRBUF_INIT;
@@ -274,7 +281,8 @@ static void create_tag(const unsigned char *object, const char *tag,
        if (!opt->message_given && !buf->len)
                die(_("no tag message?"));
 
-       strbuf_insert(buf, 0, header_buf, header_len);
+       strbuf_insert(buf, 0, header.buf, header.len);
+       strbuf_release(&header);
 
        if (build_tag_object(buf, opt->sign, result) < 0) {
                if (path)
@@ -288,6 +296,54 @@ static void create_tag(const unsigned char *object, const char *tag,
        }
 }
 
+static void create_reflog_msg(const struct object_id *oid, struct strbuf *sb)
+{
+       enum object_type type;
+       struct commit *c;
+       char *buf;
+       unsigned long size;
+       int subject_len = 0;
+       const char *subject_start;
+
+       char *rla = getenv("GIT_REFLOG_ACTION");
+       if (rla) {
+               strbuf_addstr(sb, rla);
+       } else {
+               strbuf_addstr(sb, "tag: tagging ");
+               strbuf_add_unique_abbrev(sb, oid->hash, DEFAULT_ABBREV);
+       }
+
+       strbuf_addstr(sb, " (");
+       type = sha1_object_info(oid->hash, NULL);
+       switch (type) {
+       default:
+               strbuf_addstr(sb, "object of unknown type");
+               break;
+       case OBJ_COMMIT:
+               if ((buf = read_sha1_file(oid->hash, &type, &size)) != NULL) {
+                       subject_len = find_commit_subject(buf, &subject_start);
+                       strbuf_insert(sb, sb->len, subject_start, subject_len);
+               } else {
+                       strbuf_addstr(sb, "commit object");
+               }
+               free(buf);
+
+               if ((c = lookup_commit_reference(oid)) != NULL)
+                       strbuf_addf(sb, ", %s", show_date(c->date, 0, DATE_MODE(SHORT)));
+               break;
+       case OBJ_TREE:
+               strbuf_addstr(sb, "tree object");
+               break;
+       case OBJ_BLOB:
+               strbuf_addstr(sb, "blob object");
+               break;
+       case OBJ_TAG:
+               strbuf_addstr(sb, "other tag object");
+               break;
+       }
+       strbuf_addch(sb, ')');
+}
+
 struct msg_arg {
        int given;
        struct strbuf buf;
@@ -321,13 +377,14 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 {
        struct strbuf buf = STRBUF_INIT;
        struct strbuf ref = STRBUF_INIT;
-       unsigned char object[20], prev[20];
+       struct strbuf reflog_msg = STRBUF_INIT;
+       struct object_id object, prev;
        const char *object_ref, *tag;
        struct create_tag_options opt;
        char *cleanup_arg = NULL;
        int create_reflog = 0;
        int annotate = 0, force = 0;
-       int cmdmode = 0;
+       int cmdmode = 0, create_tag_object = 0;
        const char *msgfile = NULL, *keyid = NULL;
        struct msg_arg msg = { 0, STRBUF_INIT };
        struct ref_transaction *transaction;
@@ -335,6 +392,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        struct ref_filter filter;
        static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
        const char *format = NULL;
+       int icase = 0;
        struct option options[] = {
                OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
                { OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"),
@@ -360,19 +418,25 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                OPT_GROUP(N_("Tag listing options")),
                OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
                OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")),
+               OPT_NO_CONTAINS(&filter.no_commit, N_("print only tags that don't contain the commit")),
                OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")),
+               OPT_WITHOUT(&filter.no_commit, N_("print only tags that don't contain the commit")),
                OPT_MERGED(&filter, N_("print only tags that are merged")),
                OPT_NO_MERGED(&filter, N_("print only tags that are not merged")),
                OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
                             N_("field name to sort on"), &parse_opt_ref_sorting),
                {
                        OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
-                       N_("print only tags of the object"), 0, parse_opt_object_name
+                       N_("print only tags of the object"), PARSE_OPT_LASTARG_DEFAULT,
+                       parse_opt_object_name, (intptr_t) "HEAD"
                },
                OPT_STRING(  0 , "format", &format, N_("format"), N_("format to use for the output")),
+               OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
                OPT_END()
        };
 
+       setup_ref_filter_porcelain_msg();
+
        git_config(git_tag_config, sorting_tail);
 
        memset(&opt, 0, sizeof(opt));
@@ -385,12 +449,18 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                opt.sign = 1;
                set_signing_key(keyid);
        }
-       if (opt.sign)
-               annotate = 1;
-       if (argc == 0 && !cmdmode)
-               cmdmode = 'l';
+       create_tag_object = (opt.sign || annotate || msg.given || msgfile);
+
+       if (!cmdmode) {
+               if (argc == 0)
+                       cmdmode = 'l';
+               else if (filter.with_commit || filter.no_commit ||
+                        filter.points_at.nr || filter.merge_commit ||
+                        filter.lines != -1)
+                       cmdmode = 'l';
+       }
 
-       if ((annotate || msg.given || msgfile || force) && (cmdmode != 0))
+       if ((create_tag_object || force) && (cmdmode != 0))
                usage_with_options(git_tag_usage, options);
 
        finalize_colopts(&colopts, -1);
@@ -401,6 +471,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        }
        if (!sorting)
                sorting = ref_default_sorting();
+       sorting->ignore_case = icase;
+       filter.ignore_case = icase;
        if (cmdmode == 'l') {
                int ret;
                if (column_active(colopts)) {
@@ -416,22 +488,26 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                return ret;
        }
        if (filter.lines != -1)
-               die(_("-n option is only allowed with -l."));
+               die(_("-n option is only allowed in list mode"));
        if (filter.with_commit)
-               die(_("--contains option is only allowed with -l."));
+               die(_("--contains option is only allowed in list mode"));
+       if (filter.no_commit)
+               die(_("--no-contains option is only allowed in list mode"));
        if (filter.points_at.nr)
-               die(_("--points-at option is only allowed with -l."));
+               die(_("--points-at option is only allowed in list mode"));
        if (filter.merge_commit)
-               die(_("--merged and --no-merged option are only allowed with -l"));
+               die(_("--merged and --no-merged options are only allowed in list mode"));
        if (cmdmode == 'd')
-               return for_each_tag_name(argv, delete_tag);
-       if (cmdmode == 'v')
-               return for_each_tag_name(argv, verify_tag);
+               return for_each_tag_name(argv, delete_tag, NULL);
+       if (cmdmode == 'v') {
+               if (format)
+                       verify_ref_format(format);
+               return for_each_tag_name(argv, verify_tag, format);
+       }
 
        if (msg.given || msgfile) {
                if (msg.given && msgfile)
                        die(_("only one -F or -m option is allowed."));
-               annotate = 1;
                if (msg.given)
                        strbuf_addbuf(&buf, &(msg.buf));
                else {
@@ -452,14 +528,14 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        if (argc > 2)
                die(_("too many params"));
 
-       if (get_sha1(object_ref, object))
+       if (get_oid(object_ref, &object))
                die(_("Failed to resolve '%s' as a valid ref."), object_ref);
 
        if (strbuf_check_tag_ref(&ref, tag))
                die(_("'%s' is not a valid tag name."), tag);
 
-       if (read_ref(ref.buf, prev))
-               hashclr(prev);
+       if (read_ref(ref.buf, prev.hash))
+               oidclr(&prev);
        else if (!force)
                die(_("tag '%s' already exists"), tag);
 
@@ -474,22 +550,28 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        else
                die(_("Invalid cleanup mode %s"), cleanup_arg);
 
-       if (annotate)
-               create_tag(object, tag, &buf, &opt, prev, object);
+       create_reflog_msg(&object, &reflog_msg);
+
+       if (create_tag_object) {
+               if (force_sign_annotate && !annotate)
+                       opt.sign = 1;
+               create_tag(&object, tag, &buf, &opt, &prev, &object);
+       }
 
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
-           ref_transaction_update(transaction, ref.buf, object, prev,
+           ref_transaction_update(transaction, ref.buf, object.hash, prev.hash,
                                   create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
-                                  NULL, &err) ||
+                                  reflog_msg.buf, &err) ||
            ref_transaction_commit(transaction, &err))
                die("%s", err.buf);
        ref_transaction_free(transaction);
-       if (force && !is_null_sha1(prev) && hashcmp(prev, object))
-               printf(_("Updated tag '%s' (was %s)\n"), tag, find_unique_abbrev(prev, DEFAULT_ABBREV));
+       if (force && !is_null_oid(&prev) && oidcmp(&prev, &object))
+               printf(_("Updated tag '%s' (was %s)\n"), tag, find_unique_abbrev(prev.hash, DEFAULT_ABBREV));
 
        strbuf_release(&err);
        strbuf_release(&buf);
        strbuf_release(&ref);
+       strbuf_release(&reflog_msg);
        return 0;
 }