Skip to content

Commit b823166

Browse files
szedergitster
authored andcommitted
versioncmp: cope with common part overlapping with prerelease suffix
Version sort with prerelease reordering sometimes puts tagnames in the wrong order, when the common part of two compared tagnames overlaps with the leading character(s) of one or more configured prerelease suffixes. Note the position of "v2.1.0-beta-1": $ git -c versionsort.prereleaseSuffix=-beta \ tag -l --sort=version:refname v2.1.* v2.1.0-beta-2 v2.1.0-beta-3 v2.1.0 v2.1.0-RC1 v2.1.0-RC2 v2.1.0-beta-1 v2.1.1 v2.1.2 The reason is that when comparing a pair of tagnames, first versioncmp() looks for the first different character in a pair of tagnames, and then the swap_prereleases() helper function looks for a configured prerelease suffix _starting at_ that character. Thus, when in the above example the sorting algorithm happens to compare the tagnames "v2.1.0-beta-1" and "v2.1.0-RC2", swap_prereleases() tries to match the suffix "-beta" against "beta-1" to no avail, and the two tagnames erroneously end up being ordered lexicographically. To fix this issue change swap_prereleases() to look for configured prerelease suffixes _containing_ the position of that first different character. Care must be taken, when a configured suffix is longer than the tagnames' common part up to the first different character, to avoid reading memory before the beginning of the tagnames. Add a test that uses an exceptionally long prerelease suffix to check for this, in the hope that in case of a regression the illegal memory access causes a segfault in 'git tag' on one of the commonly used platforms (the test happens to pass successfully on my Linux system with the safety check removed), or at least makes valgrind complain. Under some circumstances it's possible that more than one prerelease suffixes can be found in the same tagname around that first different character. With this simple bugfix patch such a tagname is sorted according to the contained suffix that comes first in the configuration for now. This is less than ideal in some cases, and the following patch will take care of those. Reported-by: Leho Kraav <leho@conversionready.com> Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 109064a commit b823166

File tree

3 files changed

+34
-11
lines changed

3 files changed

+34
-11
lines changed

Documentation/config.txt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3047,8 +3047,12 @@ versionsort.prereleaseSuffix::
30473047
This variable can be specified multiple times, once per suffix. The
30483048
order of suffixes in the config file determines the sorting order
30493049
(e.g. if "-pre" appears before "-rc" in the config file then 1.0-preXX
3050-
is sorted before 1.0-rcXX). The sorting order between different
3051-
suffixes is undefined if they are in multiple config files.
3050+
is sorted before 1.0-rcXX).
3051+
If more than one suffixes match the same tagname, then that tagname will
3052+
be sorted according to the matching suffix which comes first in the
3053+
configuration.
3054+
The sorting order between different suffixes is undefined if they are
3055+
in multiple config files.
30523056

30533057
web.browser::
30543058
Specify a web browser that may be used by some commands.

t/t7004-tag.sh

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1538,7 +1538,7 @@ test_expect_success 'reverse version sort with prerelease reordering' '
15381538
test_cmp expect actual
15391539
'
15401540

1541-
test_expect_failure 'version sort with prerelease reordering and common leading character' '
1541+
test_expect_success 'version sort with prerelease reordering and common leading character' '
15421542
test_config versionsort.prereleaseSuffix -before &&
15431543
git tag foo1.7-before1 &&
15441544
git tag foo1.7 &&
@@ -1552,7 +1552,7 @@ test_expect_failure 'version sort with prerelease reordering and common leading
15521552
test_cmp expect actual
15531553
'
15541554

1555-
test_expect_failure 'version sort with prerelease reordering, multiple suffixes and common leading character' '
1555+
test_expect_success 'version sort with prerelease reordering, multiple suffixes and common leading character' '
15561556
test_config versionsort.prereleaseSuffix -before &&
15571557
git config --add versionsort.prereleaseSuffix -after &&
15581558
git tag -l --sort=version:refname "foo1.7*" >actual &&
@@ -1564,6 +1564,11 @@ test_expect_failure 'version sort with prerelease reordering, multiple suffixes
15641564
test_cmp expect actual
15651565
'
15661566

1567+
test_expect_success 'version sort with very long prerelease suffix' '
1568+
test_config versionsort.prereleaseSuffix -very-looooooooooooooooooooooooong-prerelease-suffix &&
1569+
git tag -l --sort=version:refname
1570+
'
1571+
15671572
run_with_limited_stack () {
15681573
(ulimit -s 128 && "$@")
15691574
}

versioncmp.c

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@ static int initialized;
2626

2727
/*
2828
* off is the offset of the first different character in the two strings
29-
* s1 and s2. If either s1 or s2 contains a prerelease suffix starting
30-
* at that offset, it will be forced to be on top.
29+
* s1 and s2. If either s1 or s2 contains a prerelease suffix containing
30+
* that offset, then that string will be forced to be on top.
3131
*
32-
* If both s1 and s2 contain a (different) suffix at that position,
32+
* If both s1 and s2 contain a (different) suffix around that position,
3333
* their order is determined by the order of those two suffixes in the
3434
* configuration.
35+
* If any of the strings contains more than one different suffixes around
36+
* that position, then that string is sorted according to the contained
37+
* suffix which comes first in the configuration.
3538
*
3639
* Return non-zero if *diff contains the return value for versioncmp()
3740
*/
@@ -44,10 +47,21 @@ static int swap_prereleases(const char *s1,
4447

4548
for (i = 0; i < prereleases->nr; i++) {
4649
const char *suffix = prereleases->items[i].string;
47-
if (i1 == -1 && starts_with(s1 + off, suffix))
48-
i1 = i;
49-
if (i2 == -1 && starts_with(s2 + off, suffix))
50-
i2 = i;
50+
int j, start, suffix_len = strlen(suffix);
51+
if (suffix_len < off)
52+
start = off - suffix_len + 1;
53+
else
54+
start = 0;
55+
for (j = start; j <= off; j++)
56+
if (i1 == -1 && starts_with(s1 + j, suffix)) {
57+
i1 = i;
58+
break;
59+
}
60+
for (j = start; j <= off; j++)
61+
if (i2 == -1 && starts_with(s2 + j, suffix)) {
62+
i2 = i;
63+
break;
64+
}
5165
}
5266
if (i1 == -1 && i2 == -1)
5367
return 0;

0 commit comments

Comments
 (0)