From db9e12c61795f77b1fac2de2970bb5c17b5e69c3 Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Thu, 8 Aug 2019 00:56:48 -0500 Subject: [PATCH 1/4] Sort versions taking into consideration the vcs type Ref https://github.com/readthedocs/readthedocs.org/issues/6010#issuecomment-519340989 --- .../projects/templatetags/projects_tags.py | 7 +- readthedocs/projects/version_handling.py | 26 +++-- .../tests/projects/test_version_sorting.py | 100 +++++++++++++++++- readthedocs/vcs_support/backends/__init__.py | 2 - 4 files changed, 122 insertions(+), 13 deletions(-) diff --git a/readthedocs/projects/templatetags/projects_tags.py b/readthedocs/projects/templatetags/projects_tags.py index 6a6d6d16a2e..4fbf769b655 100644 --- a/readthedocs/projects/templatetags/projects_tags.py +++ b/readthedocs/projects/templatetags/projects_tags.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - """Project template tags and filters.""" from django import template @@ -13,9 +11,12 @@ @register.filter def sort_version_aware(versions): """Takes a list of versions objects and sort them using version schemes.""" + repo_type = None + if versions: + repo_type = versions[0].project.repo_type return sorted( versions, - key=lambda version: comparable_version(version.verbose_name), + key=lambda version: comparable_version(version.verbose_name, repo_type=repo_type), reverse=True, ) diff --git a/readthedocs/projects/version_handling.py b/readthedocs/projects/version_handling.py index 15cfe8be91e..84b3890c0d8 100644 --- a/readthedocs/projects/version_handling.py +++ b/readthedocs/projects/version_handling.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - """Project version handling.""" import unicodedata @@ -10,6 +8,7 @@ STABLE_VERBOSE_NAME, TAG, ) +from readthedocs.vcs_support.backends import backend_cls def parse_version_failsafe(version_string): @@ -49,7 +48,7 @@ def parse_version_failsafe(version_string): return None -def comparable_version(version_string): +def comparable_version(version_string, repo_type=None): """ Can be used as ``key`` argument to ``sorted``. @@ -57,19 +56,32 @@ def comparable_version(version_string): ``STABLE`` should be listed second. If we cannot figure out the version number then we sort it to the bottom of the list. + If `repo_type` is given, it adds the default "master" version + from the VCS (master, default, trunk). + This version is highest than LATEST and STABLE. + :param version_string: version as string object (e.g. '3.10.1' or 'latest') :type version_string: str or unicode + :param repo_type: Repository type from which the versions are generated. + :returns: a comparable version object (e.g. 'latest' -> Version('99999.0')) :rtype: packaging.version.Version """ + highest_versions = [] + if repo_type: + backend = backend_cls.get(repo_type) + if backend.fallback_branch: + highest_versions.append(backend.fallback_branch) + highest_versions.extend([LATEST_VERBOSE_NAME, STABLE_VERBOSE_NAME]) + comparable = parse_version_failsafe(version_string) if not comparable: - if version_string == LATEST_VERBOSE_NAME: - comparable = Version('99999.0') - elif version_string == STABLE_VERBOSE_NAME: - comparable = Version('9999.0') + if version_string in highest_versions: + position = highest_versions.index(version_string) + version_number = str(999999 - position) + comparable = Version(version_number) else: comparable = Version('0.01') return comparable diff --git a/readthedocs/rtd_tests/tests/projects/test_version_sorting.py b/readthedocs/rtd_tests/tests/projects/test_version_sorting.py index 2c8607d2e30..5b4ceea574a 100644 --- a/readthedocs/rtd_tests/tests/projects/test_version_sorting.py +++ b/readthedocs/rtd_tests/tests/projects/test_version_sorting.py @@ -1,8 +1,14 @@ from django.test import TestCase from django_dynamic_fixture import get -from readthedocs.builds.constants import BRANCH +from readthedocs.builds.constants import BRANCH, LATEST from readthedocs.builds.models import Version +from readthedocs.projects.constants import ( + REPO_TYPE_BZR, + REPO_TYPE_GIT, + REPO_TYPE_HG, + REPO_TYPE_SVN, +) from readthedocs.projects.models import Project from readthedocs.projects.templatetags.projects_tags import sort_version_aware @@ -65,3 +71,95 @@ def test_sort_alpha(self): ['latest', 'carrot', 'banana', 'apple'], [v.slug for v in sort_version_aware(versions)], ) + + def test_sort_git_master(self): + identifiers = ['master', '1.0', '2.0', '1.1', '1.9', '1.10'] + self.project.repo_type = REPO_TYPE_GIT + self.project.save() + self.project.versions.get(slug=LATEST).delete() + + for identifier in identifiers: + get( + Version, + project=self.project, + type=BRANCH, + identifier=identifier, + verbose_name=identifier, + slug=identifier, + ) + + versions = list(Version.objects.filter(project=self.project)) + self.assertEqual( + ['master', '2.0', '1.10', '1.9', '1.1', '1.0'], + [v.slug for v in sort_version_aware(versions)], + ) + + def test_sort_hg_default(self): + identifiers = ['default', '1.0', '2.0', '1.1', '1.9', '1.10'] + self.project.repo_type = REPO_TYPE_HG + self.project.save() + self.project.versions.get(slug=LATEST).delete() + + for identifier in identifiers: + get( + Version, + project=self.project, + type=BRANCH, + identifier=identifier, + verbose_name=identifier, + slug=identifier, + ) + + versions = list(Version.objects.filter(project=self.project)) + self.assertEqual( + ['default', '2.0', '1.10', '1.9', '1.1', '1.0'], + [v.slug for v in sort_version_aware(versions)], + ) + + def test_sort_bzr_latest(self): + """ + BZR doesn't have a name for "master", + so here master gets sorted by its ascii value. + """ + identifiers = ['master', '1.0', '2.0', '1.1', '1.9', '1.10'] + self.project.repo_type = REPO_TYPE_BZR + self.project.save() + self.project.versions.get(slug=LATEST).delete() + + for identifier in identifiers: + get( + Version, + project=self.project, + type=BRANCH, + identifier=identifier, + verbose_name=identifier, + slug=identifier, + ) + + versions = list(Version.objects.filter(project=self.project)) + self.assertEqual( + ['2.0', '1.10', '1.9', '1.1', '1.0', 'master'], + [v.slug for v in sort_version_aware(versions)], + ) + + def test_sort_svn_trunk(self): + identifiers = ['/trunk/', '1.0', '2.0', '1.1', '1.9', '1.10'] + self.project.repo_type = REPO_TYPE_SVN + self.project.save() + self.project.versions.get(slug=LATEST).delete() + + for identifier in identifiers: + get( + Version, + project=self.project, + type=BRANCH, + identifier=identifier, + verbose_name=identifier, + slug=identifier, + ) + + versions = list(Version.objects.filter(project=self.project)) + self.assertEqual( + ['/trunk/', '2.0', '1.10', '1.9', '1.1', '1.0'], + [v.slug for v in sort_version_aware(versions)], + ) diff --git a/readthedocs/vcs_support/backends/__init__.py b/readthedocs/vcs_support/backends/__init__.py index 0a022a545d1..ff88fa587ec 100644 --- a/readthedocs/vcs_support/backends/__init__.py +++ b/readthedocs/vcs_support/backends/__init__.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - """Listing of all the VCS backends.""" from __future__ import absolute_import from . import bzr, hg, git, svn From f91b03822bc691254d86a7a764d27d979c6e99ae Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Thu, 8 Aug 2019 11:11:46 -0500 Subject: [PATCH 2/4] Add test for master and latest --- .../tests/projects/test_version_sorting.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/readthedocs/rtd_tests/tests/projects/test_version_sorting.py b/readthedocs/rtd_tests/tests/projects/test_version_sorting.py index 5b4ceea574a..4bd29fe743b 100644 --- a/readthedocs/rtd_tests/tests/projects/test_version_sorting.py +++ b/readthedocs/rtd_tests/tests/projects/test_version_sorting.py @@ -94,6 +94,32 @@ def test_sort_git_master(self): [v.slug for v in sort_version_aware(versions)], ) + def test_sort_git_master_and_latest(self): + """ + The branch named master should havea a higher priority + than latest, ideally users should only have one of the two activated. + """ + identifiers = ['latest', 'master', '1.0', '2.0', '1.1', '1.9', '1.10'] + self.project.repo_type = REPO_TYPE_GIT + self.project.save() + self.project.versions.get(slug=LATEST).delete() + + for identifier in identifiers: + get( + Version, + project=self.project, + type=BRANCH, + identifier=identifier, + verbose_name=identifier, + slug=identifier, + ) + + versions = list(Version.objects.filter(project=self.project)) + self.assertEqual( + ['master', 'latest', '2.0', '1.10', '1.9', '1.1', '1.0'], + [v.slug for v in sort_version_aware(versions)], + ) + def test_sort_hg_default(self): identifiers = ['default', '1.0', '2.0', '1.1', '1.9', '1.10'] self.project.repo_type = REPO_TYPE_HG From db048a961ecdd9285132b54ad313ca1a285024a7 Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Thu, 8 Aug 2019 11:15:23 -0500 Subject: [PATCH 3/4] Protection agains null --- readthedocs/projects/version_handling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs/projects/version_handling.py b/readthedocs/projects/version_handling.py index 84b3890c0d8..6604ded4d94 100644 --- a/readthedocs/projects/version_handling.py +++ b/readthedocs/projects/version_handling.py @@ -72,7 +72,7 @@ def comparable_version(version_string, repo_type=None): highest_versions = [] if repo_type: backend = backend_cls.get(repo_type) - if backend.fallback_branch: + if backend and backend.fallback_branch: highest_versions.append(backend.fallback_branch) highest_versions.extend([LATEST_VERBOSE_NAME, STABLE_VERBOSE_NAME]) From 90642fada6d66e6a400b44b9e223f42e842a173a Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Thu, 8 Aug 2019 12:07:30 -0500 Subject: [PATCH 4/4] update copy Co-Authored-By: David Fischer --- readthedocs/projects/version_handling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs/projects/version_handling.py b/readthedocs/projects/version_handling.py index 6604ded4d94..3ca60f13b41 100644 --- a/readthedocs/projects/version_handling.py +++ b/readthedocs/projects/version_handling.py @@ -58,7 +58,7 @@ def comparable_version(version_string, repo_type=None): If `repo_type` is given, it adds the default "master" version from the VCS (master, default, trunk). - This version is highest than LATEST and STABLE. + This version is sorted higher than LATEST and STABLE. :param version_string: version as string object (e.g. '3.10.1' or 'latest') :type version_string: str or unicode