tag: support --sort=<spec>
--sort=version:refname (or --sort=v:refname for short) sorts tags as if they are versions. --sort=-refname reverses the order (with or without ":version"). versioncmp() is copied from string/strverscmp.c in glibc commit ee9247c38a8def24a59eb5cfb7196a98bef8cfdc, reformatted to Git coding style. The implementation is under LGPL-2.1 and according to [1] I can relicense it to GPLv2. [1] http://www.gnu.org/licenses/gpl-faq.html#AllCompatibility Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Nguyễn Thái Ngọc Duy committed
Feb 27, 2014 at 19:56 UTC
9ef176b55c373ba087b2d84e77b5713578891927
6 files changed
+211
-5
Documentation/git-tag.txt
+6
@@ -95,6 +95,12 @@ OPTIONS
95
using fnmatch(3)). Multiple patterns may be given; if any of
96
them matches, the tag is shown.
97
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
+
104
--column[=<options>]::
105
--no-column::
106
Display tag listing in columns. See configuration variable
Makefile
+1
@@ -884,6 +884,7 @@ LIB_OBJS += userdiff.o
884
LIB_OBJS += utf8.o
885
LIB_OBJS += varint.o
886
LIB_OBJS += version.o
887
+LIB_OBJS += versioncmp.o
888
LIB_OBJS += walker.o
889
LIB_OBJS += wildmatch.o
890
LIB_OBJS += wrapper.o
builtin/tag.c
+66
-5
@@ -27,9 +27,16 @@ static const char * const git_tag_usage[] = {
27
NULL
28
};
29
30
+#define STRCMP_SORT 0 /* must be zero */
31
+#define VERCMP_SORT 1
32
+#define SORT_MASK 0x7fff
33
+#define REVERSE_SORT 0x8000
34
+
35
struct tag_filter {
36
const char **patterns;
37
int lines;
38
+ int sort;
39
+ struct string_list tags;
40
struct commit_list *with_commit;
41
};
42
@@ -166,7 +173,10 @@ static int show_reference(const char *refname, const unsigned char *sha1,
173
return 0;
174
175
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);
180
return 0;
181
}
182
printf("%-15s ", refname);
@@ -177,17 +187,39 @@ static int show_reference(const char *refname, const unsigned char *sha1,
187
return 0;
188
}
189
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
+
197
static int list_tags(const char **patterns, int lines,
181
- struct commit_list *with_commit)
198
+ struct commit_list *with_commit, int sort)
199
{
200
struct tag_filter filter;
201
202
filter.patterns = patterns;
203
filter.lines = lines;
204
+ filter.sort = sort;
205
filter.with_commit = with_commit;
206
+ memset(&filter.tags, 0, sizeof(filter.tags));
207
+ filter.tags.strdup_strings = 1;
208
209
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
+ }
223
return 0;
224
}
225
@@ -427,6 +459,29 @@ static int parse_opt_points_at(const struct option *opt __attribute__((unused)),
459
return 0;
460
}
461
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
+
485
int cmd_tag(int argc, const char **argv, const char *prefix)
486
{
487
struct strbuf buf = STRBUF_INIT;
@@ -437,7 +492,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
492
struct create_tag_options opt;
493
char *cleanup_arg = NULL;
494
int annotate = 0, force = 0, lines = -1;
440
- int cmdmode = 0;
495
+ int cmdmode = 0, sort = 0;
496
const char *msgfile = NULL, *keyid = NULL;
497
struct msg_arg msg = { 0, STRBUF_INIT };
498
struct commit_list *with_commit = NULL;
@@ -462,6 +517,10 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
517
N_("use another key to sign the tag")),
518
OPT__FORCE(&force, N_("replace the tag if exists")),
519
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
+ },
524
525
OPT_GROUP(N_("Tag listing options")),
526
{
@@ -509,7 +568,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
568
copts.padding = 2;
569
run_column_filter(colopts, &copts);
570
}
512
- ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit);
571
+ if (lines != -1 && sort)
572
+ die(_("--sort and -n are incompatible"));
573
+ ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, sort);
574
if (column_active(colopts))
575
stop_column_filter();
576
return ret;
cache.h
+2
@@ -1367,4 +1367,6 @@ int stat_validity_check(struct stat_validity *sv, const char *path);
1367
*/
1368
void stat_validity_update(struct stat_validity *sv, int fd);
1369
1370
+int versioncmp(const char *s1, const char *s2);
1371
+
1372
#endif /* CACHE_H */
t/t7004-tag.sh
+43
@@ -1380,4 +1380,47 @@ test_expect_success 'multiple --points-at are OR-ed together' '
1380
test_cmp expect actual
1381
'
1382
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
+
1426
test_done
versioncmp.c
new
+93
@@ -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
+}