Skip to content
7 changes: 4 additions & 3 deletions readthedocs/api/v2/views/integrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from rest_framework.status import HTTP_400_BAD_REQUEST
from rest_framework.views import APIView

from readthedocs.builds.constants import LATEST
from readthedocs.builds.constants import BRANCH, LATEST
from readthedocs.core.signals import webhook_bitbucket, webhook_github, webhook_gitlab
from readthedocs.core.views.hooks import (
build_branches,
Expand Down Expand Up @@ -294,7 +294,7 @@ def get_closed_external_version_response(self, project):

def update_default_branch(self, default_branch):
"""
Update the `Version.identifer` for `latest` with the VCS's `default_branch`.
Update the `Version.identifier` for `latest` with the VCS's `default_branch`.

The VCS's `default_branch` is the branch cloned when there is no specific branch specified
(e.g. `git clone <URL>`).
Expand All @@ -316,7 +316,8 @@ def update_default_branch(self, default_branch):
# Always check for the machine attribute, since latest can be user created.
# RTD doesn't manage those.
self.project.versions.filter(slug=LATEST, machine=True).update(
identifier=default_branch
identifier=default_branch,
type=BRANCH,
)


Expand Down
14 changes: 4 additions & 10 deletions readthedocs/builds/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
EXTERNAL_VERSION_STATES,
INTERNAL,
LATEST,
NON_REPOSITORY_VERSIONS,
PREDEFINED_MATCH_ARGS,
PREDEFINED_MATCH_ARGS_VALUES,
STABLE,
Expand Down Expand Up @@ -292,9 +291,9 @@ def ref(self):
def vcs_url(self):
version_name = self.verbose_name
if not self.is_external:
if self.slug == STABLE:
if self.slug == STABLE and self.machine:
version_name = self.ref
elif self.slug == LATEST:
elif self.slug == LATEST and self.machine:
version_name = self.project.get_default_branch()
else:
version_name = self.slug
Expand Down Expand Up @@ -341,10 +340,10 @@ def commit_name(self):
"""
# LATEST is special as it is usually a branch but does not contain the
# name in verbose_name.
if self.slug == LATEST:
if self.slug == LATEST and self.machine:
return self.project.get_default_branch()

if self.slug == STABLE:
if self.slug == STABLE and self.machine:
if self.type == BRANCH:
# Special case, as we do not store the original branch name
# that the stable version works on. We can only interpolate the
Expand All @@ -355,11 +354,6 @@ def commit_name(self):
return self.identifier[len('origin/'):]
return self.identifier

# By now we must have handled all special versions.
if self.slug in NON_REPOSITORY_VERSIONS:
# pylint: disable=broad-exception-raised
raise Exception('All special versions must be handled by now.')

if self.type in (BRANCH, TAG):
# If this version is a branch or a tag, the verbose_name will
# contain the actual name. We cannot use identifier as this might
Expand Down
13 changes: 10 additions & 3 deletions readthedocs/doc_builder/director.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from readthedocs.builds.constants import EXTERNAL
from readthedocs.builds.constants import EXTERNAL, LATEST
from readthedocs.core.utils.filesystem import safe_open
from readthedocs.doc_builder.config import load_yaml_config
from readthedocs.doc_builder.exceptions import BuildUserError
Expand Down Expand Up @@ -226,8 +226,15 @@ def checkout(self):
self.vcs_repository.update()

identifier = self.data.build_commit or self.data.version.identifier
log.info("Checking out.", identifier=identifier)
self.vcs_repository.checkout(identifier)
is_rtd_latest = self.data.version.slug == LATEST and self.data.version.machine
skip_checkout = not identifier or (
is_rtd_latest and not self.data.project.default_branch
)
if skip_checkout:
log.info("Skipping checkout, using default branch.")
else:
log.info("Checking out.", identifier=identifier)
self.vcs_repository.checkout(identifier)

# The director is responsible for understanding which config file to use for a build.
# In order to reproduce a build 1:1, we may use readthedocs_yaml_path defined by the build
Expand Down
22 changes: 10 additions & 12 deletions readthedocs/projects/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,19 +653,10 @@ def save(self, *args, **kwargs):

try:
if not self.versions.filter(slug=LATEST).exists():
self.versions.create_latest()
self.versions.create_latest(identifier=self.get_default_branch())
except Exception:
log.exception("Error creating default branches")

# Update `Version.identifier` for `latest` with the default branch the user has selected,
# even if it's `None` (meaning to match the `default_branch` of the repository)
# NOTE: this code is required to be *after* ``create_latest()``.
# It has to be updated after creating LATEST originally.
log.debug(
"Updating default branch.", slug=LATEST, identifier=self.default_branch
)
self.versions.filter(slug=LATEST).update(identifier=self.default_branch)

def delete(self, *args, **kwargs):
from readthedocs.projects.tasks.utils import clean_project_resources

Expand Down Expand Up @@ -1211,7 +1202,8 @@ def update_latest_version(self):
"""
latest = self.get_latest_version()
if not latest:
latest = self.versions.create_latest()
latest = self.versions.create_latest(identifier=self.get_default_branch())

if not latest.machine:
return

Expand Down Expand Up @@ -1300,7 +1292,13 @@ def get_default_version(self):
return LATEST

def get_default_branch(self):
"""Get the version representing 'latest'."""
"""
Get the branch/tag name of the version representing 'latest'.

If the project has a default branch explicitly set, we use that,
otherwise we try to get it from the remote repository,
or fallback to the default branch of the VCS backend.
"""
if self.default_branch:
return self.default_branch

Expand Down
49 changes: 39 additions & 10 deletions readthedocs/projects/tasks/builds.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from readthedocs.builds.constants import (
ARTIFACT_TYPES,
ARTIFACT_TYPES_WITHOUT_MULTIPLE_FILES_SUPPORT,
BRANCH,
BUILD_FINAL_STATES,
BUILD_STATE_BUILDING,
BUILD_STATE_CLONING,
Expand All @@ -32,6 +33,7 @@
BUILD_STATUS_FAILURE,
BUILD_STATUS_SUCCESS,
EXTERNAL,
LATEST,
UNDELETABLE_ARTIFACT_TYPES,
)
from readthedocs.builds.models import APIVersion, Build
Expand Down Expand Up @@ -119,6 +121,10 @@ class TaskData:
config: BuildConfigV2 = None
project: APIProject = None
version: APIVersion = None
# Default branch for the project.
# This is used to update the latest version in case the project doesn't
# have an explicit default branch set.
default_branch: str = None

# Dictionary returned from the API.
build: dict = field(default_factory=dict)
Expand Down Expand Up @@ -634,17 +640,28 @@ def on_success(self, retval, task_id, args, kwargs):
# TODO: remove this condition and *always* update the DB Version instance
if "html" in valid_artifacts:
try:
self.data.api_client.version(self.data.version.pk).patch(
{
"built": True,
"documentation_type": self.data.version.documentation_type,
"has_pdf": "pdf" in valid_artifacts,
"has_epub": "epub" in valid_artifacts,
"has_htmlzip": "htmlzip" in valid_artifacts,
"build_data": self.data.version.build_data,
"addons": self.data.version.addons,
}
payload = {
"built": True,
"documentation_type": self.data.version.documentation_type,
"has_pdf": "pdf" in valid_artifacts,
"has_epub": "epub" in valid_artifacts,
"has_htmlzip": "htmlzip" in valid_artifacts,
"build_data": self.data.version.build_data,
"addons": self.data.version.addons,
}
# Update the latest version to point to the current default branch
# if the project doesn't have a default branch set.
Comment on lines +652 to +653
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Update the latest version to point to the current default branch
# if the project doesn't have a default branch set.
# Update the latest version to point to the current VCS default branch
# if the project doesn't have an explicit default branch set.

is_rtd_latest = (
self.data.version.slug == LATEST and self.data.version.machine
)
if (
is_rtd_latest
and not self.data.project.default_branch
and self.data.default_branch
):
payload["identifier"] = self.data.default_branch
payload["type"] = BRANCH
self.data.api_client.version(self.data.version.pk).patch(payload)
except HttpClientError:
# NOTE: I think we should fail the build if we cannot update
# the version at this point. Otherwise, we will have inconsistent data
Expand Down Expand Up @@ -777,6 +794,18 @@ def execute(self):
with self.data.build_director.vcs_environment:
self.data.build_director.setup_vcs()

# Get the default branch of the repository if the project doesn't
# have an explicit default branch set and we are building latest.
# The identifier from latest will be updated with this value
# if the build succeeds.
is_rtd_latest = (
self.data.version.slug == LATEST and self.data.version.machine
)
if is_rtd_latest and not self.data.project.default_branch:
self.data.default_branch = (
self.data.build_director.vcs_repository.get_default_branch()
)

# Sync tags/branches from VCS repository into Read the Docs'
# `Version` objects in the database. This method runs commands
# (e.g. "hg tags") inside the VCS environment, so it requires to be
Expand Down
Loading