Skip to content

Commit 953d27a

Browse files
authored
Versions: allow latest to be a tag (#10738)
* Versions: allow latest to be a tag We were kind of always expecting latest (machine created) to be a branch (the field is even named `default_branch`!). But we were allowing both branches and tags to be the default version (latest). https://github.com/readthedocs/readthedocs.org/blob/53e21bb3ee8a5fce0194eb5481fd81ac87d3d1fa/readthedocs/projects/forms.py#L272-L279 Closes #10735 * Update docstring * Create methods * Fix docstring
1 parent 8566a62 commit 953d27a

File tree

5 files changed

+139
-18
lines changed

5 files changed

+139
-18
lines changed

readthedocs/api/v2/utils.py

-4
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,6 @@ def sync_versions_to_db(project, versions, type):
115115
if latest_version:
116116
# Put back the RTD's latest version
117117
latest_version.machine = True
118-
latest_version.identifier = project.get_default_branch()
119-
latest_version.verbose_name = LATEST_VERBOSE_NAME
120-
# The machine created latest version always points to a branch.
121-
latest_version.type = BRANCH
122118
latest_version.save()
123119
if added:
124120
log.info(

readthedocs/builds/tasks.py

+2
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,8 @@ def sync_versions_task(project_pk, tags_data, branches_data, **kwargs):
359359
versions=added_versions,
360360
)
361361

362+
# Sync latest and stable to match the correct type and identifier.
363+
project.update_latest_version()
362364
# TODO: move this to an automation rule
363365
promoted_version = project.update_stable_version()
364366
new_stable = project.get_stable_version()

readthedocs/projects/models.py

+54-5
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,14 @@
2323
from django_extensions.db.models import TimeStampedModel
2424
from taggit.managers import TaggableManager
2525

26-
from readthedocs.builds.constants import EXTERNAL, INTERNAL, LATEST, STABLE
26+
from readthedocs.builds.constants import (
27+
BRANCH,
28+
EXTERNAL,
29+
INTERNAL,
30+
LATEST,
31+
LATEST_VERBOSE_NAME,
32+
STABLE,
33+
)
2734
from readthedocs.core.history import ExtraHistoricalRecords
2835
from readthedocs.core.resolver import resolve, resolve_domain
2936
from readthedocs.core.utils import extract_valid_attributes_for_model, slugify
@@ -52,10 +59,7 @@
5259
from readthedocs.storage import build_media_storage
5360
from readthedocs.vcs_support.backends import backend_cls
5461

55-
from .constants import (
56-
DOWNLOADABLE_MEDIA_TYPES,
57-
MEDIA_TYPES,
58-
)
62+
from .constants import DOWNLOADABLE_MEDIA_TYPES, MEDIA_TYPES
5963

6064
log = structlog.get_logger(__name__)
6165

@@ -1106,6 +1110,51 @@ def get_original_stable_version(self):
11061110
)
11071111
return original_stable
11081112

1113+
def get_latest_version(self):
1114+
return self.versions.filter(slug=LATEST).first()
1115+
1116+
def get_original_latest_version(self):
1117+
"""
1118+
Get the original version that latest points to.
1119+
1120+
When latest is machine created, it's basically an alias
1121+
for the default branch/tag (like main/master),
1122+
1123+
Returns None if the current default version doesn't point to a valid version.
1124+
"""
1125+
default_version_name = self.get_default_branch()
1126+
return (
1127+
self.versions(manager=INTERNAL)
1128+
.exclude(slug=LATEST)
1129+
.filter(
1130+
verbose_name=default_version_name,
1131+
)
1132+
.first()
1133+
)
1134+
1135+
def update_latest_version(self):
1136+
"""
1137+
If the current latest version is machine created, update it.
1138+
1139+
A machine created LATEST version is an alias for the default branch/tag,
1140+
so we need to update it to match the type and identifier of the default branch/tag.
1141+
"""
1142+
latest = self.get_latest_version()
1143+
if not latest:
1144+
latest = self.versions.create_latest()
1145+
if not latest.machine:
1146+
return
1147+
1148+
# default_branch can be a tag or a branch name!
1149+
default_version_name = self.get_default_branch()
1150+
original_latest = self.get_original_latest_version()
1151+
latest.verbose_name = LATEST_VERBOSE_NAME
1152+
latest.type = original_latest.type if original_latest else BRANCH
1153+
# For latest, the identifier is the name of the branch/tag.
1154+
latest.identifier = default_version_name
1155+
latest.save()
1156+
return latest
1157+
11091158
def update_stable_version(self):
11101159
"""
11111160
Returns the version that was promoted to be the new stable version.

readthedocs/rtd_tests/tests/test_sync_versions.py

+47
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,53 @@ def test_update_latest_version_type(self):
307307
self.assertEqual(latest_version.verbose_name, "latest")
308308
self.assertEqual(latest_version.machine, True)
309309

310+
# Latest points to the default branch/tag.
311+
self.pip.default_branch = "2.6"
312+
self.pip.save()
313+
314+
sync_versions_task(
315+
self.pip.pk,
316+
branches_data=[
317+
{
318+
"identifier": "master",
319+
"verbose_name": "master",
320+
},
321+
{
322+
"identifier": "2.6",
323+
"verbose_name": "2.6",
324+
},
325+
],
326+
tags_data=[],
327+
)
328+
329+
latest_version = self.pip.versions.get(slug=LATEST)
330+
self.assertEqual(latest_version.type, BRANCH)
331+
self.assertEqual(latest_version.identifier, "2.6")
332+
self.assertEqual(latest_version.verbose_name, "latest")
333+
self.assertEqual(latest_version.machine, True)
334+
335+
sync_versions_task(
336+
self.pip.pk,
337+
branches_data=[
338+
{
339+
"identifier": "master",
340+
"verbose_name": "master",
341+
},
342+
],
343+
tags_data=[
344+
{
345+
"identifier": "abc123",
346+
"verbose_name": "2.6",
347+
}
348+
],
349+
)
350+
351+
latest_version = self.pip.versions.get(slug=LATEST)
352+
self.assertEqual(latest_version.type, TAG)
353+
self.assertEqual(latest_version.identifier, "2.6")
354+
self.assertEqual(latest_version.verbose_name, "latest")
355+
self.assertEqual(latest_version.machine, True)
356+
310357
def test_machine_attr_when_user_define_stable_tag_and_delete_it(self):
311358
"""
312359
The user creates a tag named ``stable`` on an existing repo,

readthedocs/vcs_support/backends/git.py

+36-9
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@
55

66
import structlog
77

8-
from readthedocs.builds.constants import BRANCH, EXTERNAL, TAG
8+
from readthedocs.builds.constants import (
9+
BRANCH,
10+
EXTERNAL,
11+
LATEST_VERBOSE_NAME,
12+
STABLE_VERBOSE_NAME,
13+
TAG,
14+
)
915
from readthedocs.config import ALL
1016
from readthedocs.projects.constants import (
1117
GITHUB_BRAND,
@@ -73,13 +79,27 @@ def get_remote_fetch_refspec(self):
7379
This method sits on top of a lot of legacy design.
7480
It decides how to treat the incoming ``Version.identifier`` from
7581
knowledge of how the caller (the build process) uses build data.
82+
Thi is:
83+
84+
For branches:
85+
86+
- Version.identifier is the branch name.
87+
- Version.verbose_name is also the branch name,
88+
except for latest and stable (machine created),
89+
where this is the alias name.
90+
91+
For tags:
7692
77-
Version.identifier = a branch name (branches)
78-
Version.identifier = commit (tags)
79-
Version.identifier = commit (external versions)
80-
Version.verbose_name = branch alias, e.g. latest (branches)
81-
Version.verbose_name = tag name (tags)
82-
Version.verbose_name = PR number (external versions)
93+
- Version.identifier is the commit hash,
94+
except for latest, where this is the tag name.
95+
- Version.verbose_name is the tag name,
96+
except for latest and stable (machine created),
97+
where this is the alias name.
98+
99+
For external versions:
100+
101+
- Version.identifier is the commit hash.
102+
- Version.verbose_name is the PR number.
83103
84104
:return: A refspec valid for fetch operation
85105
"""
@@ -115,12 +135,19 @@ def get_remote_fetch_refspec(self):
115135
# denoting that it's not a branch/tag that really exists.
116136
# Because we don't know if it originates from the default branch or some
117137
# other tagged release, we will fetch the exact commit it points to.
118-
if self.version_machine and self.verbose_name == "stable":
138+
if self.version_machine and self.verbose_name == STABLE_VERBOSE_NAME:
119139
if self.version_identifier:
120140
return f"{self.version_identifier}"
121141
log.error("'stable' version without a commit hash.")
122142
return None
123-
return f"refs/tags/{self.verbose_name}:refs/tags/{self.verbose_name}"
143+
144+
tag_name = self.verbose_name
145+
# For a machine created "latest" tag, the name of the tag is set
146+
# in the `Version.identifier` field, note that it isn't a commit
147+
# hash, but the name of the tag.
148+
if self.version_machine and self.verbose_name == LATEST_VERBOSE_NAME:
149+
tag_name = self.version_identifier
150+
return f"refs/tags/{tag_name}:refs/tags/{tag_name}"
124151

125152
if self.version_type == EXTERNAL:
126153
# TODO: We should be able to resolve this without looking up in oauth registry

0 commit comments

Comments
 (0)