Skip to content

Commit

Permalink
Versions: allow latest to be a tag (#10738)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
stsewd authored Sep 19, 2023
1 parent 8566a62 commit 953d27a
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 18 deletions.
4 changes: 0 additions & 4 deletions readthedocs/api/v2/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,6 @@ def sync_versions_to_db(project, versions, type):
if latest_version:
# Put back the RTD's latest version
latest_version.machine = True
latest_version.identifier = project.get_default_branch()
latest_version.verbose_name = LATEST_VERBOSE_NAME
# The machine created latest version always points to a branch.
latest_version.type = BRANCH
latest_version.save()
if added:
log.info(
Expand Down
2 changes: 2 additions & 0 deletions readthedocs/builds/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,8 @@ def sync_versions_task(project_pk, tags_data, branches_data, **kwargs):
versions=added_versions,
)

# Sync latest and stable to match the correct type and identifier.
project.update_latest_version()
# TODO: move this to an automation rule
promoted_version = project.update_stable_version()
new_stable = project.get_stable_version()
Expand Down
59 changes: 54 additions & 5 deletions readthedocs/projects/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@
from django_extensions.db.models import TimeStampedModel
from taggit.managers import TaggableManager

from readthedocs.builds.constants import EXTERNAL, INTERNAL, LATEST, STABLE
from readthedocs.builds.constants import (
BRANCH,
EXTERNAL,
INTERNAL,
LATEST,
LATEST_VERBOSE_NAME,
STABLE,
)
from readthedocs.core.history import ExtraHistoricalRecords
from readthedocs.core.resolver import resolve, resolve_domain
from readthedocs.core.utils import extract_valid_attributes_for_model, slugify
Expand Down Expand Up @@ -52,10 +59,7 @@
from readthedocs.storage import build_media_storage
from readthedocs.vcs_support.backends import backend_cls

from .constants import (
DOWNLOADABLE_MEDIA_TYPES,
MEDIA_TYPES,
)
from .constants import DOWNLOADABLE_MEDIA_TYPES, MEDIA_TYPES

log = structlog.get_logger(__name__)

Expand Down Expand Up @@ -1106,6 +1110,51 @@ def get_original_stable_version(self):
)
return original_stable

def get_latest_version(self):
return self.versions.filter(slug=LATEST).first()

def get_original_latest_version(self):
"""
Get the original version that latest points to.
When latest is machine created, it's basically an alias
for the default branch/tag (like main/master),
Returns None if the current default version doesn't point to a valid version.
"""
default_version_name = self.get_default_branch()
return (
self.versions(manager=INTERNAL)
.exclude(slug=LATEST)
.filter(
verbose_name=default_version_name,
)
.first()
)

def update_latest_version(self):
"""
If the current latest version is machine created, update it.
A machine created LATEST version is an alias for the default branch/tag,
so we need to update it to match the type and identifier of the default branch/tag.
"""
latest = self.get_latest_version()
if not latest:
latest = self.versions.create_latest()
if not latest.machine:
return

# default_branch can be a tag or a branch name!
default_version_name = self.get_default_branch()
original_latest = self.get_original_latest_version()
latest.verbose_name = LATEST_VERBOSE_NAME
latest.type = original_latest.type if original_latest else BRANCH
# For latest, the identifier is the name of the branch/tag.
latest.identifier = default_version_name
latest.save()
return latest

def update_stable_version(self):
"""
Returns the version that was promoted to be the new stable version.
Expand Down
47 changes: 47 additions & 0 deletions readthedocs/rtd_tests/tests/test_sync_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,53 @@ def test_update_latest_version_type(self):
self.assertEqual(latest_version.verbose_name, "latest")
self.assertEqual(latest_version.machine, True)

# Latest points to the default branch/tag.
self.pip.default_branch = "2.6"
self.pip.save()

sync_versions_task(
self.pip.pk,
branches_data=[
{
"identifier": "master",
"verbose_name": "master",
},
{
"identifier": "2.6",
"verbose_name": "2.6",
},
],
tags_data=[],
)

latest_version = self.pip.versions.get(slug=LATEST)
self.assertEqual(latest_version.type, BRANCH)
self.assertEqual(latest_version.identifier, "2.6")
self.assertEqual(latest_version.verbose_name, "latest")
self.assertEqual(latest_version.machine, True)

sync_versions_task(
self.pip.pk,
branches_data=[
{
"identifier": "master",
"verbose_name": "master",
},
],
tags_data=[
{
"identifier": "abc123",
"verbose_name": "2.6",
}
],
)

latest_version = self.pip.versions.get(slug=LATEST)
self.assertEqual(latest_version.type, TAG)
self.assertEqual(latest_version.identifier, "2.6")
self.assertEqual(latest_version.verbose_name, "latest")
self.assertEqual(latest_version.machine, True)

def test_machine_attr_when_user_define_stable_tag_and_delete_it(self):
"""
The user creates a tag named ``stable`` on an existing repo,
Expand Down
45 changes: 36 additions & 9 deletions readthedocs/vcs_support/backends/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@

import structlog

from readthedocs.builds.constants import BRANCH, EXTERNAL, TAG
from readthedocs.builds.constants import (
BRANCH,
EXTERNAL,
LATEST_VERBOSE_NAME,
STABLE_VERBOSE_NAME,
TAG,
)
from readthedocs.config import ALL
from readthedocs.projects.constants import (
GITHUB_BRAND,
Expand Down Expand Up @@ -73,13 +79,27 @@ def get_remote_fetch_refspec(self):
This method sits on top of a lot of legacy design.
It decides how to treat the incoming ``Version.identifier`` from
knowledge of how the caller (the build process) uses build data.
Thi is:
For branches:
- Version.identifier is the branch name.
- Version.verbose_name is also the branch name,
except for latest and stable (machine created),
where this is the alias name.
For tags:
Version.identifier = a branch name (branches)
Version.identifier = commit (tags)
Version.identifier = commit (external versions)
Version.verbose_name = branch alias, e.g. latest (branches)
Version.verbose_name = tag name (tags)
Version.verbose_name = PR number (external versions)
- Version.identifier is the commit hash,
except for latest, where this is the tag name.
- Version.verbose_name is the tag name,
except for latest and stable (machine created),
where this is the alias name.
For external versions:
- Version.identifier is the commit hash.
- Version.verbose_name is the PR number.
:return: A refspec valid for fetch operation
"""
Expand Down Expand Up @@ -115,12 +135,19 @@ def get_remote_fetch_refspec(self):
# denoting that it's not a branch/tag that really exists.
# Because we don't know if it originates from the default branch or some
# other tagged release, we will fetch the exact commit it points to.
if self.version_machine and self.verbose_name == "stable":
if self.version_machine and self.verbose_name == STABLE_VERBOSE_NAME:
if self.version_identifier:
return f"{self.version_identifier}"
log.error("'stable' version without a commit hash.")
return None
return f"refs/tags/{self.verbose_name}:refs/tags/{self.verbose_name}"

tag_name = self.verbose_name
# For a machine created "latest" tag, the name of the tag is set
# in the `Version.identifier` field, note that it isn't a commit
# hash, but the name of the tag.
if self.version_machine and self.verbose_name == LATEST_VERBOSE_NAME:
tag_name = self.version_identifier
return f"refs/tags/{tag_name}:refs/tags/{tag_name}"

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

0 comments on commit 953d27a

Please sign in to comment.