Skip to content

Commit db9e12c

Browse files
committed
Sort versions taking into consideration the vcs type
Ref readthedocs#6010 (comment)
1 parent e632813 commit db9e12c

File tree

4 files changed

+122
-13
lines changed

4 files changed

+122
-13
lines changed

readthedocs/projects/templatetags/projects_tags.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
# -*- coding: utf-8 -*-
2-
31
"""Project template tags and filters."""
42

53
from django import template
@@ -13,9 +11,12 @@
1311
@register.filter
1412
def sort_version_aware(versions):
1513
"""Takes a list of versions objects and sort them using version schemes."""
14+
repo_type = None
15+
if versions:
16+
repo_type = versions[0].project.repo_type
1617
return sorted(
1718
versions,
18-
key=lambda version: comparable_version(version.verbose_name),
19+
key=lambda version: comparable_version(version.verbose_name, repo_type=repo_type),
1920
reverse=True,
2021
)
2122

readthedocs/projects/version_handling.py

+19-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
# -*- coding: utf-8 -*-
2-
31
"""Project version handling."""
42
import unicodedata
53

@@ -10,6 +8,7 @@
108
STABLE_VERBOSE_NAME,
119
TAG,
1210
)
11+
from readthedocs.vcs_support.backends import backend_cls
1312

1413

1514
def parse_version_failsafe(version_string):
@@ -49,27 +48,40 @@ def parse_version_failsafe(version_string):
4948
return None
5049

5150

52-
def comparable_version(version_string):
51+
def comparable_version(version_string, repo_type=None):
5352
"""
5453
Can be used as ``key`` argument to ``sorted``.
5554
5655
The ``LATEST`` version shall always beat other versions in comparison.
5756
``STABLE`` should be listed second. If we cannot figure out the version
5857
number then we sort it to the bottom of the list.
5958
59+
If `repo_type` is given, it adds the default "master" version
60+
from the VCS (master, default, trunk).
61+
This version is highest than LATEST and STABLE.
62+
6063
:param version_string: version as string object (e.g. '3.10.1' or 'latest')
6164
:type version_string: str or unicode
6265
66+
:param repo_type: Repository type from which the versions are generated.
67+
6368
:returns: a comparable version object (e.g. 'latest' -> Version('99999.0'))
6469
6570
:rtype: packaging.version.Version
6671
"""
72+
highest_versions = []
73+
if repo_type:
74+
backend = backend_cls.get(repo_type)
75+
if backend.fallback_branch:
76+
highest_versions.append(backend.fallback_branch)
77+
highest_versions.extend([LATEST_VERBOSE_NAME, STABLE_VERBOSE_NAME])
78+
6779
comparable = parse_version_failsafe(version_string)
6880
if not comparable:
69-
if version_string == LATEST_VERBOSE_NAME:
70-
comparable = Version('99999.0')
71-
elif version_string == STABLE_VERBOSE_NAME:
72-
comparable = Version('9999.0')
81+
if version_string in highest_versions:
82+
position = highest_versions.index(version_string)
83+
version_number = str(999999 - position)
84+
comparable = Version(version_number)
7385
else:
7486
comparable = Version('0.01')
7587
return comparable

readthedocs/rtd_tests/tests/projects/test_version_sorting.py

+99-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
from django.test import TestCase
22
from django_dynamic_fixture import get
33

4-
from readthedocs.builds.constants import BRANCH
4+
from readthedocs.builds.constants import BRANCH, LATEST
55
from readthedocs.builds.models import Version
6+
from readthedocs.projects.constants import (
7+
REPO_TYPE_BZR,
8+
REPO_TYPE_GIT,
9+
REPO_TYPE_HG,
10+
REPO_TYPE_SVN,
11+
)
612
from readthedocs.projects.models import Project
713
from readthedocs.projects.templatetags.projects_tags import sort_version_aware
814

@@ -65,3 +71,95 @@ def test_sort_alpha(self):
6571
['latest', 'carrot', 'banana', 'apple'],
6672
[v.slug for v in sort_version_aware(versions)],
6773
)
74+
75+
def test_sort_git_master(self):
76+
identifiers = ['master', '1.0', '2.0', '1.1', '1.9', '1.10']
77+
self.project.repo_type = REPO_TYPE_GIT
78+
self.project.save()
79+
self.project.versions.get(slug=LATEST).delete()
80+
81+
for identifier in identifiers:
82+
get(
83+
Version,
84+
project=self.project,
85+
type=BRANCH,
86+
identifier=identifier,
87+
verbose_name=identifier,
88+
slug=identifier,
89+
)
90+
91+
versions = list(Version.objects.filter(project=self.project))
92+
self.assertEqual(
93+
['master', '2.0', '1.10', '1.9', '1.1', '1.0'],
94+
[v.slug for v in sort_version_aware(versions)],
95+
)
96+
97+
def test_sort_hg_default(self):
98+
identifiers = ['default', '1.0', '2.0', '1.1', '1.9', '1.10']
99+
self.project.repo_type = REPO_TYPE_HG
100+
self.project.save()
101+
self.project.versions.get(slug=LATEST).delete()
102+
103+
for identifier in identifiers:
104+
get(
105+
Version,
106+
project=self.project,
107+
type=BRANCH,
108+
identifier=identifier,
109+
verbose_name=identifier,
110+
slug=identifier,
111+
)
112+
113+
versions = list(Version.objects.filter(project=self.project))
114+
self.assertEqual(
115+
['default', '2.0', '1.10', '1.9', '1.1', '1.0'],
116+
[v.slug for v in sort_version_aware(versions)],
117+
)
118+
119+
def test_sort_bzr_latest(self):
120+
"""
121+
BZR doesn't have a name for "master",
122+
so here master gets sorted by its ascii value.
123+
"""
124+
identifiers = ['master', '1.0', '2.0', '1.1', '1.9', '1.10']
125+
self.project.repo_type = REPO_TYPE_BZR
126+
self.project.save()
127+
self.project.versions.get(slug=LATEST).delete()
128+
129+
for identifier in identifiers:
130+
get(
131+
Version,
132+
project=self.project,
133+
type=BRANCH,
134+
identifier=identifier,
135+
verbose_name=identifier,
136+
slug=identifier,
137+
)
138+
139+
versions = list(Version.objects.filter(project=self.project))
140+
self.assertEqual(
141+
['2.0', '1.10', '1.9', '1.1', '1.0', 'master'],
142+
[v.slug for v in sort_version_aware(versions)],
143+
)
144+
145+
def test_sort_svn_trunk(self):
146+
identifiers = ['/trunk/', '1.0', '2.0', '1.1', '1.9', '1.10']
147+
self.project.repo_type = REPO_TYPE_SVN
148+
self.project.save()
149+
self.project.versions.get(slug=LATEST).delete()
150+
151+
for identifier in identifiers:
152+
get(
153+
Version,
154+
project=self.project,
155+
type=BRANCH,
156+
identifier=identifier,
157+
verbose_name=identifier,
158+
slug=identifier,
159+
)
160+
161+
versions = list(Version.objects.filter(project=self.project))
162+
self.assertEqual(
163+
['/trunk/', '2.0', '1.10', '1.9', '1.1', '1.0'],
164+
[v.slug for v in sort_version_aware(versions)],
165+
)

readthedocs/vcs_support/backends/__init__.py

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
# -*- coding: utf-8 -*-
2-
31
"""Listing of all the VCS backends."""
42
from __future__ import absolute_import
53
from . import bzr, hg, git, svn

0 commit comments

Comments
 (0)