{"thread":{"id":"35914","subject":"[PATCH] tag: support --sort=version","startedAt":"2014-02-19T13:39:27Z","lastAt":"2014-02-27T13:23:17Z","messageCount":25,"participants":["Nguyễn Thái Ngọc Duy","Jeff King","Duy Nguyen","Eric Sunshine","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"235055","messageId":"1392817167-29802-1-git-send-email-pclouds@gmail.com","threadId":"35914","inReplyTo":null,"subject":"[PATCH] tag: support --sort=version","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-19T13:39:27Z","receivedAt":"2014-02-19T13:39:27Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"--sort=version sorts tags as versions. GNU extension's strverscmp is\nused and no real compat implementation is provided so this is Linux only.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n I didn't know that coreutils' sort is simply a wrapper of strverscmp.\n With that GNU extension, implementing --sort is easy. Mac and Windows\n are welcome to reimplement strverscmp (of rip it off glibc).\n\n Documentation/git-tag.txt |  4 ++++\n builtin/tag.c             | 49 ++++++++++++++++++++++++++++++++++++++++++-----\n git-compat-util.h         |  7 +++++++\n 3 files changed, 55 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\nindex 404257d..fc23dc0 100644\n--- a/Documentation/git-tag.txt\n+++ b/Documentation/git-tag.txt\n@@ -95,6 +95,10 @@ OPTIONS\n \tusing fnmatch(3)).  Multiple patterns may be given; if any of\n \tthem matches, the tag is shown.\n \n+--sort=<type>::\n+\tSort in a specific order. Supported type is \"version\". Prepend\n+\t\"-\" to revert sort order.\n+\n --column[=<options>]::\n --no-column::\n \tDisplay tag listing in columns. See configuration variable\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 74d3780..db3567b 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -30,6 +30,8 @@ static const char * const git_tag_usage[] = {\n struct tag_filter {\n \tconst char **patterns;\n \tint lines;\n+\tint version_sort;\n+\tstruct string_list tags;\n \tstruct commit_list *with_commit;\n };\n \n@@ -166,7 +168,10 @@ static int show_reference(const char *refname, const unsigned char *sha1,\n \t\t\treturn 0;\n \n \t\tif (!filter->lines) {\n-\t\t\tprintf(\"%s\\n\", refname);\n+\t\t\tif (filter->version_sort)\n+\t\t\t\tstring_list_append(&filter->tags, refname);\n+\t\t\telse\n+\t\t\t\tprintf(\"%s\\n\", refname);\n \t\t\treturn 0;\n \t\t}\n \t\tprintf(\"%-15s \", refname);\n@@ -177,17 +182,38 @@ static int show_reference(const char *refname, const unsigned char *sha1,\n \treturn 0;\n }\n \n+static int sort_by_version(const void *a_, const void *b_)\n+{\n+\tconst struct string_list_item *a = a_;\n+\tconst struct string_list_item *b = b_;\n+\treturn strverscmp(a->string, b->string);\n+}\n+\n static int list_tags(const char **patterns, int lines,\n-\t\t\tstruct commit_list *with_commit)\n+\t\t     struct commit_list *with_commit, int version_sort)\n {\n \tstruct tag_filter filter;\n \n \tfilter.patterns = patterns;\n \tfilter.lines = lines;\n+\tfilter.version_sort = version_sort;\n \tfilter.with_commit = with_commit;\n+\tmemset(&filter.tags, 0, sizeof(filter.tags));\n+\tfilter.tags.strdup_strings = 1;\n \n \tfor_each_tag_ref(show_reference, (void *) &filter);\n-\n+\tif (version_sort) {\n+\t\tint i;\n+\t\tqsort(filter.tags.items, filter.tags.nr,\n+\t\t      sizeof(struct string_list_item), sort_by_version);\n+\t\tif (version_sort > 0)\n+\t\t\tfor (i = 0; i < filter.tags.nr; i++)\n+\t\t\t\tprintf(\"%s\\n\", filter.tags.items[i].string);\n+\t\telse\n+\t\t\tfor (i = filter.tags.nr - 1; i >= 0; i--)\n+\t\t\t\tprintf(\"%s\\n\", filter.tags.items[i].string);\n+\t\tstring_list_clear(&filter.tags, 0);\n+\t}\n \treturn 0;\n }\n \n@@ -437,7 +463,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstruct create_tag_options opt;\n \tchar *cleanup_arg = NULL;\n \tint annotate = 0, force = 0, lines = -1;\n-\tint cmdmode = 0;\n+\tint cmdmode = 0, version_sort = 0;\n+\tconst char *sort = NULL;\n \tconst char *msgfile = NULL, *keyid = NULL;\n \tstruct msg_arg msg = { 0, STRBUF_INIT };\n \tstruct commit_list *with_commit = NULL;\n@@ -462,6 +489,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\t\t\tN_(\"use another key to sign the tag\")),\n \t\tOPT__FORCE(&force, N_(\"replace the tag if exists\")),\n \t\tOPT_COLUMN(0, \"column\", &colopts, N_(\"show tag list in columns\")),\n+\t\tOPT_STRING(0, \"sort\", &sort, N_(\"type\"), N_(\"sort tags\")),\n \n \t\tOPT_GROUP(N_(\"Tag listing options\")),\n \t\t{\n@@ -509,7 +537,18 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\tcopts.padding = 2;\n \t\t\trun_column_filter(colopts, &copts);\n \t\t}\n-\t\tret = list_tags(argv, lines == -1 ? 0 : lines, with_commit);\n+\t\tif (sort) {\n+\t\t\tif (!strcmp(sort, \"version\"))\n+\t\t\t\tversion_sort = 1;\n+\t\t\telse if (!strcmp(sort, \"-version\"))\n+\t\t\t\tversion_sort = -1;\n+\t\t\telse\n+\t\t\t\tdie(_(\"unsupported sort type %s\"), sort);\n+\t\t}\n+\t\tif (lines != -1 && version_sort)\n+\t\t\tdie(_(\"--sort and -l are incompatible\"));\n+\t\tret = list_tags(argv, lines == -1 ? 0 : lines, with_commit,\n+\t\t\t\tversion_sort);\n \t\tif (column_active(colopts))\n \t\t\tstop_column_filter();\n \t\treturn ret;\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex cbd86c3..22089e9 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -721,4 +721,11 @@ void warn_on_inaccessible(const char *path);\n /* Get the passwd entry for the UID of the current process. */\n struct passwd *xgetpwuid_self(void);\n \n+#ifndef __GNU_LIBRARY__\n+static inline int strverscmp(const char *s1, const char *s2)\n+{\n+\tdie(\"strverscmp() not supported\");\n+}\n+#endif\n+\n #endif\n-- \n1.9.0.40.gaa8c3ea\n"},{"id":"235056","messageId":"20140219140909.GA20128@sigill.intra.peff.net","threadId":"35914","inReplyTo":"1392817167-29802-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH] tag: support --sort=version","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2014-02-19T14:09:09Z","receivedAt":"2014-02-19T14:09:09Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Feb 19, 2014 at 08:39:27PM +0700, Nguyễn Thái Ngọc Duy wrote:\n\n> --sort=version sorts tags as versions. GNU extension's strverscmp is\n> used and no real compat implementation is provided so this is Linux only.\n\nSounds like a good goal.\n\nI wonder, if we were to merge the for-each-ref and tag\nimplementations[1], how this would integrate with for-each-ref's\nsorting. It can sort on arbitrary fields like \"--sort=-*authordate\". I\nthink given the syntax you provide, this would fall out naturally as\njust another key (albeit a slightly magical one, as it is building on\nthe %(refname:short) field but using a different comparator).\n\nWould we ever want to version-sort any other field? Perhaps\n%(content:subject) for a tag? I'm not sure what would be the most\nnatural way to specify that. Maybe \"--sort=version:content:subject\",\nwith just \"--version\" as a synonym for \"version:refname:short\".\n\nWe don't need to do any of that immediately.  This is mostly just me\nthinking aloud, to make sure we do not paint ourselves into a corner\ncompatibility-wise.\n\n-Peff\n\n[1] I have patches which I really need to polish and send out that\n    combine the ref-selection code (so tag, branch, and for-each-ref all\n    know \"--merged\", \"--contains\", etc). I'd really like to combine the\n    sorting and formatting code, too, so everybody can use \"--sort\" and\n    \"--format\" with all of the associated fields.\n"},{"id":"235057","messageId":"CACsJy8CL3rQx=QHf_eABCUqS+9kZXEmopUuCMiNCL+UPyvtbyw@mail.gmail.com","threadId":"35914","inReplyTo":"20140219140909.GA20128@sigill.intra.peff.net","subject":"Re: [PATCH] tag: support --sort=version","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-19T14:19:24Z","receivedAt":"2014-02-19T14:19:24Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Feb 19, 2014 at 9:09 PM, Jeff King <peff@peff.net> wrote:\n> On Wed, Feb 19, 2014 at 08:39:27PM +0700, Nguyễn Thái Ngọc Duy wrote:\n>\n>> --sort=version sorts tags as versions. GNU extension's strverscmp is\n>> used and no real compat implementation is provided so this is Linux only.\n>\n> Sounds like a good goal.\n>\n> I wonder, if we were to merge the for-each-ref and tag\n> implementations[1], how this would integrate with for-each-ref's\n> sorting. It can sort on arbitrary fields like \"--sort=-*authordate\". I\n> think given the syntax you provide, this would fall out naturally as\n> just another key (albeit a slightly magical one, as it is building on\n> the %(refname:short) field but using a different comparator).\n>\n> Would we ever want to version-sort any other field? Perhaps\n> %(content:subject) for a tag? I'm not sure what would be the most\n> natural way to specify that. Maybe \"--sort=version:content:subject\",\n> with just \"--version\" as a synonym for \"version:refname:short\".\n\nIf f-e-r and tag share code and syntax then we could use another\nletter for this sort type (like we use '-' to reverse sort order). I\nthink that would make the implementation easier as well. So we could\nhave --sort=.refname for version sorting refname, --sort=-.refname for\nreversed version sort, and sort=-refname for reverse alphabetical\nsort.\n\n> We don't need to do any of that immediately.  This is mostly just me\n> thinking aloud, to make sure we do not paint ourselves into a corner\n> compatibility-wise.\n\nGood thinking. If you plan to use the exact same sort syntax f-e-r\nuses now, pick a letter (the dot I used above is probably not the\nbest), I'll rewrite this patch to use the same syntax.\n\n>\n> -Peff\n>\n> [1] I have patches which I really need to polish and send out that\n>     combine the ref-selection code (so tag, branch, and for-each-ref all\n>     know \"--merged\", \"--contains\", etc). I'd really like to combine the\n>     sorting and formatting code, too, so everybody can use \"--sort\" and\n>     \"--format\" with all of the associated fields.\n-- \nDuy\n"},{"id":"235059","messageId":"CAPig+cSXt4LKFfj6VqH=xfmbMvmT2o2ozDaH-+_435GeuKLa=g@mail.gmail.com","threadId":"35914","inReplyTo":"1392817167-29802-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH] tag: support --sort=version","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-02-19T18:43:55Z","receivedAt":"2014-02-19T18:43:55Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Feb 19, 2014 at 8:39 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> --sort=version sorts tags as versions. GNU extension's strverscmp is\n> used and no real compat implementation is provided so this is Linux only.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\n> index 404257d..fc23dc0 100644\n> --- a/Documentation/git-tag.txt\n> +++ b/Documentation/git-tag.txt\n> @@ -95,6 +95,10 @@ OPTIONS\n>         using fnmatch(3)).  Multiple patterns may be given; if any of\n>         them matches, the tag is shown.\n>\n> +--sort=<type>::\n> +       Sort in a specific order. Supported type is \"version\". Prepend\n> +       \"-\" to revert sort order.\n\ns/revert/reverse/\n\n> +\n>  --column[=<options>]::\n>  --no-column::\n>         Display tag listing in columns. See configuration variable\n"},{"id":"235109","messageId":"20140220204345.GA3374@sigill.intra.peff.net","threadId":"35914","inReplyTo":"CACsJy8CL3rQx=QHf_eABCUqS+9kZXEmopUuCMiNCL+UPyvtbyw@mail.gmail.com","subject":"Re: [PATCH] tag: support --sort=version","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2014-02-20T20:43:45Z","receivedAt":"2014-02-20T20:43:45Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Feb 19, 2014 at 09:19:24PM +0700, Duy Nguyen wrote:\n\n> > We don't need to do any of that immediately.  This is mostly just me\n> > thinking aloud, to make sure we do not paint ourselves into a corner\n> > compatibility-wise.\n> \n> Good thinking. If you plan to use the exact same sort syntax f-e-r\n> uses now, pick a letter (the dot I used above is probably not the\n> best), I'll rewrite this patch to use the same syntax.\n\nI think I actually prefer the full word \"version\", as you have already.\nIt's clear what it means, and we can extend the syntax generally to:\n\n  --sort=[-][comparison:]field\n\nlike:\n\n  --sort=-version:subject\n\nfor descending version-sort by subject.  And then as a special-case\nconvenience, make \"version\" without a field default to\n\"version:refname\". There's no ambiguity because the set of comparison\nnames and field-names is fixed, and we know there is no overlap.\n\nIf want to, we can _also_ give a one-letter abbreviation to the\ncomparison field, like:\n\n  --sort=v:subject\n\nbut that is not necessary.\n\nSo I think your patch is fine as-is. It is perhaps a little funny to\nstart with the special case and only implement the general case later,\nbut:\n\n  1. We would want the special case eventually, because it is the most\n     natural thing to type, and pretty clearly the most common use-case.\n\n  2. We may not ever even end up with the general case. This is just\n     about making sure that if we _do_ add it, that it fits\n     syntactically with \"--sort=version\".\n\n-Peff\n"},{"id":"235125","messageId":"CACsJy8BrfKckHzgwRnW_UMTjipuYWGVcumvCmQC9EG1Eq-MScA@mail.gmail.com","threadId":"35914","inReplyTo":"20140220204345.GA3374@sigill.intra.peff.net","subject":"Re: [PATCH] tag: support --sort=version","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-21T11:58:16Z","receivedAt":"2014-02-21T11:58:16Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Fri, Feb 21, 2014 at 3:43 AM, Jeff King <peff@peff.net> wrote:\n> I think I actually prefer the full word \"version\", as you have already.\n> It's clear what it means, and we can extend the syntax generally to:\n\nAgreed. It's hard to find a letter that reminds you about \"version\".\n\n>\n>   --sort=[-][comparison:]field\n>\n> like:\n>\n>   --sort=-version:subject\n>\n> for descending version-sort by subject.  And then as a special-case\n> convenience, make \"version\" without a field default to\n> \"version:refname\". There's no ambiguity because the set of comparison\n> names and field-names is fixed, and we know there is no overlap.\n>\n> If want to, we can _also_ give a one-letter abbreviation to the\n> comparison field, like:\n>\n>   --sort=v:subject\n>\n> but that is not necessary.\n\nWhy not reversed order? So its syntax could be\n\n[ \"-\" ] FIELD [ \":\" [ \"version\" | \"v\" ] ]\n\nIt fits better to current f-e-r syntax where modifiers are after the\ncolon. And it avoids the possibility that someone adds field \"version\"\nand we can't tell what \"version\" is what.\n-- \nDuy\n"},{"id":"235145","messageId":"xmqqvbw84ass.fsf@gitster.dls.corp.google.com","threadId":"35914","inReplyTo":"CACsJy8BrfKckHzgwRnW_UMTjipuYWGVcumvCmQC9EG1Eq-MScA@mail.gmail.com","subject":"Re: [PATCH] tag: support --sort=version","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-21T17:48:03Z","receivedAt":"2014-02-21T17:48:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Duy Nguyen <pclouds@gmail.com> writes:\n\n> Why not reversed order? So its syntax could be\n>\n> [ \"-\" ] FIELD [ \":\" [ \"version\" | \"v\" ] ]\n>\n> It fits better to current f-e-r syntax where modifiers are after the\n> colon. And it avoids the possibility that someone adds field \"version\"\n> and we can't tell what \"version\" is what.\n\nI think that reads well.\n"},{"id":"235162","messageId":"1393039762-4843-1-git-send-email-pclouds@gmail.com","threadId":"35914","inReplyTo":"1392817167-29802-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2] tag: support --sort=<spec>","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-22T03:29:22Z","receivedAt":"2014-02-22T03:29:22Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"--sort=refname:version (or --sort=refname:v for short) sorts tags as\nif they are versions. --sort=-refname reverses the order (with or\nwithout \":version\"). This syntax is chosen to make it compatible with\nfuture extension in \"for-each-ref --sort\"\n\nGNU extension strverscmp is used so this is Linux only. Mac and\nWindows will need to bundle a compat implementation (and long term we\nmay want to use compat version only so we can make XXX-rc, XXX-pre...\nappear before XXX)\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n The new prereq GNULINUX is an ugly workaround until people provide\n strverscmp compat implementation. I hope that will happen soon as\n strverscmp.c does not look very complex.\n\n Documentation/git-tag.txt |  6 +++++\n builtin/tag.c             | 69 +++++++++++++++++++++++++++++++++++++++++++----\n git-compat-util.h         |  7 +++++\n t/t7004-tag.sh            | 43 +++++++++++++++++++++++++++++\n t/test-lib.sh             |  2 ++\n 5 files changed, 122 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\nindex 404257d..d8633bb 100644\n--- a/Documentation/git-tag.txt\n+++ b/Documentation/git-tag.txt\n@@ -95,6 +95,12 @@ OPTIONS\n \tusing fnmatch(3)).  Multiple patterns may be given; if any of\n \tthem matches, the tag is shown.\n \n+--sort=<type>::\n+\tSort in a specific order. Supported type is \"refname\"\n+\t(lexical order), \"refname:version\" or \"refname:v\" (tag names\n+\tare treated as version strings). Prepend \"-\" to reverse sorting\n+\torder.\n+\n --column[=<options>]::\n --no-column::\n \tDisplay tag listing in columns. See configuration variable\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 74d3780..483d293 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -27,9 +27,16 @@ static const char * const git_tag_usage[] = {\n \tNULL\n };\n \n+#define STRCMP_SORT     0\t/* must be zero */\n+#define STRVERSCMP_SORT 1\n+#define SORT_MASK       0x7fff\n+#define REVERSE_SORT    0x8000\n+\n struct tag_filter {\n \tconst char **patterns;\n \tint lines;\n+\tint sort;\n+\tstruct string_list tags;\n \tstruct commit_list *with_commit;\n };\n \n@@ -166,7 +173,10 @@ static int show_reference(const char *refname, const unsigned char *sha1,\n \t\t\treturn 0;\n \n \t\tif (!filter->lines) {\n-\t\t\tprintf(\"%s\\n\", refname);\n+\t\t\tif (filter->sort)\n+\t\t\t\tstring_list_append(&filter->tags, refname);\n+\t\t\telse\n+\t\t\t\tprintf(\"%s\\n\", refname);\n \t\t\treturn 0;\n \t\t}\n \t\tprintf(\"%-15s \", refname);\n@@ -177,17 +187,39 @@ static int show_reference(const char *refname, const unsigned char *sha1,\n \treturn 0;\n }\n \n+static int sort_by_version(const void *a_, const void *b_)\n+{\n+\tconst struct string_list_item *a = a_;\n+\tconst struct string_list_item *b = b_;\n+\treturn strverscmp(a->string, b->string);\n+}\n+\n static int list_tags(const char **patterns, int lines,\n-\t\t\tstruct commit_list *with_commit)\n+\t\t     struct commit_list *with_commit, int sort)\n {\n \tstruct tag_filter filter;\n \n \tfilter.patterns = patterns;\n \tfilter.lines = lines;\n+\tfilter.sort = sort;\n \tfilter.with_commit = with_commit;\n+\tmemset(&filter.tags, 0, sizeof(filter.tags));\n+\tfilter.tags.strdup_strings = 1;\n \n \tfor_each_tag_ref(show_reference, (void *) &filter);\n-\n+\tif (sort) {\n+\t\tint i;\n+\t\tif ((sort & SORT_MASK) == STRVERSCMP_SORT)\n+\t\t\tqsort(filter.tags.items, filter.tags.nr,\n+\t\t\t      sizeof(struct string_list_item), sort_by_version);\n+\t\tif (sort & REVERSE_SORT)\n+\t\t\tfor (i = filter.tags.nr - 1; i >= 0; i--)\n+\t\t\t\tprintf(\"%s\\n\", filter.tags.items[i].string);\n+\t\telse\n+\t\t\tfor (i = 0; i < filter.tags.nr; i++)\n+\t\t\t\tprintf(\"%s\\n\", filter.tags.items[i].string);\n+\t\tstring_list_clear(&filter.tags, 0);\n+\t}\n \treturn 0;\n }\n \n@@ -427,6 +459,27 @@ static int parse_opt_points_at(const struct option *opt __attribute__((unused)),\n \treturn 0;\n }\n \n+static int parse_opt_sort(const struct option *opt, const char *arg, int unset)\n+{\n+\tint *sort = opt->value;\n+\tif (*arg == '-') {\n+\t\t*sort = REVERSE_SORT;\n+\t\targ++;\n+\t} else\n+\t\t*sort = STRCMP_SORT;\n+\tif (!starts_with(arg, \"refname\") ||\n+\t    (arg[7] != ':' && arg[7] != '\\0'))\n+\t\tdie(_(\"unsupported sort field %s\"), arg);\n+\tif (arg[7] == ':') {\n+\t\tconst char *modifier = arg + 8;\n+\t\tif (!strcmp(modifier, \"version\") || !strcmp(modifier, \"v\"))\n+\t\t\t*sort |= STRVERSCMP_SORT;\n+\t\telse\n+\t\t\tdie(_(\"unsupported modifier %s\"), modifier);\n+\t}\n+\treturn 0;\n+}\n+\n int cmd_tag(int argc, const char **argv, const char *prefix)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -437,7 +490,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstruct create_tag_options opt;\n \tchar *cleanup_arg = NULL;\n \tint annotate = 0, force = 0, lines = -1;\n-\tint cmdmode = 0;\n+\tint cmdmode = 0, sort = 0;\n \tconst char *msgfile = NULL, *keyid = NULL;\n \tstruct msg_arg msg = { 0, STRBUF_INIT };\n \tstruct commit_list *with_commit = NULL;\n@@ -462,6 +515,10 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\t\t\tN_(\"use another key to sign the tag\")),\n \t\tOPT__FORCE(&force, N_(\"replace the tag if exists\")),\n \t\tOPT_COLUMN(0, \"column\", &colopts, N_(\"show tag list in columns\")),\n+\t\t{\n+\t\t\tOPTION_CALLBACK, 0, \"sort\", &sort, N_(\"type\"), N_(\"sort tags\"),\n+\t\t\tPARSE_OPT_NONEG, parse_opt_sort\n+\t\t},\n \n \t\tOPT_GROUP(N_(\"Tag listing options\")),\n \t\t{\n@@ -509,7 +566,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\tcopts.padding = 2;\n \t\t\trun_column_filter(colopts, &copts);\n \t\t}\n-\t\tret = list_tags(argv, lines == -1 ? 0 : lines, with_commit);\n+\t\tif (lines != -1 && sort)\n+\t\t\tdie(_(\"--sort and -n are incompatible\"));\n+\t\tret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, sort);\n \t\tif (column_active(colopts))\n \t\t\tstop_column_filter();\n \t\treturn ret;\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex cbd86c3..22089e9 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -721,4 +721,11 @@ void warn_on_inaccessible(const char *path);\n /* Get the passwd entry for the UID of the current process. */\n struct passwd *xgetpwuid_self(void);\n \n+#ifndef __GNU_LIBRARY__\n+static inline int strverscmp(const char *s1, const char *s2)\n+{\n+\tdie(\"strverscmp() not supported\");\n+}\n+#endif\n+\n #endif\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex c8d6e9f..0b7b170 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -1380,4 +1380,47 @@ test_expect_success 'multiple --points-at are OR-ed together' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success GNULINUX 'lexical sort' '\n+\tgit tag foo1.3 &&\n+\tgit tag foo1.6 &&\n+\tgit tag foo1.10 &&\n+\tgit tag -l --sort=refname \"foo*\" >actual &&\n+\tcat >expect <<EOF &&\n+foo1.10\n+foo1.3\n+foo1.6\n+EOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success GNULINUX 'version sort' '\n+\tgit tag -l --sort=refname:version \"foo*\" >actual &&\n+\tcat >expect <<EOF &&\n+foo1.3\n+foo1.6\n+foo1.10\n+EOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success GNULINUX 'reverse version sort' '\n+\tgit tag -l --sort=-refname:version \"foo*\" >actual &&\n+\tcat >expect <<EOF &&\n+foo1.10\n+foo1.6\n+foo1.3\n+EOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success GNULINUX 'reverse lexical sort' '\n+\tgit tag -l --sort=-refname \"foo*\" >actual &&\n+\tcat >expect <<EOF &&\n+foo1.6\n+foo1.3\n+foo1.10\n+EOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\ndiff --git a/t/test-lib.sh b/t/test-lib.sh\nindex 1531c24..5e8c39a 100644\n--- a/t/test-lib.sh\n+++ b/t/test-lib.sh\n@@ -771,6 +771,8 @@ case $(uname -s) in\n \t;;\n esac\n \n+[ \"$(uname -o)\" = \"GNU/Linux\" ] && test_set_prereq GNULINUX\n+\n ( COLUMNS=1 && test $COLUMNS = 1 ) && test_set_prereq COLUMNS_CAN_BE_1\n test -z \"$NO_PERL\" && test_set_prereq PERL\n test -z \"$NO_PYTHON\" && test_set_prereq PYTHON\n-- \n1.9.0.40.gaa8c3ea\n"},{"id":"235166","messageId":"20140222075935.GA1576@sigill.intra.peff.net","threadId":"35914","inReplyTo":"CACsJy8BrfKckHzgwRnW_UMTjipuYWGVcumvCmQC9EG1Eq-MScA@mail.gmail.com","subject":"Re: [PATCH] tag: support --sort=version","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2014-02-22T07:59:35Z","receivedAt":"2014-02-22T07:59:35Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Feb 21, 2014 at 06:58:16PM +0700, Duy Nguyen wrote:\n\n> >   --sort=[-][comparison:]field\n> [...]\n> Why not reversed order? So its syntax could be\n> \n> [ \"-\" ] FIELD [ \":\" [ \"version\" | \"v\" ] ]\n> \n> It fits better to current f-e-r syntax where modifiers are after the\n> colon. And it avoids the possibility that someone adds field \"version\"\n> and we can't tell what \"version\" is what.\n\nI find my version a bit more obvious, for two reasons:\n\n  1. \"version\" here is not a modifier of the field name, it is a\n     modifier of the sort. You cannot use it in non-sort contexts (like\n     --format), and you cannot order it like other modifiers (you cannot\n     say \"refname:version:short\", only \"refname:short:version\").\n\n  2. There are actually two sort-modifiers: \"-\" for ordering, and then a\n     comparator. In your proposal, they are split, whereas in mine, they\n     are next to each other.\n\n-Peff\n"},{"id":"235167","messageId":"20140222080404.GB1576@sigill.intra.peff.net","threadId":"35914","inReplyTo":"1393039762-4843-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v2] tag: support --sort=<spec>","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2014-02-22T08:04:04Z","receivedAt":"2014-02-22T08:04:04Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, Feb 22, 2014 at 10:29:22AM +0700, Nguyễn Thái Ngọc Duy wrote:\n\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  The new prereq GNULINUX is an ugly workaround until people provide\n>  strverscmp compat implementation. I hope that will happen soon as\n>  strverscmp.c does not look very complex.\n\nShould GNULINUX be called HAVE_STRVERSCMP in the Makefile?\n\nThen this:\n\n> --- a/git-compat-util.h\n> +++ b/git-compat-util.h\n> @@ -721,4 +721,11 @@ void warn_on_inaccessible(const char *path);\n>  /* Get the passwd entry for the UID of the current process. */\n>  struct passwd *xgetpwuid_self(void);\n>  \n> +#ifndef __GNU_LIBRARY__\n> +static inline int strverscmp(const char *s1, const char *s2)\n> +{\n> +\tdie(\"strverscmp() not supported\");\n> +}\n> +#endif\n\nbecomes \"#ifndef HAVE_STRVERSCMP\", and this:\n\n> diff --git a/t/test-lib.sh b/t/test-lib.sh\n> index 1531c24..5e8c39a 100644\n> --- a/t/test-lib.sh\n> +++ b/t/test-lib.sh\n> @@ -771,6 +771,8 @@ case $(uname -s) in\n>  \t;;\n>  esac\n>  \n> +[ \"$(uname -o)\" = \"GNU/Linux\" ] && test_set_prereq GNULINUX\n> +\n\ncan pick up the value from GIT-BUILD-OPTIONS as a prerequisite (see the\nway we handle NO_PERL for an example). Though if we can just grab the\nglibc version as a fallback, we can do away with that completely.\n\n-Peff\n"},{"id":"235175","messageId":"CACsJy8B5UL+nsDxMCUj5+Cp7-me_R2T1etMTh+_JpW8Fqkt9DQ@mail.gmail.com","threadId":"35914","inReplyTo":"20140222075935.GA1576@sigill.intra.peff.net","subject":"Re: [PATCH] tag: support --sort=version","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-22T09:07:49Z","receivedAt":"2014-02-22T09:07:49Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Sat, Feb 22, 2014 at 2:59 PM, Jeff King <peff@peff.net> wrote:\n> On Fri, Feb 21, 2014 at 06:58:16PM +0700, Duy Nguyen wrote:\n>\n>> >   --sort=[-][comparison:]field\n>> [...]\n>> Why not reversed order? So its syntax could be\n>>\n>> [ \"-\" ] FIELD [ \":\" [ \"version\" | \"v\" ] ]\n>>\n>> It fits better to current f-e-r syntax where modifiers are after the\n>> colon. And it avoids the possibility that someone adds field \"version\"\n>> and we can't tell what \"version\" is what.\n>\n> I find my version a bit more obvious, for two reasons:\n>\n>   1. \"version\" here is not a modifier of the field name, it is a\n>      modifier of the sort. You cannot use it in non-sort contexts (like\n>      --format), and you cannot order it like other modifiers (you cannot\n>      say \"refname:version:short\", only \"refname:short:version\").\n\nOr you can read it like \"type cast this field as a version\", where\nsorting is affected but formatting, not so much. So you can specify it\nwith --format (but it's no-op, unless we find a fancy way to color\nversions). I don't see a problem with accepting both\nrefname:version:short and refname:short:version in future for-each-ref\neither. It will be the first time we accept multiple modifiers though.\n\n>   2. There are actually two sort-modifiers: \"-\" for ordering, and then a\n>      comparator. In your proposal, they are split, whereas in mine, they\n>      are next to each other.\n-- \nDuy\n"},{"id":"235262","messageId":"xmqqy510bh2g.fsf@gitster.dls.corp.google.com","threadId":"35914","inReplyTo":"20140222080404.GB1576@sigill.intra.peff.net","subject":"Re: [PATCH v2] tag: support --sort=<spec>","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-24T16:39:51Z","receivedAt":"2014-02-24T16:39:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Sat, Feb 22, 2014 at 10:29:22AM +0700, Nguyễn Thái Ngọc Duy wrote:\n>\n>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>> ---\n>>  The new prereq GNULINUX is an ugly workaround until people provide\n>>  strverscmp compat implementation. I hope that will happen soon as\n>>  strverscmp.c does not look very complex.\n>\n> Should GNULINUX be called HAVE_STRVERSCMP in the Makefile?\n>\n> Then this:\n>\n>> --- a/git-compat-util.h\n>> +++ b/git-compat-util.h\n>> @@ -721,4 +721,11 @@ void warn_on_inaccessible(const char *path);\n>>  /* Get the passwd entry for the UID of the current process. */\n>>  struct passwd *xgetpwuid_self(void);\n>>  \n>> +#ifndef __GNU_LIBRARY__\n>> +static inline int strverscmp(const char *s1, const char *s2)\n>> +{\n>> +\tdie(\"strverscmp() not supported\");\n>> +}\n>> +#endif\n>\n> becomes \"#ifndef HAVE_STRVERSCMP\", and this:\n>\n>> diff --git a/t/test-lib.sh b/t/test-lib.sh\n>> index 1531c24..5e8c39a 100644\n>> --- a/t/test-lib.sh\n>> +++ b/t/test-lib.sh\n>> @@ -771,6 +771,8 @@ case $(uname -s) in\n>>  \t;;\n>>  esac\n>>  \n>> +[ \"$(uname -o)\" = \"GNU/Linux\" ] && test_set_prereq GNULINUX\n>> +\n>\n> can pick up the value from GIT-BUILD-OPTIONS as a prerequisite (see the\n> way we handle NO_PERL for an example). Though if we can just grab the\n> glibc version as a fallback, we can do away with that completely.\n\n;-)  I like that.\n"},{"id":"235305","messageId":"CACsJy8CRe4MmkTnuChOi2SHG52tx_LZ+51vWXN9ooGmcRUN7bQ@mail.gmail.com","threadId":"35914","inReplyTo":"xmqqy510bh2g.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v2] tag: support --sort=<spec>","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-24T23:30:35Z","receivedAt":"2014-02-24T23:30:35Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Mon, Feb 24, 2014 at 11:39 PM, Junio C Hamano <gitster@pobox.com> wrote:\n>>> diff --git a/t/test-lib.sh b/t/test-lib.sh\n>>> index 1531c24..5e8c39a 100644\n>>> --- a/t/test-lib.sh\n>>> +++ b/t/test-lib.sh\n>>> @@ -771,6 +771,8 @@ case $(uname -s) in\n>>>      ;;\n>>>  esac\n>>>\n>>> +[ \"$(uname -o)\" = \"GNU/Linux\" ] && test_set_prereq GNULINUX\n>>> +\n>>\n>> can pick up the value from GIT-BUILD-OPTIONS as a prerequisite (see the\n>> way we handle NO_PERL for an example). Though if we can just grab the\n>> glibc version as a fallback, we can do away with that completely.\n>\n> ;-)  I like that.\n>\n\nJeff, I'm still waiting if you agree to go with this syntax or put\nversion before refname before rerolling (either with build time option\nlike this, or implement the compat function myself).\n-- \nDuy\n"},{"id":"235306","messageId":"20140224233303.GA27689@sigill.intra.peff.net","threadId":"35914","inReplyTo":"CACsJy8CRe4MmkTnuChOi2SHG52tx_LZ+51vWXN9ooGmcRUN7bQ@mail.gmail.com","subject":"Re: [PATCH v2] tag: support --sort=<spec>","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2014-02-24T23:33:03Z","receivedAt":"2014-02-24T23:33:03Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Feb 25, 2014 at 06:30:35AM +0700, Duy Nguyen wrote:\n\n> >>> +[ \"$(uname -o)\" = \"GNU/Linux\" ] && test_set_prereq GNULINUX\n> >>> +\n> >>\n> >> can pick up the value from GIT-BUILD-OPTIONS as a prerequisite (see the\n> >> way we handle NO_PERL for an example). Though if we can just grab the\n> >> glibc version as a fallback, we can do away with that completely.\n> >\n> > ;-)  I like that.\n> >\n> \n> Jeff, I'm still waiting if you agree to go with this syntax or put\n> version before refname before rerolling (either with build time option\n> like this, or implement the compat function myself).\n\nSorry. I didn't respond because I was trying to think of something to\nsay besides \"no, I still like mine better\". I admit that it's mostly a\ngut feeling, and I don't have any argument beyond what I've already\nmade (your \"it's like a typecast\" does make some sense, but I think that\nis a convoluted way of thinking about it).\n\n-Peff\n"},{"id":"235314","messageId":"1393330935-22229-1-git-send-email-pclouds@gmail.com","threadId":"35914","inReplyTo":"1393039762-4843-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3] tag: support --sort=<spec>","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-25T12:22:15Z","receivedAt":"2014-02-25T12:22:15Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"--sort=version:refname (or --sort=v:refname for short) sorts tags as\nif they are versions. --sort=-refname reverses the order (with or\nwithout \":version\").\n\nversioncmp() is copied from string/strverscmp.c in glibc commit\nee9247c38a8def24a59eb5cfb7196a98bef8cfdc, reformatted to Git coding\nstyle. The implementation is under LGPL-2.1 and according to [1] I can\nrelicense it to GPLv2.\n\n[1] http://www.gnu.org/licenses/gpl-faq.html#AllCompatibility\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n v3 tweaks the sorting syntax a bit (version:refname vs refname:version)\n and embed strverscmp() to git code (instead of relying on one from glibc).\n\n Documentation/git-tag.txt |  6 ++++\n Makefile                  |  1 +\n builtin/tag.c             | 68 ++++++++++++++++++++++++++++++++++---\n cache.h                   |  2 ++\n t/t7004-tag.sh            | 43 ++++++++++++++++++++++++\n versioncmp.c (new)        | 85 +++++++++++++++++++++++++++++++++++++++++++++++\n 6 files changed, 200 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\nindex 404257d..9b05931 100644\n--- a/Documentation/git-tag.txt\n+++ b/Documentation/git-tag.txt\n@@ -95,6 +95,12 @@ OPTIONS\n \tusing fnmatch(3)).  Multiple patterns may be given; if any of\n \tthem matches, the tag is shown.\n \n+--sort=<type>::\n+\tSort in a specific order. Supported type is \"refname\"\n+\t(lexicographic order), \"version:refname\" or \"v:refname\" (tags\n+\tname are treated as versions). Prepend \"-\" to reverse sort\n+\torder.\n+\n --column[=<options>]::\n --no-column::\n \tDisplay tag listing in columns. See configuration variable\ndiff --git a/Makefile b/Makefile\nindex dddaf4f..16b00a5 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -884,6 +884,7 @@ LIB_OBJS += userdiff.o\n LIB_OBJS += utf8.o\n LIB_OBJS += varint.o\n LIB_OBJS += version.o\n+LIB_OBJS += versioncmp.o\n LIB_OBJS += walker.o\n LIB_OBJS += wildmatch.o\n LIB_OBJS += wrapper.o\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 74d3780..73b8a24 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -27,9 +27,16 @@ static const char * const git_tag_usage[] = {\n \tNULL\n };\n \n+#define STRCMP_SORT     0\t/* must be zero */\n+#define VERCMP_SORT     1\n+#define SORT_MASK       0x7fff\n+#define REVERSE_SORT    0x8000\n+\n struct tag_filter {\n \tconst char **patterns;\n \tint lines;\n+\tint sort;\n+\tstruct string_list tags;\n \tstruct commit_list *with_commit;\n };\n \n@@ -166,7 +173,10 @@ static int show_reference(const char *refname, const unsigned char *sha1,\n \t\t\treturn 0;\n \n \t\tif (!filter->lines) {\n-\t\t\tprintf(\"%s\\n\", refname);\n+\t\t\tif (filter->sort)\n+\t\t\t\tstring_list_append(&filter->tags, refname);\n+\t\t\telse\n+\t\t\t\tprintf(\"%s\\n\", refname);\n \t\t\treturn 0;\n \t\t}\n \t\tprintf(\"%-15s \", refname);\n@@ -177,17 +187,39 @@ static int show_reference(const char *refname, const unsigned char *sha1,\n \treturn 0;\n }\n \n+static int sort_by_version(const void *a_, const void *b_)\n+{\n+\tconst struct string_list_item *a = a_;\n+\tconst struct string_list_item *b = b_;\n+\treturn versioncmp(a->string, b->string);\n+}\n+\n static int list_tags(const char **patterns, int lines,\n-\t\t\tstruct commit_list *with_commit)\n+\t\t     struct commit_list *with_commit, int sort)\n {\n \tstruct tag_filter filter;\n \n \tfilter.patterns = patterns;\n \tfilter.lines = lines;\n+\tfilter.sort = sort;\n \tfilter.with_commit = with_commit;\n+\tmemset(&filter.tags, 0, sizeof(filter.tags));\n+\tfilter.tags.strdup_strings = 1;\n \n \tfor_each_tag_ref(show_reference, (void *) &filter);\n-\n+\tif (sort) {\n+\t\tint i;\n+\t\tif ((sort & SORT_MASK) == VERCMP_SORT)\n+\t\t\tqsort(filter.tags.items, filter.tags.nr,\n+\t\t\t      sizeof(struct string_list_item), sort_by_version);\n+\t\tif (sort & REVERSE_SORT)\n+\t\t\tfor (i = filter.tags.nr - 1; i >= 0; i--)\n+\t\t\t\tprintf(\"%s\\n\", filter.tags.items[i].string);\n+\t\telse\n+\t\t\tfor (i = 0; i < filter.tags.nr; i++)\n+\t\t\t\tprintf(\"%s\\n\", filter.tags.items[i].string);\n+\t\tstring_list_clear(&filter.tags, 0);\n+\t}\n \treturn 0;\n }\n \n@@ -427,6 +459,26 @@ static int parse_opt_points_at(const struct option *opt __attribute__((unused)),\n \treturn 0;\n }\n \n+static int parse_opt_sort(const struct option *opt, const char *arg, int unset)\n+{\n+\tint *sort = opt->value;\n+\tif (*arg == '-') {\n+\t\t*sort = REVERSE_SORT;\n+\t\targ++;\n+\t} else\n+\t\t*sort = STRCMP_SORT;\n+\tif (starts_with(arg, \"version:\")) {\n+\t\t*sort |= VERCMP_SORT;\n+\t\targ += 8;\n+\t} else if (starts_with(arg, \"v:\")) {\n+\t\t*sort |= VERCMP_SORT;\n+\t\targ += 2;\n+\t}\n+\tif (strcmp(arg, \"refname\"))\n+\t\tdie(_(\"unsupported sort specification %s\"), arg);\n+\treturn 0;\n+}\n+\n int cmd_tag(int argc, const char **argv, const char *prefix)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -437,7 +489,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstruct create_tag_options opt;\n \tchar *cleanup_arg = NULL;\n \tint annotate = 0, force = 0, lines = -1;\n-\tint cmdmode = 0;\n+\tint cmdmode = 0, sort = 0;\n \tconst char *msgfile = NULL, *keyid = NULL;\n \tstruct msg_arg msg = { 0, STRBUF_INIT };\n \tstruct commit_list *with_commit = NULL;\n@@ -462,6 +514,10 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\t\t\tN_(\"use another key to sign the tag\")),\n \t\tOPT__FORCE(&force, N_(\"replace the tag if exists\")),\n \t\tOPT_COLUMN(0, \"column\", &colopts, N_(\"show tag list in columns\")),\n+\t\t{\n+\t\t\tOPTION_CALLBACK, 0, \"sort\", &sort, N_(\"type\"), N_(\"sort tags\"),\n+\t\t\tPARSE_OPT_NONEG, parse_opt_sort\n+\t\t},\n \n \t\tOPT_GROUP(N_(\"Tag listing options\")),\n \t\t{\n@@ -509,7 +565,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\tcopts.padding = 2;\n \t\t\trun_column_filter(colopts, &copts);\n \t\t}\n-\t\tret = list_tags(argv, lines == -1 ? 0 : lines, with_commit);\n+\t\tif (lines != -1 && sort)\n+\t\t\tdie(_(\"--sort and -n are incompatible\"));\n+\t\tret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, sort);\n \t\tif (column_active(colopts))\n \t\t\tstop_column_filter();\n \t\treturn ret;\ndiff --git a/cache.h b/cache.h\nindex dc040fb..082a783 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1367,4 +1367,6 @@ int stat_validity_check(struct stat_validity *sv, const char *path);\n  */\n void stat_validity_update(struct stat_validity *sv, int fd);\n \n+int versioncmp(const char *s1, const char *s2);\n+\n #endif /* CACHE_H */\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex c8d6e9f..143a8ea 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -1380,4 +1380,47 @@ test_expect_success 'multiple --points-at are OR-ed together' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'lexical sort' '\n+\tgit tag foo1.3 &&\n+\tgit tag foo1.6 &&\n+\tgit tag foo1.10 &&\n+\tgit tag -l --sort=refname \"foo*\" >actual &&\n+\tcat >expect <<EOF &&\n+foo1.10\n+foo1.3\n+foo1.6\n+EOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'version sort' '\n+\tgit tag -l --sort=version:refname \"foo*\" >actual &&\n+\tcat >expect <<EOF &&\n+foo1.3\n+foo1.6\n+foo1.10\n+EOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'reverse version sort' '\n+\tgit tag -l --sort=-version:refname \"foo*\" >actual &&\n+\tcat >expect <<EOF &&\n+foo1.10\n+foo1.6\n+foo1.3\n+EOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'reverse lexical sort' '\n+\tgit tag -l --sort=-refname \"foo*\" >actual &&\n+\tcat >expect <<EOF &&\n+foo1.6\n+foo1.3\n+foo1.10\n+EOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\ndiff --git a/versioncmp.c b/versioncmp.c\nnew file mode 100644\nindex 0000000..18a9632\n--- /dev/null\n+++ b/versioncmp.c\n@@ -0,0 +1,85 @@\n+#include \"git-compat-util.h\"\n+\n+/*\n+ * states: S_N: normal, S_I: comparing integral part, S_F: comparing\n+ * fractionnal parts, S_Z: idem but with leading Zeroes only\n+ */\n+#define  S_N    0x0\n+#define  S_I    0x3\n+#define  S_F    0x6\n+#define  S_Z    0x9\n+\n+/* result_type: CMP: return diff; LEN: compare using len_diff/diff */\n+#define  CMP    2\n+#define  LEN    3\n+\n+\n+/*\n+ * Compare S1 and S2 as strings holding indices/version numbers,\n+ * returning less than, equal to or greater than zero if S1 is less\n+ * than, equal to or greater than S2 (for more info, see the texinfo\n+ * doc).\n+ */\n+\n+int versioncmp(const char *s1, const char *s2)\n+{\n+\tconst unsigned char *p1 = (const unsigned char *) s1;\n+\tconst unsigned char *p2 = (const unsigned char *) s2;\n+\n+\t/*\n+\t * Symbol(s)    0       [1-9]   others\n+\t * Transition   (10) 0  (01) d  (00) x\n+\t */\n+\tstatic const uint8_t next_state[] = {\n+\t\t/* state    x    d    0  */\n+\t\t/* S_N */  S_N, S_I, S_Z,\n+\t\t/* S_I */  S_N, S_I, S_I,\n+\t\t/* S_F */  S_N, S_F, S_F,\n+\t\t/* S_Z */  S_N, S_F, S_Z\n+\t};\n+\n+\tstatic const int8_t result_type[] = {\n+\t\t/* state   x/x  x/d  x/0  d/x  d/d  d/0  0/x  0/d  0/0  */\n+\n+\t\t/* S_N */  CMP, CMP, CMP, CMP, LEN, CMP, CMP, CMP, CMP,\n+\t\t/* S_I */  CMP, -1,  -1,  +1,  LEN, LEN, +1,  LEN, LEN,\n+\t\t/* S_F */  CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP,\n+\t\t/* S_Z */  CMP, +1,  +1,  -1,  CMP, CMP, -1,  CMP, CMP\n+\t};\n+\n+\tif (p1 == p2)\n+\t\treturn 0;\n+\n+\tunsigned char c1 = *p1++;\n+\tunsigned char c2 = *p2++;\n+\t/* Hint: '0' is a digit too.  */\n+\tint state = S_N + ((c1 == '0') + (isdigit (c1) != 0));\n+\n+\tint diff;\n+\twhile ((diff = c1 - c2) == 0) {\n+\t\tif (c1 == '\\0')\n+\t\t\treturn diff;\n+\n+\t\tstate = next_state[state];\n+\t\tc1 = *p1++;\n+\t\tc2 = *p2++;\n+\t\tstate += (c1 == '0') + (isdigit (c1) != 0);\n+\t}\n+\n+\tstate = result_type[state * 3 + (((c2 == '0') + (isdigit (c2) != 0)))];\n+\n+\tswitch (state) {\n+\tcase CMP:\n+\t\treturn diff;\n+\n+\tcase LEN:\n+\t\twhile (isdigit (*p1++))\n+\t\t\tif (!isdigit (*p2++))\n+\t\t\t\treturn 1;\n+\n+\t\treturn isdigit (*p2) ? -1 : diff;\n+\n+\tdefault:\n+\t\treturn state;\n+\t}\n+}\n-- \n1.9.0.40.gaa8c3ea\n"},{"id":"235328","messageId":"xmqqa9df6qcj.fsf@gitster.dls.corp.google.com","threadId":"35914","inReplyTo":"1393330935-22229-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3] tag: support --sort=<spec>","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-25T17:42:52Z","receivedAt":"2014-02-25T17:42:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> versioncmp() is copied from string/strverscmp.c in glibc commit\n> ee9247c38a8def24a59eb5cfb7196a98bef8cfdc, reformatted to Git coding\n> style. The implementation is under LGPL-2.1 and according to [1] I can\n> relicense it to GPLv2.\n\nI'd propose this trivial change squashed in to record the above\nin the file in question.\n\nThanks.\n\ndiff --git a/versioncmp.c b/versioncmp.c\nindex 18a9632..8f5a388 100644\n--- a/versioncmp.c\n+++ b/versioncmp.c\n@@ -1,6 +1,13 @@\n #include \"git-compat-util.h\"\n \n /*\n+ * versioncmp(): copied from string/strverscmp.c in glibc commit\n+ * ee9247c38a8def24a59eb5cfb7196a98bef8cfdc, reformatted to Git coding\n+ * style. The implementation is under LGPL-2.1 and Git relicenses it\n+ * to GPLv2.\n+ */\n+\n+/*\n  * states: S_N: normal, S_I: comparing integral part, S_F: comparing\n  * fractionnal parts, S_Z: idem but with leading Zeroes only\n  */\n"},{"id":"235356","messageId":"20140226090511.GA32537@sigill.intra.peff.net","threadId":"35914","inReplyTo":"1393330935-22229-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3] tag: support --sort=<spec>","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2014-02-26T09:05:11Z","receivedAt":"2014-02-26T09:05:11Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Feb 25, 2014 at 07:22:15PM +0700, Nguyễn Thái Ngọc Duy wrote:\n\n> versioncmp() is copied from string/strverscmp.c in glibc commit\n> ee9247c38a8def24a59eb5cfb7196a98bef8cfdc, reformatted to Git coding\n> style. The implementation is under LGPL-2.1 and according to [1] I can\n> relicense it to GPLv2.\n\nCool. I think doing this makes the most sense, as we do not have to\nworry about build-time config (and I do not see any particular reason\nwhy we would want to use the system strverscmp on glibc systems).\n\n> +static int parse_opt_sort(const struct option *opt, const char *arg, int unset)\n> +{\n> +\tint *sort = opt->value;\n> +\tif (*arg == '-') {\n> +\t\t*sort = REVERSE_SORT;\n> +\t\targ++;\n> +\t} else\n> +\t\t*sort = STRCMP_SORT;\n> +\tif (starts_with(arg, \"version:\")) {\n> +\t\t*sort |= VERCMP_SORT;\n> +\t\targ += 8;\n> +\t} else if (starts_with(arg, \"v:\")) {\n> +\t\t*sort |= VERCMP_SORT;\n> +\t\targ += 2;\n> +\t}\n> +\tif (strcmp(arg, \"refname\"))\n> +\t\tdie(_(\"unsupported sort specification %s\"), arg);\n\nI found this logic a bit weird, as STRCMP_SORT and VERCMP_SORT are not\nmutually exclusive flags, but REVERSE and STRCMP are. I would have\nthought REVERSE is the flag, and the other two are selections. Like:\n\n  int flags = 0;\n\n  if (*arg == '-') {\n          flags |= REVERSE_SORT;\n          arg++;\n  }\n  if (starts_with(arg, \"version:\")) {\n          *sort = VERCMP_SORT;\n          arg += 8;\n  } else\n          *sort = STRCMP_SORT;\n\n  *sort |= flags;\n\nI think they end up doing the same thing, but maybe I am missing\nsomething.\n\n-Peff\n"},{"id":"235368","messageId":"CACsJy8CW9p-PDJC9zXBwXrxOk9GhBFk1nVtG1Xn76g3FN+KgaA@mail.gmail.com","threadId":"35914","inReplyTo":"20140226090511.GA32537@sigill.intra.peff.net","subject":"Re: [PATCH v3] tag: support --sort=<spec>","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-26T11:03:40Z","receivedAt":"2014-02-26T11:03:40Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Feb 26, 2014 at 4:05 PM, Jeff King <peff@peff.net> wrote:\n> On Tue, Feb 25, 2014 at 07:22:15PM +0700, Nguyễn Thái Ngọc Duy wrote:\n>\n>> versioncmp() is copied from string/strverscmp.c in glibc commit\n>> ee9247c38a8def24a59eb5cfb7196a98bef8cfdc, reformatted to Git coding\n>> style. The implementation is under LGPL-2.1 and according to [1] I can\n>> relicense it to GPLv2.\n>\n> Cool. I think doing this makes the most sense, as we do not have to\n> worry about build-time config (and I do not see any particular reason\n> why we would want to use the system strverscmp on glibc systems).\n\nAnother reason I want to stay away from glibc is I want to fix the\nalgorithm to sort YY-preXX and YY-rcXX before YY. There could be\nreasons that glibc might reject such a change even if it makes sense\nin our context. Even if we make it to newer glibc and fix compat\nversion, people on older glibc will not receive the fix while people\non compat do. Not so good.\n\n>> +static int parse_opt_sort(const struct option *opt, const char *arg, int unset)\n>> +{\n>> +     int *sort = opt->value;\n>> +     if (*arg == '-') {\n>> +             *sort = REVERSE_SORT;\n>> +             arg++;\n>> +     } else\n>> +             *sort = STRCMP_SORT;\n>> +     if (starts_with(arg, \"version:\")) {\n>> +             *sort |= VERCMP_SORT;\n>> +             arg += 8;\n>> +     } else if (starts_with(arg, \"v:\")) {\n>> +             *sort |= VERCMP_SORT;\n>> +             arg += 2;\n>> +     }\n>> +     if (strcmp(arg, \"refname\"))\n>> +             die(_(\"unsupported sort specification %s\"), arg);\n>\n> I found this logic a bit weird, as STRCMP_SORT and VERCMP_SORT are not\n> mutually exclusive flags, but REVERSE and STRCMP are. I would have\n> thought REVERSE is the flag, and the other two are selections. Like:\n>\n>   int flags = 0;\n>\n>   if (*arg == '-') {\n>           flags |= REVERSE_SORT;\n>           arg++;\n>   }\n>   if (starts_with(arg, \"version:\")) {\n>           *sort = VERCMP_SORT;\n>           arg += 8;\n>   } else\n>           *sort = STRCMP_SORT;\n>\n>   *sort |= flags;\n>\n> I think they end up doing the same thing, but maybe I am missing\n> something.\n\nNo you're not. I did not make it absolute beautiful because I expected\nit to be deleted soon after you polish your f-e-r series. Will resend\nwith this change shortly.\n-- \nDuy\n"},{"id":"235370","messageId":"20140226110803.GG25711@sigill.intra.peff.net","threadId":"35914","inReplyTo":"CACsJy8CW9p-PDJC9zXBwXrxOk9GhBFk1nVtG1Xn76g3FN+KgaA@mail.gmail.com","subject":"Re: [PATCH v3] tag: support --sort=<spec>","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2014-02-26T11:08:04Z","receivedAt":"2014-02-26T11:08:04Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Feb 26, 2014 at 06:03:40PM +0700, Duy Nguyen wrote:\n\n> > Cool. I think doing this makes the most sense, as we do not have to\n> > worry about build-time config (and I do not see any particular reason\n> > why we would want to use the system strverscmp on glibc systems).\n> \n> Another reason I want to stay away from glibc is I want to fix the\n> algorithm to sort YY-preXX and YY-rcXX before YY. There could be\n> reasons that glibc might reject such a change even if it makes sense\n> in our context. Even if we make it to newer glibc and fix compat\n> version, people on older glibc will not receive the fix while people\n> on compat do. Not so good.\n\nYeah, the handling of -rc has bugged me, too (in my personal alias, I\njust grep out the -rc before feeding the list to \"sort -V\" :) ).\n\nI'd worry slightly, though, that there are other schemes where that\nbehaves poorly. Should we optimize for git's version numbering, or for\nwhat most other projects want? There could even be room for two types of\nversion-compare. But before thinking about that, I'd want to know why\nglibc behaves as it does.\n\n-Peff\n"},{"id":"235371","messageId":"CACsJy8Aqd4-9z3nb2Hi=nxrRLUYN+9pGiNP7-fnUE_Bn5zRW+Q@mail.gmail.com","threadId":"35914","inReplyTo":"20140226110803.GG25711@sigill.intra.peff.net","subject":"Re: [PATCH v3] tag: support --sort=<spec>","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-26T11:11:54Z","receivedAt":"2014-02-26T11:11:54Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Feb 26, 2014 at 6:08 PM, Jeff King <peff@peff.net> wrote:\n> I'd worry slightly, though, that there are other schemes where that\n> behaves poorly. Should we optimize for git's version numbering, or for\n> what most other projects want? There could even be room for two types of\n> version-compare. But before thinking about that, I'd want to know why\n> glibc behaves as it does.\n\nWe don't have to force one version style for all projects. We could\nprovide --sort=\"thisver:refname\" vs. \"thatver:refname\", or put the\n\"-pre\" part in config file. The important thing is we can control the\nversion algorithm.\n-- \nDuy\n"},{"id":"235373","messageId":"20140226111711.GA3122@sigill.intra.peff.net","threadId":"35914","inReplyTo":"CACsJy8Aqd4-9z3nb2Hi=nxrRLUYN+9pGiNP7-fnUE_Bn5zRW+Q@mail.gmail.com","subject":"Re: [PATCH v3] tag: support --sort=<spec>","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2014-02-26T11:17:11Z","receivedAt":"2014-02-26T11:17:11Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Feb 26, 2014 at 06:11:54PM +0700, Duy Nguyen wrote:\n\n> On Wed, Feb 26, 2014 at 6:08 PM, Jeff King <peff@peff.net> wrote:\n> > I'd worry slightly, though, that there are other schemes where that\n> > behaves poorly. Should we optimize for git's version numbering, or for\n> > what most other projects want? There could even be room for two types of\n> > version-compare. But before thinking about that, I'd want to know why\n> > glibc behaves as it does.\n> \n> We don't have to force one version style for all projects. We could\n> provide --sort=\"thisver:refname\" vs. \"thatver:refname\", or put the\n> \"-pre\" part in config file. The important thing is we can control the\n> version algorithm.\n\nRight, exactly. It may make sense to just do it the way _we_ think is\nsensible for now, then, and we can add a glibc-compatible one later if\nsomebody actually wants it.\n\n-Peff\n"},{"id":"235379","messageId":"CACsJy8An3GH7LMx4=s-7hO3waphcC2nGK1N-CvPYbh+=Zzngqw@mail.gmail.com","threadId":"35914","inReplyTo":"20140226110803.GG25711@sigill.intra.peff.net","subject":"Re: [PATCH v3] tag: support --sort=<spec>","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-26T11:31:58Z","receivedAt":"2014-02-26T11:31:58Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Feb 26, 2014 at 6:08 PM, Jeff King <peff@peff.net> wrote:\n> But before thinking about that, I'd want to know why\n> glibc behaves as it does.\n\nPure guess. It may be because it targets more than software version.\nIn strverscmp man page, the example is \"jan1, jan10, jan2...\".\nversionsort() in glibc might be the reason that strverscmp was added\nand it's used to compare/sort dir entries, so the target might be\nnumbered log files.\n-- \nDuy\n"},{"id":"235468","messageId":"1393505812-7171-1-git-send-email-pclouds@gmail.com","threadId":"35914","inReplyTo":"1393330935-22229-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4] tag: support --sort=<spec>","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-27T12:56:52Z","receivedAt":"2014-02-27T12:56:52Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"--sort=version:refname (or --sort=v:refname for short) sorts tags as\nif they are versions. --sort=-refname reverses the order (with or\nwithout \":version\").\n\nversioncmp() is copied from string/strverscmp.c in glibc commit\nee9247c38a8def24a59eb5cfb7196a98bef8cfdc, reformatted to Git coding\nstyle. The implementation is under LGPL-2.1 and according to [1] I can\nrelicense it to GPLv2.\n\n[1] http://www.gnu.org/licenses/gpl-faq.html#AllCompatibility\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n - add the relicensing note in versioncmp.c\n - reorder the logic in parse_opt_sort for better readability\n - include cache.h instead of git-compat-util.h in versioncmp.c\n\n Documentation/git-tag.txt |  6 ++++\n Makefile                  |  1 +\n builtin/tag.c             | 71 +++++++++++++++++++++++++++++++++---\n cache.h                   |  2 ++\n t/t7004-tag.sh            | 43 ++++++++++++++++++++++\n versioncmp.c (new)        | 92 +++++++++++++++++++++++++++++++++++++++++++++++\n 6 files changed, 210 insertions(+), 5 deletions(-)\n create mode 100644 versioncmp.c\n\ndiff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\nindex 404257d..9b05931 100644\n--- a/Documentation/git-tag.txt\n+++ b/Documentation/git-tag.txt\n@@ -95,6 +95,12 @@ OPTIONS\n \tusing fnmatch(3)).  Multiple patterns may be given; if any of\n \tthem matches, the tag is shown.\n \n+--sort=<type>::\n+\tSort in a specific order. Supported type is \"refname\"\n+\t(lexicographic order), \"version:refname\" or \"v:refname\" (tags\n+\tname are treated as versions). Prepend \"-\" to reverse sort\n+\torder.\n+\n --column[=<options>]::\n --no-column::\n \tDisplay tag listing in columns. See configuration variable\ndiff --git a/Makefile b/Makefile\nindex dddaf4f..16b00a5 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -884,6 +884,7 @@ LIB_OBJS += userdiff.o\n LIB_OBJS += utf8.o\n LIB_OBJS += varint.o\n LIB_OBJS += version.o\n+LIB_OBJS += versioncmp.o\n LIB_OBJS += walker.o\n LIB_OBJS += wildmatch.o\n LIB_OBJS += wrapper.o\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 74d3780..0439c48 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -27,9 +27,16 @@ static const char * const git_tag_usage[] = {\n \tNULL\n };\n \n+#define STRCMP_SORT     0\t/* must be zero */\n+#define VERCMP_SORT     1\n+#define SORT_MASK       0x7fff\n+#define REVERSE_SORT    0x8000\n+\n struct tag_filter {\n \tconst char **patterns;\n \tint lines;\n+\tint sort;\n+\tstruct string_list tags;\n \tstruct commit_list *with_commit;\n };\n \n@@ -166,7 +173,10 @@ static int show_reference(const char *refname, const unsigned char *sha1,\n \t\t\treturn 0;\n \n \t\tif (!filter->lines) {\n-\t\t\tprintf(\"%s\\n\", refname);\n+\t\t\tif (filter->sort)\n+\t\t\t\tstring_list_append(&filter->tags, refname);\n+\t\t\telse\n+\t\t\t\tprintf(\"%s\\n\", refname);\n \t\t\treturn 0;\n \t\t}\n \t\tprintf(\"%-15s \", refname);\n@@ -177,17 +187,39 @@ static int show_reference(const char *refname, const unsigned char *sha1,\n \treturn 0;\n }\n \n+static int sort_by_version(const void *a_, const void *b_)\n+{\n+\tconst struct string_list_item *a = a_;\n+\tconst struct string_list_item *b = b_;\n+\treturn versioncmp(a->string, b->string);\n+}\n+\n static int list_tags(const char **patterns, int lines,\n-\t\t\tstruct commit_list *with_commit)\n+\t\t     struct commit_list *with_commit, int sort)\n {\n \tstruct tag_filter filter;\n \n \tfilter.patterns = patterns;\n \tfilter.lines = lines;\n+\tfilter.sort = sort;\n \tfilter.with_commit = with_commit;\n+\tmemset(&filter.tags, 0, sizeof(filter.tags));\n+\tfilter.tags.strdup_strings = 1;\n \n \tfor_each_tag_ref(show_reference, (void *) &filter);\n-\n+\tif (sort) {\n+\t\tint i;\n+\t\tif ((sort & SORT_MASK) == VERCMP_SORT)\n+\t\t\tqsort(filter.tags.items, filter.tags.nr,\n+\t\t\t      sizeof(struct string_list_item), sort_by_version);\n+\t\tif (sort & REVERSE_SORT)\n+\t\t\tfor (i = filter.tags.nr - 1; i >= 0; i--)\n+\t\t\t\tprintf(\"%s\\n\", filter.tags.items[i].string);\n+\t\telse\n+\t\t\tfor (i = 0; i < filter.tags.nr; i++)\n+\t\t\t\tprintf(\"%s\\n\", filter.tags.items[i].string);\n+\t\tstring_list_clear(&filter.tags, 0);\n+\t}\n \treturn 0;\n }\n \n@@ -427,6 +459,29 @@ static int parse_opt_points_at(const struct option *opt __attribute__((unused)),\n \treturn 0;\n }\n \n+static int parse_opt_sort(const struct option *opt, const char *arg, int unset)\n+{\n+\tint *sort = opt->value;\n+\tint flags = 0;\n+\n+\tif (*arg == '-') {\n+\t\tflags |= REVERSE_SORT;\n+\t\targ++;\n+\t}\n+\tif (starts_with(arg, \"version:\")) {\n+\t\t*sort = VERCMP_SORT;\n+\t\targ += 8;\n+\t} else if (starts_with(arg, \"v:\")) {\n+\t\t*sort = VERCMP_SORT;\n+\t\targ += 2;\n+\t} else\n+\t\t*sort = STRCMP_SORT;\n+\tif (strcmp(arg, \"refname\"))\n+\t\tdie(_(\"unsupported sort specification %s\"), arg);\n+\t*sort |= flags;\n+\treturn 0;\n+}\n+\n int cmd_tag(int argc, const char **argv, const char *prefix)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -437,7 +492,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstruct create_tag_options opt;\n \tchar *cleanup_arg = NULL;\n \tint annotate = 0, force = 0, lines = -1;\n-\tint cmdmode = 0;\n+\tint cmdmode = 0, sort = 0;\n \tconst char *msgfile = NULL, *keyid = NULL;\n \tstruct msg_arg msg = { 0, STRBUF_INIT };\n \tstruct commit_list *with_commit = NULL;\n@@ -462,6 +517,10 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\t\t\tN_(\"use another key to sign the tag\")),\n \t\tOPT__FORCE(&force, N_(\"replace the tag if exists\")),\n \t\tOPT_COLUMN(0, \"column\", &colopts, N_(\"show tag list in columns\")),\n+\t\t{\n+\t\t\tOPTION_CALLBACK, 0, \"sort\", &sort, N_(\"type\"), N_(\"sort tags\"),\n+\t\t\tPARSE_OPT_NONEG, parse_opt_sort\n+\t\t},\n \n \t\tOPT_GROUP(N_(\"Tag listing options\")),\n \t\t{\n@@ -509,7 +568,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\tcopts.padding = 2;\n \t\t\trun_column_filter(colopts, &copts);\n \t\t}\n-\t\tret = list_tags(argv, lines == -1 ? 0 : lines, with_commit);\n+\t\tif (lines != -1 && sort)\n+\t\t\tdie(_(\"--sort and -n are incompatible\"));\n+\t\tret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, sort);\n \t\tif (column_active(colopts))\n \t\t\tstop_column_filter();\n \t\treturn ret;\ndiff --git a/cache.h b/cache.h\nindex dc040fb..082a783 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1367,4 +1367,6 @@ int stat_validity_check(struct stat_validity *sv, const char *path);\n  */\n void stat_validity_update(struct stat_validity *sv, int fd);\n \n+int versioncmp(const char *s1, const char *s2);\n+\n #endif /* CACHE_H */\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex c8d6e9f..143a8ea 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -1380,4 +1380,47 @@ test_expect_success 'multiple --points-at are OR-ed together' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'lexical sort' '\n+\tgit tag foo1.3 &&\n+\tgit tag foo1.6 &&\n+\tgit tag foo1.10 &&\n+\tgit tag -l --sort=refname \"foo*\" >actual &&\n+\tcat >expect <<EOF &&\n+foo1.10\n+foo1.3\n+foo1.6\n+EOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'version sort' '\n+\tgit tag -l --sort=version:refname \"foo*\" >actual &&\n+\tcat >expect <<EOF &&\n+foo1.3\n+foo1.6\n+foo1.10\n+EOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'reverse version sort' '\n+\tgit tag -l --sort=-version:refname \"foo*\" >actual &&\n+\tcat >expect <<EOF &&\n+foo1.10\n+foo1.6\n+foo1.3\n+EOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'reverse lexical sort' '\n+\tgit tag -l --sort=-refname \"foo*\" >actual &&\n+\tcat >expect <<EOF &&\n+foo1.6\n+foo1.3\n+foo1.10\n+EOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\ndiff --git a/versioncmp.c b/versioncmp.c\nnew file mode 100644\nindex 0000000..3c876bf\n--- /dev/null\n+++ b/versioncmp.c\n@@ -0,0 +1,92 @@\n+#include \"cache.h\"\n+\n+/*\n+ * versioncmp(): copied from string/strverscmp.c in glibc commit\n+ * ee9247c38a8def24a59eb5cfb7196a98bef8cfdc, reformatted to Git coding\n+ * style. The implementation is under LGPL-2.1 and Git relicenses it\n+ * to GPLv2.\n+ */\n+\n+/*\n+ * states: S_N: normal, S_I: comparing integral part, S_F: comparing\n+ * fractionnal parts, S_Z: idem but with leading Zeroes only\n+ */\n+#define  S_N    0x0\n+#define  S_I    0x3\n+#define  S_F    0x6\n+#define  S_Z    0x9\n+\n+/* result_type: CMP: return diff; LEN: compare using len_diff/diff */\n+#define  CMP    2\n+#define  LEN    3\n+\n+\n+/*\n+ * Compare S1 and S2 as strings holding indices/version numbers,\n+ * returning less than, equal to or greater than zero if S1 is less\n+ * than, equal to or greater than S2 (for more info, see the texinfo\n+ * doc).\n+ */\n+\n+int versioncmp(const char *s1, const char *s2)\n+{\n+\tconst unsigned char *p1 = (const unsigned char *) s1;\n+\tconst unsigned char *p2 = (const unsigned char *) s2;\n+\n+\t/*\n+\t * Symbol(s)    0       [1-9]   others\n+\t * Transition   (10) 0  (01) d  (00) x\n+\t */\n+\tstatic const uint8_t next_state[] = {\n+\t\t/* state    x    d    0  */\n+\t\t/* S_N */  S_N, S_I, S_Z,\n+\t\t/* S_I */  S_N, S_I, S_I,\n+\t\t/* S_F */  S_N, S_F, S_F,\n+\t\t/* S_Z */  S_N, S_F, S_Z\n+\t};\n+\n+\tstatic const int8_t result_type[] = {\n+\t\t/* state   x/x  x/d  x/0  d/x  d/d  d/0  0/x  0/d  0/0  */\n+\n+\t\t/* S_N */  CMP, CMP, CMP, CMP, LEN, CMP, CMP, CMP, CMP,\n+\t\t/* S_I */  CMP, -1,  -1,  +1,  LEN, LEN, +1,  LEN, LEN,\n+\t\t/* S_F */  CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP,\n+\t\t/* S_Z */  CMP, +1,  +1,  -1,  CMP, CMP, -1,  CMP, CMP\n+\t};\n+\n+\tif (p1 == p2)\n+\t\treturn 0;\n+\n+\tunsigned char c1 = *p1++;\n+\tunsigned char c2 = *p2++;\n+\t/* Hint: '0' is a digit too.  */\n+\tint state = S_N + ((c1 == '0') + (isdigit (c1) != 0));\n+\n+\tint diff;\n+\twhile ((diff = c1 - c2) == 0) {\n+\t\tif (c1 == '\\0')\n+\t\t\treturn diff;\n+\n+\t\tstate = next_state[state];\n+\t\tc1 = *p1++;\n+\t\tc2 = *p2++;\n+\t\tstate += (c1 == '0') + (isdigit (c1) != 0);\n+\t}\n+\n+\tstate = result_type[state * 3 + (((c2 == '0') + (isdigit (c2) != 0)))];\n+\n+\tswitch (state) {\n+\tcase CMP:\n+\t\treturn diff;\n+\n+\tcase LEN:\n+\t\twhile (isdigit (*p1++))\n+\t\t\tif (!isdigit (*p2++))\n+\t\t\t\treturn 1;\n+\n+\t\treturn isdigit (*p2) ? -1 : diff;\n+\n+\tdefault:\n+\t\treturn state;\n+\t}\n+}\n-- \n1.9.0.40.gaa8c3ea\n"},{"id":"235470","messageId":"20140227131100.GA22783@sigill.intra.peff.net","threadId":"35914","inReplyTo":"1393505812-7171-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v4] tag: support --sort=<spec>","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2014-02-27T13:11:00Z","receivedAt":"2014-02-27T13:11:00Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Feb 27, 2014 at 07:56:52PM +0700, Nguyễn Thái Ngọc Duy wrote:\n\n> --sort=version:refname (or --sort=v:refname for short) sorts tags as\n> if they are versions. --sort=-refname reverses the order (with or\n> without \":version\").\n> \n> versioncmp() is copied from string/strverscmp.c in glibc commit\n> ee9247c38a8def24a59eb5cfb7196a98bef8cfdc, reformatted to Git coding\n> style. The implementation is under LGPL-2.1 and according to [1] I can\n> relicense it to GPLv2.\n\nThis looks good to me. One minor typo:\n\n> diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\n> index 404257d..9b05931 100644\n> --- a/Documentation/git-tag.txt\n> +++ b/Documentation/git-tag.txt\n> @@ -95,6 +95,12 @@ OPTIONS\n>  \tusing fnmatch(3)).  Multiple patterns may be given; if any of\n>  \tthem matches, the tag is shown.\n>  \n> +--sort=<type>::\n> +\tSort in a specific order. Supported type is \"refname\"\n> +\t(lexicographic order), \"version:refname\" or \"v:refname\" (tags\n> +\tname are treated as versions). Prepend \"-\" to reverse sort\n\ns/tags name/tag names/\n\nYou had mentioned earlier tweaking the version comparison to handle\nthings like -rc better. I think that can come on top of this initial\npatch, but we should probably figure out the final sort order before\nincluding this in a release.\n\n-Peff\n"},{"id":"235474","messageId":"CACsJy8DZH_Bi3UCNuGTA4K47Qp=h+52kPxcuRDjFt19je3DWqw@mail.gmail.com","threadId":"35914","inReplyTo":"20140227131100.GA22783@sigill.intra.peff.net","subject":"Re: [PATCH v4] tag: support --sort=<spec>","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2014-02-27T13:23:17Z","receivedAt":"2014-02-27T13:23:17Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Feb 27, 2014 at 8:11 PM, Jeff King <peff@peff.net> wrote:\n> You had mentioned earlier tweaking the version comparison to handle\n> things like -rc better. I think that can come on top of this initial\n> patch, but we should probably figure out the final sort order before\n> including this in a release.\n\nYeah I have been thinking about it too. That's why I did not send the\nreroll \"shortly\" as I said. So far I like the idea of using config\nfile to specify -rc and the like. If XX is in the configured list and\nit's the last component in the version string, then reorder it. Seems\nto work well with different versioning schemes listed at\nhttp://en.wikipedia.org/wiki/Software_versioning.\n-- \nDuy\n"}]}