Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build: skip checkout step when building latest without an explicit default branch #10927

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
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