files_log_ref_write: new function
[gitweb.git] / builtin / tag.c
index f048cae0e0593866105574e685b97eb71023fbd2..8db8c87e57ef05edadce8de19316572498c40753 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 -d <tagname>..."),
        N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]"
-               "\n\t\t[<pattern>...]"),
+               "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"),
        N_("git tag -v <tagname>..."),
        NULL
 };
 
-#define STRCMP_SORT     0      /* must be zero */
-#define VERCMP_SORT     1
-#define SORT_MASK       0x7fff
-#define REVERSE_SORT    0x8000
-
-static int tag_sort;
-
-struct tag_filter {
-       const char **patterns;
-       int lines;
-       int sort;
-       struct string_list tags;
-       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 (!wildmatch(*patterns, ref, 0, NULL))
-                       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;
-}
-
-enum contains_result {
-       CONTAINS_UNKNOWN = -1,
-       CONTAINS_NO = 0,
-       CONTAINS_YES = 1
-};
-
-/*
- * Test whether the candidate or one of its parents is contained in the list.
- * Do not recurse to find out, though, but return -1 if inconclusive.
- */
-static enum contains_result contains_test(struct commit *candidate,
-                           const struct commit_list *want)
-{
-       /* 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)) {
-               candidate->object.flags |= TMP_MARK;
-               return 1;
-       }
-
-       if (parse_commit(candidate) < 0)
-               return 0;
-
-       return -1;
-}
-
-/*
- * Mimicking the real stack, this stack lives on the heap, avoiding stack
- * overflows.
- *
- * At each recursion step, the stack items points to the commits whose
- * ancestors are to be inspected.
- */
-struct stack {
-       int nr, alloc;
-       struct stack_entry {
-               struct commit *commit;
-               struct commit_list *parents;
-       } *stack;
-};
-
-static void push_to_stack(struct commit *candidate, struct stack *stack)
-{
-       int index = stack->nr++;
-       ALLOC_GROW(stack->stack, stack->nr, stack->alloc);
-       stack->stack[index].commit = candidate;
-       stack->stack[index].parents = candidate->parents;
-}
-
-static enum contains_result contains(struct commit *candidate,
-               const struct commit_list *want)
-{
-       struct stack stack = { 0, 0, NULL };
-       int result = contains_test(candidate, want);
-
-       if (result != CONTAINS_UNKNOWN)
-               return result;
-
-       push_to_stack(candidate, &stack);
-       while (stack.nr) {
-               struct stack_entry *entry = &stack.stack[stack.nr - 1];
-               struct commit *commit = entry->commit;
-               struct commit_list *parents = entry->parents;
-
-               if (!parents) {
-                       commit->object.flags |= UNINTERESTING;
-                       stack.nr--;
-               }
-               /*
-                * If we just popped the stack, parents->item has been marked,
-                * therefore contains_test will return a meaningful 0 or 1.
-                */
-               else switch (contains_test(parents->item, want)) {
-               case CONTAINS_YES:
-                       commit->object.flags |= TMP_MARK;
-                       stack.nr--;
-                       break;
-               case CONTAINS_NO:
-                       entry->parents = parents->next;
-                       break;
-               case CONTAINS_UNKNOWN:
-                       push_to_stack(parents->item, &stack);
-                       break;
-               }
-       }
-       free(stack.stack);
-       return contains_test(candidate, want);
-}
-
-static void show_tag_lines(const struct object_id *oid, 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(oid->hash, &type, &size);
-       if (!buf)
-               die_errno("unable to read object %s", oid_to_hex(oid));
-       if (type != OBJ_COMMIT && type != OBJ_TAG)
-               goto free_return;
-       if (!size)
-               die("an empty %s object %s?",
-                   typename(type), oid_to_hex(oid));
-
-       /* 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);
-}
-
-static int show_reference(const char *refname, const struct object_id *oid,
-                         int flag, void *cb_data)
-{
-       struct tag_filter *filter = cb_data;
 
-       if (match_pattern(filter->patterns, refname)) {
-               if (filter->with_commit) {
-                       struct commit *commit;
+       memset(&array, 0, sizeof(array));
 
-                       commit = lookup_commit_reference_gently(oid->hash, 1);
-                       if (!commit)
-                               return 0;
-                       if (!contains(commit, filter->with_commit))
-                               return 0;
-               }
-
-               if (points_at.nr && !match_points_at(refname, oid->hash))
-                       return 0;
+       if (filter->lines == -1)
+               filter->lines = 0;
 
-               if (!filter->lines) {
-                       if (filter->sort)
-                               string_list_append(&filter->tags, refname);
-                       else
-                               printf("%s\n", refname);
-                       return 0;
-               }
-               printf("%-15s ", refname);
-               show_tag_lines(oid, filter->lines);
-               putchar('\n');
+       if (!format) {
+               if (filter->lines) {
+                       to_free = xstrfmt("%s %%(contents:lines=%d)",
+                                         "%(align:15)%(refname:short)%(end)",
+                                         filter->lines);
+                       format = to_free;
+               } else
+                       format = "%(refname:short)";
        }
 
-       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 sort_by_version(const void *a_, const void *b_)
-{
-       const struct string_list_item *a = a_;
-       const struct string_list_item *b = b_;
-       return versioncmp(a->string, b->string);
-}
+       for (i = 0; i < array.nr; i++)
+               show_ref_array_item(array.items[i], format, 0);
+       ref_array_clear(&array);
+       free(to_free);
 
-static int list_tags(const char **patterns, int lines,
-                    struct commit_list *with_commit, int sort)
-{
-       struct tag_filter filter;
-
-       filter.patterns = patterns;
-       filter.lines = lines;
-       filter.sort = sort;
-       filter.with_commit = with_commit;
-       memset(&filter.tags, 0, sizeof(filter.tags));
-       filter.tags.strdup_strings = 1;
-
-       for_each_tag_ref(show_reference, (void *)&filter);
-       if (sort) {
-               int i;
-               if ((sort & SORT_MASK) == VERCMP_SORT)
-                       qsort(filter.tags.items, filter.tags.nr,
-                             sizeof(struct string_list_item), sort_by_version);
-               if (sort & REVERSE_SORT)
-                       for (i = filter.tags.nr - 1; i >= 0; i--)
-                               printf("%s\n", filter.tags.items[i].string);
-               else
-                       for (i = 0; i < filter.tags.nr; i++)
-                               printf("%s\n", filter.tags.items[i].string);
-               string_list_clear(&filter.tags, 0);
-       }
        return 0;
 }
 
@@ -348,35 +127,26 @@ static const char tag_template_nocleanup[] =
        "Lines starting with '%c' will be kept; you may remove them"
        " yourself if you want to.\n");
 
-/*
- * Parse a sort string, and return 0 if parsed successfully. Will return
- * non-zero when the sort string does not parse into a known type. If var is
- * given, the error message becomes a warning and includes information about
- * the configuration value.
- */
-static int parse_sort_string(const char *var, const char *arg, int *sort)
+/* Parse arg given and add it the ref_sorting array */
+static int parse_sorting_string(const char *arg, struct ref_sorting **sorting_tail)
 {
-       int type = 0, flags = 0;
+       struct ref_sorting *s;
+       int len;
 
-       if (skip_prefix(arg, "-", &arg))
-               flags |= REVERSE_SORT;
+       s = xcalloc(1, sizeof(*s));
+       s->next = *sorting_tail;
+       *sorting_tail = s;
 
-       if (skip_prefix(arg, "version:", &arg) || skip_prefix(arg, "v:", &arg))
-               type = VERCMP_SORT;
-       else
-               type = STRCMP_SORT;
-
-       if (strcmp(arg, "refname")) {
-               if (!var)
-                       return error(_("unsupported sort specification '%s'"), arg);
-               else {
-                       warning(_("unsupported sort specification '%s' in variable '%s'"),
-                               var, arg);
-                       return -1;
-               }
+       if (*arg == '-') {
+               s->reverse = 1;
+               arg++;
        }
+       if (skip_prefix(arg, "version:", &arg) ||
+           skip_prefix(arg, "v:", &arg))
+               s->version = 1;
 
-       *sort = (type | flags);
+       len = strlen(arg);
+       s->atom = parse_ref_filter_atom(arg, arg+len);
 
        return 0;
 }
@@ -384,11 +154,12 @@ static int parse_sort_string(const char *var, const char *arg, int *sort)
 static int git_tag_config(const char *var, const char *value, void *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_sort_string(var, value, &tag_sort);
+               parse_sorting_string(value, sorting_tail);
                return 0;
        }
 
@@ -546,30 +317,6 @@ 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;
-}
-
-static int parse_opt_sort(const struct option *opt, const char *arg, int unset)
-{
-       int *sort = opt->value;
-
-       return parse_sort_string(NULL, arg, sort);
-}
-
 int cmd_tag(int argc, const char **argv, const char *prefix)
 {
        struct strbuf buf = STRBUF_INIT;
@@ -578,17 +325,19 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        const char *object_ref, *tag;
        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'),
@@ -610,32 +359,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_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, "sort", &tag_sort, N_("type"), N_("sort tags"),
-                       PARSE_OPT_NONEG, parse_opt_sort
-               },
-               {
-                       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, "with", &with_commit, N_("commit"),
-                       N_("print only tags that contain the commit"),
-                       PARSE_OPT_HIDDEN | 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);
 
@@ -652,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)) {
@@ -665,19 +409,20 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                        copts.padding = 2;
                        run_column_filter(colopts, &copts);
                }
-               if (lines != -1 && tag_sort)
-                       die(_("--sort and -n are incompatible"));
-               ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, tag_sort);
+               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')