diff options
Diffstat (limited to 'builtin/tag.c')
-rw-r--r-- | builtin/tag.c | 207 |
1 files changed, 181 insertions, 26 deletions
diff --git a/builtin/tag.c b/builtin/tag.c index 74d3780b77..19eb747820 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -27,9 +27,18 @@ static const char * const git_tag_usage[] = { 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; }; @@ -42,7 +51,7 @@ static int match_pattern(const char **patterns, const char *ref) if (!*patterns) return 1; for (; *patterns; patterns++) - if (!fnmatch(*patterns, ref, 0)) + if (!wildmatch(*patterns, ref, 0, NULL)) return 1; return 0; } @@ -73,11 +82,19 @@ static int in_commit_list(const struct commit_list *want, struct commit *c) return 0; } -static int contains_recurse(struct commit *candidate, +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) { - struct commit_list *p; - /* was it previously marked as containing a want commit? */ if (candidate->object.flags & TMP_MARK) return 1; @@ -85,26 +102,78 @@ static int contains_recurse(struct commit *candidate, if (candidate->object.flags & UNINTERESTING) return 0; /* or are we it? */ - if (in_commit_list(want, candidate)) + if (in_commit_list(want, candidate)) { + candidate->object.flags |= TMP_MARK; 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; + 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 int contains(struct commit *candidate, const struct commit_list *want) +static enum contains_result contains(struct commit *candidate, + const struct commit_list *want) { - return contains_recurse(candidate, 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 unsigned char *sha1, int lines) @@ -166,7 +235,10 @@ static int show_reference(const char *refname, const unsigned char *sha1, return 0; if (!filter->lines) { - printf("%s\n", refname); + if (filter->sort) + string_list_append(&filter->tags, refname); + else + printf("%s\n", refname); return 0; } printf("%-15s ", refname); @@ -177,17 +249,39 @@ static int show_reference(const char *refname, const unsigned char *sha1, return 0; } +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); +} + static int list_tags(const char **patterns, int lines, - struct commit_list *with_commit) + 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; } @@ -246,17 +340,59 @@ 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 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) +{ + int type = 0, flags = 0; + + if (skip_prefix(arg, "-", &arg)) + flags |= REVERSE_SORT; + + 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; + } + } + + *sort = (type | flags); + + 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; + + if (!strcmp(var, "tag.sort")) { + if (!value) + return config_error_nonbool(var); + parse_sort_string(var, value, &tag_sort); + return 0; + } + + status = git_gpg_config(var, value, cb); if (status) return status; if (starts_with(var, "column.")) @@ -346,9 +482,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); } @@ -427,6 +563,13 @@ static int parse_opt_points_at(const struct option *opt __attribute__((unused)), 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; @@ -458,10 +601,14 @@ 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")), + { + OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"), + PARSE_OPT_NONEG, parse_opt_sort + }, OPT_GROUP(N_("Tag listing options")), { @@ -471,6 +618,12 @@ int cmd_tag(int argc, const char **argv, const char *prefix) 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 }, @@ -509,7 +662,9 @@ 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); + if (lines != -1 && tag_sort) + die(_("--sort and -n are incompatible")); + ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, tag_sort); if (column_active(colopts)) stop_column_filter(); return ret; |