real_path_internal(): add comment explaining use of cwd
[gitweb.git] / builtin / tag.c
index 03df16ac6e0e492483bf3695e0f6b2aa0a95978b..9c3e0673d5bd4e011c877c4d3a17975498265e22 100644 (file)
 #include "diff.h"
 #include "revision.h"
 #include "gpg-interface.h"
+#include "sha1-array.h"
+#include "column.h"
 
 static const char * const git_tag_usage[] = {
-       "git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
-       "git tag -d <tagname>...",
-       "git tag -l [-n[<num>]] [<pattern>...]",
-       "git tag -v <tagname>...",
+       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\t\t[<pattern>...]"),
+       N_("git tag -v <tagname>..."),
        NULL
 };
 
@@ -30,6 +33,9 @@ struct tag_filter {
        struct commit_list *with_commit;
 };
 
+static struct sha1_array points_at;
+static unsigned int colopts;
+
 static int match_pattern(const char **patterns, const char *ref)
 {
        /* no pattern means match everything */
@@ -41,6 +47,24 @@ static int match_pattern(const char **patterns, const char *ref)
        return 0;
 }
 
+static const unsigned char *match_points_at(const char *refname,
+                                           const unsigned char *sha1)
+{
+       const unsigned char *tagged_sha1 = NULL;
+       struct object *obj;
+
+       if (sha1_array_lookup(&points_at, sha1) >= 0)
+               return sha1;
+       obj = parse_object(sha1);
+       if (!obj)
+               die(_("malformed object at '%s'"), refname);
+       if (obj->type == OBJ_TAG)
+               tagged_sha1 = ((struct tag *)obj)->tagged->sha1;
+       if (tagged_sha1 && sha1_array_lookup(&points_at, tagged_sha1) >= 0)
+               return tagged_sha1;
+       return NULL;
+}
+
 static int in_commit_list(const struct commit_list *want, struct commit *c)
 {
        for (; want; want = want->next)
@@ -138,6 +162,9 @@ static int show_reference(const char *refname, const unsigned char *sha1,
                                return 0;
                }
 
+               if (points_at.nr && !match_points_at(refname, sha1))
+                       return 0;
+
                if (!filter->lines) {
                        printf("%s\n", refname);
                        return 0;
@@ -238,6 +265,8 @@ static int git_tag_config(const char *var, const char *value, void *cb)
        int status = git_gpg_config(var, value, cb);
        if (status)
                return status;
+       if (!prefixcmp(var, "column."))
+               return git_column_config(var, value, "tag", &colopts);
        return git_default_config(var, value, cb);
 }
 
@@ -303,7 +332,7 @@ static void create_tag(const unsigned char *object, const char *tag,
                          sha1_to_hex(object),
                          typename(type),
                          tag,
-                         git_committer_info(IDENT_ERROR_ON_NO_NAME));
+                         git_committer_info(IDENT_STRICT));
 
        if (header_len > sizeof(header_buf) - 1)
                die(_("tag header too big."));
@@ -383,6 +412,23 @@ static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)
        return check_refname_format(sb->buf, 0);
 }
 
+static int parse_opt_points_at(const struct option *opt __attribute__((unused)),
+                       const char *arg, int unset)
+{
+       unsigned char sha1[20];
+
+       if (unset) {
+               sha1_array_clear(&points_at);
+               return 0;
+       }
+       if (!arg)
+               return error(_("switch 'points-at' requires an object"));
+       if (get_sha1(arg, sha1))
+               return error(_("malformed object name '%s'"), arg);
+       sha1_array_append(&points_at, sha1);
+       return 0;
+}
+
 int cmd_tag(int argc, const char **argv, const char *prefix)
 {
        struct strbuf buf = STRBUF_INIT;
@@ -398,33 +444,38 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        struct msg_arg msg = { 0, STRBUF_INIT };
        struct commit_list *with_commit = NULL;
        struct option options[] = {
-               OPT_BOOLEAN('l', "list", &list, "list tag names"),
-               { OPTION_INTEGER, 'n', NULL, &lines, "n",
-                               "print <n> lines of each tag message",
+               OPT_BOOLEAN('l', "list", &list, N_("list tag names")),
+               { OPTION_INTEGER, 'n', NULL, &lines, N_("n"),
+                               N_("print <n> lines of each tag message"),
                                PARSE_OPT_OPTARG, NULL, 1 },
-               OPT_BOOLEAN('d', "delete", &delete, "delete tags"),
-               OPT_BOOLEAN('v', "verify", &verify, "verify tags"),
+               OPT_BOOLEAN('d', "delete", &delete, N_("delete tags")),
+               OPT_BOOLEAN('v', "verify", &verify, N_("verify tags")),
 
-               OPT_GROUP("Tag creation options"),
+               OPT_GROUP(N_("Tag creation options")),
                OPT_BOOLEAN('a', "annotate", &annotate,
-                                       "annotated tag, needs a message"),
-               OPT_CALLBACK('m', "message", &msg, "message",
-                            "tag message", parse_msg_arg),
-               OPT_FILENAME('F', "file", &msgfile, "read message from file"),
-               OPT_BOOLEAN('s', "sign", &opt.sign, "annotated and GPG-signed tag"),
-               OPT_STRING(0, "cleanup", &cleanup_arg, "mode",
-                       "how to strip spaces and #comments from message"),
-               OPT_STRING('u', "local-user", &keyid, "key-id",
-                                       "use another key to sign the tag"),
-               OPT__FORCE(&force, "replace the tag if exists"),
-
-               OPT_GROUP("Tag listing options"),
+                                       N_("annotated tag, needs a message")),
+               OPT_CALLBACK('m', "message", &msg, N_("message"),
+                            N_("tag message"), parse_msg_arg),
+               OPT_FILENAME('F', "file", &msgfile, N_("read message from file")),
+               OPT_BOOLEAN('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")),
+               OPT_STRING(0, "cleanup", &cleanup_arg, N_("mode"),
+                       N_("how to strip spaces and #comments from message")),
+               OPT_STRING('u', "local-user", &keyid, N_("key id"),
+                                       N_("use another key to sign the tag")),
+               OPT__FORCE(&force, N_("replace the tag if exists")),
+               OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
+
+               OPT_GROUP(N_("Tag listing options")),
                {
-                       OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
-                       "print only tags that contain the commit",
+                       OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"),
+                       N_("print only tags that contain the commit"),
                        PARSE_OPT_LASTARG_DEFAULT,
                        parse_opt_with_commit, (intptr_t)"HEAD",
                },
+               {
+                       OPTION_CALLBACK, 0, "points-at", NULL, N_("object"),
+                       N_("print only tags of the object"), 0, parse_opt_points_at
+               },
                OPT_END()
        };
 
@@ -449,13 +500,31 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 
        if (list + delete + verify > 1)
                usage_with_options(git_tag_usage, options);
-       if (list)
-               return list_tags(argv, lines == -1 ? 0 : lines,
-                                with_commit);
+       finalize_colopts(&colopts, -1);
+       if (list && lines != -1) {
+               if (explicitly_enable_column(colopts))
+                       die(_("--column and -n are incompatible"));
+               colopts = 0;
+       }
+       if (list) {
+               int ret;
+               if (column_active(colopts)) {
+                       struct column_options copts;
+                       memset(&copts, 0, sizeof(copts));
+                       copts.padding = 2;
+                       run_column_filter(colopts, &copts);
+               }
+               ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit);
+               if (column_active(colopts))
+                       stop_column_filter();
+               return ret;
+       }
        if (lines != -1)
                die(_("-n option is only allowed with -l."));
        if (with_commit)
                die(_("--contains option is only allowed with -l."));
+       if (points_at.nr)
+               die(_("--points-at option is only allowed with -l."));
        if (delete)
                return for_each_tag_name(argv, delete_tag);
        if (verify)