Skip to content

Commit b6de0c6

Browse files
committed
Merge branch 'nd/tag-version-sort'
Allow v1.9.0 sorted before v1.10.0 in "git tag --list" output. * nd/tag-version-sort: tag: support --sort=<spec>
2 parents 3e14384 + 9ef176b commit b6de0c6

File tree

6 files changed

+211
-5
lines changed

6 files changed

+211
-5
lines changed

Documentation/git-tag.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ OPTIONS
9595
using fnmatch(3)). Multiple patterns may be given; if any of
9696
them matches, the tag is shown.
9797

98+
--sort=<type>::
99+
Sort in a specific order. Supported type is "refname"
100+
(lexicographic order), "version:refname" or "v:refname" (tag
101+
names are treated as versions). Prepend "-" to reverse sort
102+
order.
103+
98104
--column[=<options>]::
99105
--no-column::
100106
Display tag listing in columns. See configuration variable

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -892,6 +892,7 @@ LIB_OBJS += userdiff.o
892892
LIB_OBJS += utf8.o
893893
LIB_OBJS += varint.o
894894
LIB_OBJS += version.o
895+
LIB_OBJS += versioncmp.o
895896
LIB_OBJS += walker.o
896897
LIB_OBJS += wildmatch.o
897898
LIB_OBJS += wrapper.o

builtin/tag.c

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,16 @@ static const char * const git_tag_usage[] = {
2727
NULL
2828
};
2929

30+
#define STRCMP_SORT 0 /* must be zero */
31+
#define VERCMP_SORT 1
32+
#define SORT_MASK 0x7fff
33+
#define REVERSE_SORT 0x8000
34+
3035
struct tag_filter {
3136
const char **patterns;
3237
int lines;
38+
int sort;
39+
struct string_list tags;
3340
struct commit_list *with_commit;
3441
};
3542

@@ -166,7 +173,10 @@ static int show_reference(const char *refname, const unsigned char *sha1,
166173
return 0;
167174

168175
if (!filter->lines) {
169-
printf("%s\n", refname);
176+
if (filter->sort)
177+
string_list_append(&filter->tags, refname);
178+
else
179+
printf("%s\n", refname);
170180
return 0;
171181
}
172182
printf("%-15s ", refname);
@@ -177,17 +187,39 @@ static int show_reference(const char *refname, const unsigned char *sha1,
177187
return 0;
178188
}
179189

190+
static int sort_by_version(const void *a_, const void *b_)
191+
{
192+
const struct string_list_item *a = a_;
193+
const struct string_list_item *b = b_;
194+
return versioncmp(a->string, b->string);
195+
}
196+
180197
static int list_tags(const char **patterns, int lines,
181-
struct commit_list *with_commit)
198+
struct commit_list *with_commit, int sort)
182199
{
183200
struct tag_filter filter;
184201

185202
filter.patterns = patterns;
186203
filter.lines = lines;
204+
filter.sort = sort;
187205
filter.with_commit = with_commit;
206+
memset(&filter.tags, 0, sizeof(filter.tags));
207+
filter.tags.strdup_strings = 1;
188208

189209
for_each_tag_ref(show_reference, (void *) &filter);
190-
210+
if (sort) {
211+
int i;
212+
if ((sort & SORT_MASK) == VERCMP_SORT)
213+
qsort(filter.tags.items, filter.tags.nr,
214+
sizeof(struct string_list_item), sort_by_version);
215+
if (sort & REVERSE_SORT)
216+
for (i = filter.tags.nr - 1; i >= 0; i--)
217+
printf("%s\n", filter.tags.items[i].string);
218+
else
219+
for (i = 0; i < filter.tags.nr; i++)
220+
printf("%s\n", filter.tags.items[i].string);
221+
string_list_clear(&filter.tags, 0);
222+
}
191223
return 0;
192224
}
193225

@@ -427,6 +459,29 @@ static int parse_opt_points_at(const struct option *opt __attribute__((unused)),
427459
return 0;
428460
}
429461

462+
static int parse_opt_sort(const struct option *opt, const char *arg, int unset)
463+
{
464+
int *sort = opt->value;
465+
int flags = 0;
466+
467+
if (*arg == '-') {
468+
flags |= REVERSE_SORT;
469+
arg++;
470+
}
471+
if (starts_with(arg, "version:")) {
472+
*sort = VERCMP_SORT;
473+
arg += 8;
474+
} else if (starts_with(arg, "v:")) {
475+
*sort = VERCMP_SORT;
476+
arg += 2;
477+
} else
478+
*sort = STRCMP_SORT;
479+
if (strcmp(arg, "refname"))
480+
die(_("unsupported sort specification %s"), arg);
481+
*sort |= flags;
482+
return 0;
483+
}
484+
430485
int cmd_tag(int argc, const char **argv, const char *prefix)
431486
{
432487
struct strbuf buf = STRBUF_INIT;
@@ -437,7 +492,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
437492
struct create_tag_options opt;
438493
char *cleanup_arg = NULL;
439494
int annotate = 0, force = 0, lines = -1;
440-
int cmdmode = 0;
495+
int cmdmode = 0, sort = 0;
441496
const char *msgfile = NULL, *keyid = NULL;
442497
struct msg_arg msg = { 0, STRBUF_INIT };
443498
struct commit_list *with_commit = NULL;
@@ -462,6 +517,10 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
462517
N_("use another key to sign the tag")),
463518
OPT__FORCE(&force, N_("replace the tag if exists")),
464519
OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
520+
{
521+
OPTION_CALLBACK, 0, "sort", &sort, N_("type"), N_("sort tags"),
522+
PARSE_OPT_NONEG, parse_opt_sort
523+
},
465524

466525
OPT_GROUP(N_("Tag listing options")),
467526
{
@@ -515,7 +574,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
515574
copts.padding = 2;
516575
run_column_filter(colopts, &copts);
517576
}
518-
ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit);
577+
if (lines != -1 && sort)
578+
die(_("--sort and -n are incompatible"));
579+
ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, sort);
519580
if (column_active(colopts))
520581
stop_column_filter();
521582
return ret;

cache.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1458,4 +1458,6 @@ int stat_validity_check(struct stat_validity *sv, const char *path);
14581458
*/
14591459
void stat_validity_update(struct stat_validity *sv, int fd);
14601460

1461+
int versioncmp(const char *s1, const char *s2);
1462+
14611463
#endif /* CACHE_H */

t/t7004-tag.sh

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,4 +1380,47 @@ test_expect_success 'multiple --points-at are OR-ed together' '
13801380
test_cmp expect actual
13811381
'
13821382
1383+
test_expect_success 'lexical sort' '
1384+
git tag foo1.3 &&
1385+
git tag foo1.6 &&
1386+
git tag foo1.10 &&
1387+
git tag -l --sort=refname "foo*" >actual &&
1388+
cat >expect <<EOF &&
1389+
foo1.10
1390+
foo1.3
1391+
foo1.6
1392+
EOF
1393+
test_cmp expect actual
1394+
'
1395+
1396+
test_expect_success 'version sort' '
1397+
git tag -l --sort=version:refname "foo*" >actual &&
1398+
cat >expect <<EOF &&
1399+
foo1.3
1400+
foo1.6
1401+
foo1.10
1402+
EOF
1403+
test_cmp expect actual
1404+
'
1405+
1406+
test_expect_success 'reverse version sort' '
1407+
git tag -l --sort=-version:refname "foo*" >actual &&
1408+
cat >expect <<EOF &&
1409+
foo1.10
1410+
foo1.6
1411+
foo1.3
1412+
EOF
1413+
test_cmp expect actual
1414+
'
1415+
1416+
test_expect_success 'reverse lexical sort' '
1417+
git tag -l --sort=-refname "foo*" >actual &&
1418+
cat >expect <<EOF &&
1419+
foo1.6
1420+
foo1.3
1421+
foo1.10
1422+
EOF
1423+
test_cmp expect actual
1424+
'
1425+
13831426
test_done

versioncmp.c

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#include "cache.h"
2+
3+
/*
4+
* versioncmp(): copied from string/strverscmp.c in glibc commit
5+
* ee9247c38a8def24a59eb5cfb7196a98bef8cfdc, reformatted to Git coding
6+
* style. The implementation is under LGPL-2.1 and Git relicenses it
7+
* to GPLv2.
8+
*/
9+
10+
/*
11+
* states: S_N: normal, S_I: comparing integral part, S_F: comparing
12+
* fractionnal parts, S_Z: idem but with leading Zeroes only
13+
*/
14+
#define S_N 0x0
15+
#define S_I 0x3
16+
#define S_F 0x6
17+
#define S_Z 0x9
18+
19+
/* result_type: CMP: return diff; LEN: compare using len_diff/diff */
20+
#define CMP 2
21+
#define LEN 3
22+
23+
24+
/*
25+
* Compare S1 and S2 as strings holding indices/version numbers,
26+
* returning less than, equal to or greater than zero if S1 is less
27+
* than, equal to or greater than S2 (for more info, see the texinfo
28+
* doc).
29+
*/
30+
31+
int versioncmp(const char *s1, const char *s2)
32+
{
33+
const unsigned char *p1 = (const unsigned char *) s1;
34+
const unsigned char *p2 = (const unsigned char *) s2;
35+
unsigned char c1, c2;
36+
int state, diff;
37+
38+
/*
39+
* Symbol(s) 0 [1-9] others
40+
* Transition (10) 0 (01) d (00) x
41+
*/
42+
static const uint8_t next_state[] = {
43+
/* state x d 0 */
44+
/* S_N */ S_N, S_I, S_Z,
45+
/* S_I */ S_N, S_I, S_I,
46+
/* S_F */ S_N, S_F, S_F,
47+
/* S_Z */ S_N, S_F, S_Z
48+
};
49+
50+
static const int8_t result_type[] = {
51+
/* state x/x x/d x/0 d/x d/d d/0 0/x 0/d 0/0 */
52+
53+
/* S_N */ CMP, CMP, CMP, CMP, LEN, CMP, CMP, CMP, CMP,
54+
/* S_I */ CMP, -1, -1, +1, LEN, LEN, +1, LEN, LEN,
55+
/* S_F */ CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP,
56+
/* S_Z */ CMP, +1, +1, -1, CMP, CMP, -1, CMP, CMP
57+
};
58+
59+
if (p1 == p2)
60+
return 0;
61+
62+
c1 = *p1++;
63+
c2 = *p2++;
64+
/* Hint: '0' is a digit too. */
65+
state = S_N + ((c1 == '0') + (isdigit (c1) != 0));
66+
67+
while ((diff = c1 - c2) == 0) {
68+
if (c1 == '\0')
69+
return diff;
70+
71+
state = next_state[state];
72+
c1 = *p1++;
73+
c2 = *p2++;
74+
state += (c1 == '0') + (isdigit (c1) != 0);
75+
}
76+
77+
state = result_type[state * 3 + (((c2 == '0') + (isdigit (c2) != 0)))];
78+
79+
switch (state) {
80+
case CMP:
81+
return diff;
82+
83+
case LEN:
84+
while (isdigit (*p1++))
85+
if (!isdigit (*p2++))
86+
return 1;
87+
88+
return isdigit (*p2) ? -1 : diff;
89+
90+
default:
91+
return state;
92+
}
93+
}

0 commit comments

Comments
 (0)