i18n: unpack-trees: avoid substituting only a verb in sentences
[gitweb.git] / builtin / tag.c
index 74d3780b77548fc8a7bb2f51b47a51cbfb51969a..1705c9466546c7de52b12d3fbafbbe7b6d43cf4e 100644 (file)
 #include "gpg-interface.h"
 #include "sha1-array.h"
 #include "column.h"
+#include "ref-filter.h"
 
 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 [-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 -l [-n[<num>]] [--contains <commit>] [--points-at <object>]"
+               "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"),
        N_("git tag -v <tagname>..."),
        NULL
 };
 
-struct tag_filter {
-       const char **patterns;
-       int lines;
-       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 */
-       if (!*patterns)
-               return 1;
-       for (; *patterns; patterns++)
-               if (!fnmatch(*patterns, ref, 0))
-                       return 1;
-       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)
-               if (!hashcmp(want->item->object.sha1, c->object.sha1))
-                       return 1;
-       return 0;
-}
-
-static int contains_recurse(struct commit *candidate,
-                           const struct commit_list *want)
-{
-       struct commit_list *p;
-
-       /* was it previously marked as containing a want commit? */
-       if (candidate->object.flags & TMP_MARK)
-               return 1;
-       /* or marked as not possibly containing a want commit? */
-       if (candidate->object.flags & UNINTERESTING)
-               return 0;
-       /* or are we it? */
-       if (in_commit_list(want, candidate))
-               return 1;
-
-       if (parse_commit(candidate) < 0)
-               return 0;
-
-       /* Otherwise recurse and mark ourselves for future traversals. */
-       for (p = candidate->parents; p; p = p->next) {
-               if (contains_recurse(p->item, want)) {
-                       candidate->object.flags |= TMP_MARK;
-                       return 1;
-               }
-       }
-       candidate->object.flags |= UNINTERESTING;
-       return 0;
-}
-
-static int contains(struct commit *candidate, const struct commit_list *want)
-{
-       return contains_recurse(candidate, want);
-}
-
-static void show_tag_lines(const unsigned char *sha1, int lines)
+static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format)
 {
+       struct ref_array array;
+       char *to_free = NULL;
        int i;
-       unsigned long size;
-       enum object_type type;
-       char *buf, *sp, *eol;
-       size_t len;
 
-       buf = read_sha1_file(sha1, &type, &size);
-       if (!buf)
-               die_errno("unable to read object %s", sha1_to_hex(sha1));
-       if (type != OBJ_COMMIT && type != OBJ_TAG)
-               goto free_return;
-       if (!size)
-               die("an empty %s object %s?",
-                   typename(type), sha1_to_hex(sha1));
+       memset(&array, 0, sizeof(array));
 
-       /* skip header */
-       sp = strstr(buf, "\n\n");
-       if (!sp)
-               goto free_return;
-
-       /* only take up to "lines" lines, and strip the signature from a tag */
-       if (type == OBJ_TAG)
-               size = parse_signature(buf, size);
-       for (i = 0, sp += 2; i < lines && sp < buf + size; i++) {
-               if (i)
-                       printf("\n    ");
-               eol = memchr(sp, '\n', size - (sp - buf));
-               len = eol ? eol - sp : size - (sp - buf);
-               fwrite(sp, len, 1, stdout);
-               if (!eol)
-                       break;
-               sp = eol + 1;
-       }
-free_return:
-       free(buf);
-}
+       if (filter->lines == -1)
+               filter->lines = 0;
 
-static int show_reference(const char *refname, const unsigned char *sha1,
-                         int flag, void *cb_data)
-{
-       struct tag_filter *filter = cb_data;
-
-       if (match_pattern(filter->patterns, refname)) {
-               if (filter->with_commit) {
-                       struct commit *commit;
-
-                       commit = lookup_commit_reference_gently(sha1, 1);
-                       if (!commit)
-                               return 0;
-                       if (!contains(commit, filter->with_commit))
-                               return 0;
-               }
-
-               if (points_at.nr && !match_points_at(refname, sha1))
-                       return 0;
-
-               if (!filter->lines) {
-                       printf("%s\n", refname);
-                       return 0;
-               }
-               printf("%-15s ", refname);
-               show_tag_lines(sha1, filter->lines);
-               putchar('\n');
+       if (!format) {
+               if (filter->lines) {
+                       to_free = xstrfmt("%s %%(contents:lines=%d)",
+                                         "%(align:15)%(refname:strip=2)%(end)",
+                                         filter->lines);
+                       format = to_free;
+               } else
+                       format = "%(refname:strip=2)";
        }
 
-       return 0;
-}
+       verify_ref_format(format);
+       filter->with_commit_tag_algo = 1;
+       filter_refs(&array, filter, FILTER_REFS_TAGS);
+       ref_array_sort(sorting, &array);
 
-static int list_tags(const char **patterns, int lines,
-                       struct commit_list *with_commit)
-{
-       struct tag_filter filter;
-
-       filter.patterns = patterns;
-       filter.lines = lines;
-       filter.with_commit = with_commit;
-
-       for_each_tag_ref(show_reference, (void *) &filter);
+       for (i = 0; i < array.nr; i++)
+               show_ref_array_item(array.items[i], format, 0);
+       ref_array_clear(&array);
+       free(to_free);
 
        return 0;
 }
@@ -246,17 +119,51 @@ static int do_sign(struct strbuf *buffer)
 }
 
 static const char tag_template[] =
-       N_("\nWrite a tag message\n"
+       N_("\nWrite a message for tag:\n  %s\n"
        "Lines starting with '%c' will be ignored.\n");
 
 static const char tag_template_nocleanup[] =
-       N_("\nWrite a tag message\n"
+       N_("\nWrite a message for tag:\n  %s\n"
        "Lines starting with '%c' will be kept; you may remove them"
        " yourself if you want to.\n");
 
+/* Parse arg given and add it the ref_sorting array */
+static int parse_sorting_string(const char *arg, struct ref_sorting **sorting_tail)
+{
+       struct ref_sorting *s;
+       int len;
+
+       s = xcalloc(1, sizeof(*s));
+       s->next = *sorting_tail;
+       *sorting_tail = s;
+
+       if (*arg == '-') {
+               s->reverse = 1;
+               arg++;
+       }
+       if (skip_prefix(arg, "version:", &arg) ||
+           skip_prefix(arg, "v:", &arg))
+               s->version = 1;
+
+       len = strlen(arg);
+       s->atom = parse_ref_filter_atom(arg, arg+len);
+
+       return 0;
+}
+
 static int git_tag_config(const char *var, const char *value, void *cb)
 {
-       int status = git_gpg_config(var, value, cb);
+       int status;
+       struct ref_sorting **sorting_tail = (struct ref_sorting **)cb;
+
+       if (!strcmp(var, "tag.sort")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               parse_sorting_string(value, sorting_tail);
+               return 0;
+       }
+
+       status = git_gpg_config(var, value, cb);
        if (status)
                return status;
        if (starts_with(var, "column."))
@@ -346,9 +253,9 @@ static void create_tag(const unsigned char *object, const char *tag,
                        struct strbuf buf = STRBUF_INIT;
                        strbuf_addch(&buf, '\n');
                        if (opt->cleanup_mode == CLEANUP_ALL)
-                               strbuf_commented_addf(&buf, _(tag_template), comment_line_char);
+                               strbuf_commented_addf(&buf, _(tag_template), tag, comment_line_char);
                        else
-                               strbuf_commented_addf(&buf, _(tag_template_nocleanup), comment_line_char);
+                               strbuf_commented_addf(&buf, _(tag_template_nocleanup), tag, comment_line_char);
                        write_or_die(fd, buf.buf, buf.len);
                        strbuf_release(&buf);
                }
@@ -362,7 +269,7 @@ static void create_tag(const unsigned char *object, const char *tag,
        }
 
        if (opt->cleanup_mode != CLEANUP_NONE)
-               stripspace(buf, opt->cleanup_mode == CLEANUP_ALL);
+               strbuf_stripspace(buf, opt->cleanup_mode == CLEANUP_ALL);
 
        if (!opt->message_given && !buf->len)
                die(_("no tag message?"));
@@ -410,40 +317,27 @@ 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;
        struct strbuf ref = STRBUF_INIT;
        unsigned char object[20], prev[20];
        const char *object_ref, *tag;
-       struct ref_lock *lock;
        struct create_tag_options opt;
        char *cleanup_arg = NULL;
-       int annotate = 0, force = 0, lines = -1;
+       int create_reflog = 0;
+       int annotate = 0, force = 0;
        int cmdmode = 0;
        const char *msgfile = NULL, *keyid = NULL;
        struct msg_arg msg = { 0, STRBUF_INIT };
-       struct commit_list *with_commit = NULL;
+       struct ref_transaction *transaction;
+       struct strbuf err = STRBUF_INIT;
+       struct ref_filter filter;
+       static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
+       const char *format = NULL;
        struct option options[] = {
                OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
-               { OPTION_INTEGER, 'n', NULL, &lines, N_("n"),
+               { OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"),
                                N_("print <n> lines of each tag message"),
                                PARSE_OPT_OPTARG, NULL, 1 },
                OPT_CMDMODE('d', "delete", &cmdmode, N_("delete tags"), 'd'),
@@ -458,28 +352,32 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                OPT_BOOL('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"),
+               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_BOOL(0, "create-reflog", &create_reflog, N_("create a reflog")),
 
                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_WITH(&filter.with_commit, N_("print only tags that 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, "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
+                       OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
+                       N_("print only tags of the object"), 0, parse_opt_object_name
                },
+               OPT_STRING(  0 , "format", &format, N_("format"), N_("format to use for the output")),
                OPT_END()
        };
 
-       git_config(git_tag_config, NULL);
+       git_config(git_tag_config, sorting_tail);
 
        memset(&opt, 0, sizeof(opt));
+       memset(&filter, 0, sizeof(filter));
+       filter.lines = -1;
 
        argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
 
@@ -496,11 +394,13 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                usage_with_options(git_tag_usage, options);
 
        finalize_colopts(&colopts, -1);
-       if (cmdmode == 'l' && lines != -1) {
+       if (cmdmode == 'l' && filter.lines != -1) {
                if (explicitly_enable_column(colopts))
                        die(_("--column and -n are incompatible"));
                colopts = 0;
        }
+       if (!sorting)
+               sorting = ref_default_sorting();
        if (cmdmode == 'l') {
                int ret;
                if (column_active(colopts)) {
@@ -509,17 +409,20 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                        copts.padding = 2;
                        run_column_filter(colopts, &copts);
                }
-               ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit);
+               filter.name_patterns = argv;
+               ret = list_tags(&filter, sorting, format);
                if (column_active(colopts))
                        stop_column_filter();
                return ret;
        }
-       if (lines != -1)
+       if (filter.lines != -1)
                die(_("-n option is only allowed with -l."));
-       if (with_commit)
+       if (filter.with_commit)
                die(_("--contains option is only allowed with -l."));
-       if (points_at.nr)
+       if (filter.points_at.nr)
                die(_("--points-at option is only allowed with -l."));
+       if (filter.merge_commit)
+               die(_("--merged and --no-merged option are only allowed with -l"));
        if (cmdmode == 'd')
                return for_each_tag_name(argv, delete_tag);
        if (cmdmode == 'v')
@@ -574,14 +477,18 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        if (annotate)
                create_tag(object, tag, &buf, &opt, prev, object);
 
-       lock = lock_any_ref_for_update(ref.buf, prev, 0, NULL);
-       if (!lock)
-               die(_("%s: cannot lock the ref"), ref.buf);
-       if (write_ref_sha1(lock, object, NULL) < 0)
-               die(_("%s: cannot update the ref"), ref.buf);
+       transaction = ref_transaction_begin(&err);
+       if (!transaction ||
+           ref_transaction_update(transaction, ref.buf, object, prev,
+                                  create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
+                                  NULL, &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));
 
+       strbuf_release(&err);
        strbuf_release(&buf);
        strbuf_release(&ref);
        return 0;