--sort=refname:version (or --sort=refname:v for short) sorts tags as if they are versions. --sort=-refname reverses the order (with or without ":version"). This syntax is chosen to make it compatible with future extension in "for-each-ref --sort" GNU extension strverscmp is used so this is Linux only. Mac and Windows will need to bundle a compat implementation (and long term we may want to use compat version only so we can make XXX-rc, XXX-pre... appear before XXX) Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@xxxxxxxxx> --- The new prereq GNULINUX is an ugly workaround until people provide strverscmp compat implementation. I hope that will happen soon as strverscmp.c does not look very complex. Documentation/git-tag.txt | 6 +++++ builtin/tag.c | 69 +++++++++++++++++++++++++++++++++++++++++++---- git-compat-util.h | 7 +++++ t/t7004-tag.sh | 43 +++++++++++++++++++++++++++++ t/test-lib.sh | 2 ++ 5 files changed, 122 insertions(+), 5 deletions(-) diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 404257d..d8633bb 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -95,6 +95,12 @@ OPTIONS using fnmatch(3)). Multiple patterns may be given; if any of them matches, the tag is shown. +--sort=<type>:: + Sort in a specific order. Supported type is "refname" + (lexical order), "refname:version" or "refname:v" (tag names + are treated as version strings). Prepend "-" to reverse sorting + order. + --column[=<options>]:: --no-column:: Display tag listing in columns. See configuration variable diff --git a/builtin/tag.c b/builtin/tag.c index 74d3780..483d293 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -27,9 +27,16 @@ static const char * const git_tag_usage[] = { NULL }; +#define STRCMP_SORT 0 /* must be zero */ +#define STRVERSCMP_SORT 1 +#define SORT_MASK 0x7fff +#define REVERSE_SORT 0x8000 + struct tag_filter { const char **patterns; int lines; + int sort; + struct string_list tags; struct commit_list *with_commit; }; @@ -166,7 +173,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 +187,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 strverscmp(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) == STRVERSCMP_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; } @@ -427,6 +459,27 @@ 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; + if (*arg == '-') { + *sort = REVERSE_SORT; + arg++; + } else + *sort = STRCMP_SORT; + if (!starts_with(arg, "refname") || + (arg[7] != ':' && arg[7] != '\0')) + die(_("unsupported sort field %s"), arg); + if (arg[7] == ':') { + const char *modifier = arg + 8; + if (!strcmp(modifier, "version") || !strcmp(modifier, "v")) + *sort |= STRVERSCMP_SORT; + else + die(_("unsupported modifier %s"), modifier); + } + return 0; +} + int cmd_tag(int argc, const char **argv, const char *prefix) { struct strbuf buf = STRBUF_INIT; @@ -437,7 +490,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) struct create_tag_options opt; char *cleanup_arg = NULL; int annotate = 0, force = 0, lines = -1; - int cmdmode = 0; + int cmdmode = 0, sort = 0; const char *msgfile = NULL, *keyid = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; struct commit_list *with_commit = NULL; @@ -462,6 +515,10 @@ int cmd_tag(int argc, const char **argv, const char *prefix) 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", &sort, N_("type"), N_("sort tags"), + PARSE_OPT_NONEG, parse_opt_sort + }, OPT_GROUP(N_("Tag listing options")), { @@ -509,7 +566,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 && sort) + die(_("--sort and -n are incompatible")); + ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, sort); if (column_active(colopts)) stop_column_filter(); return ret; diff --git a/git-compat-util.h b/git-compat-util.h index cbd86c3..22089e9 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -721,4 +721,11 @@ void warn_on_inaccessible(const char *path); /* Get the passwd entry for the UID of the current process. */ struct passwd *xgetpwuid_self(void); +#ifndef __GNU_LIBRARY__ +static inline int strverscmp(const char *s1, const char *s2) +{ + die("strverscmp() not supported"); +} +#endif + #endif diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index c8d6e9f..0b7b170 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1380,4 +1380,47 @@ test_expect_success 'multiple --points-at are OR-ed together' ' test_cmp expect actual ' +test_expect_success GNULINUX 'lexical sort' ' + git tag foo1.3 && + git tag foo1.6 && + git tag foo1.10 && + git tag -l --sort=refname "foo*" >actual && + cat >expect <<EOF && +foo1.10 +foo1.3 +foo1.6 +EOF + test_cmp expect actual +' + +test_expect_success GNULINUX 'version sort' ' + git tag -l --sort=refname:version "foo*" >actual && + cat >expect <<EOF && +foo1.3 +foo1.6 +foo1.10 +EOF + test_cmp expect actual +' + +test_expect_success GNULINUX 'reverse version sort' ' + git tag -l --sort=-refname:version "foo*" >actual && + cat >expect <<EOF && +foo1.10 +foo1.6 +foo1.3 +EOF + test_cmp expect actual +' + +test_expect_success GNULINUX 'reverse lexical sort' ' + git tag -l --sort=-refname "foo*" >actual && + cat >expect <<EOF && +foo1.6 +foo1.3 +foo1.10 +EOF + test_cmp expect actual +' + test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index 1531c24..5e8c39a 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -771,6 +771,8 @@ case $(uname -s) in ;; esac +[ "$(uname -o)" = "GNU/Linux" ] && test_set_prereq GNULINUX + ( COLUMNS=1 && test $COLUMNS = 1 ) && test_set_prereq COLUMNS_CAN_BE_1 test -z "$NO_PERL" && test_set_prereq PERL test -z "$NO_PYTHON" && test_set_prereq PYTHON -- 1.9.0.40.gaa8c3ea -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html