git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v2] tag: support --sort=<spec>

From
Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Date
Feb 22, 2014, 03:29 UTC
Message-ID
<1393039762-4843-1-git-send-email-pclouds@gmail.com>
In-Reply-To
<1392817167-29802-1-git-send-email-pclouds@gmail.com>

--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@gmail.com>
---
 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
Previous: Eric SunshineNext: Jeff King
Message 10 of 25 in “tag: support --sort=version”
  1. tag: support --sort=versionNguyễn Thái Ngọc Duy, Feb 19, 2014
  2. Jeff KingFeb 19, 2014
  3. Duy NguyenFeb 19, 2014
  4. Jeff KingFeb 20, 2014
  5. Duy NguyenFeb 21, 2014
  6. Junio C HamanoFeb 21, 2014
  7. Jeff KingFeb 22, 2014
  8. Duy NguyenFeb 22, 2014
  9. Eric SunshineFeb 19, 2014
  10. tag: support --sort=<spec>Nguyễn Thái Ngọc Duy, Feb 22, 2014
  11. Jeff KingFeb 22, 2014
  12. Junio C HamanoFeb 24, 2014
  13. Duy NguyenFeb 24, 2014
  14. Jeff KingFeb 24, 2014
  15. tag: support --sort=<spec>Nguyễn Thái Ngọc Duy, Feb 25, 2014
  16. Junio C HamanoFeb 25, 2014
  17. Jeff KingFeb 26, 2014
  18. Duy NguyenFeb 26, 2014
  19. Jeff KingFeb 26, 2014
  20. Duy NguyenFeb 26, 2014
  21. Jeff KingFeb 26, 2014
  22. Duy NguyenFeb 26, 2014
  23. tag: support --sort=<spec>Nguyễn Thái Ngọc Duy, Feb 27, 2014
  24. Jeff KingFeb 27, 2014
  25. Duy NguyenFeb 27, 2014

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.