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

Sync versions when creating/deleting versions #4876

Merged
Merged
23 changes: 23 additions & 0 deletions readthedocs/core/views/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,29 @@ def build_branches(project, branch_list):
return (to_build, not_building)


def sync_versions(project):
"""
Sync the versions of a repo using its latest version.

This doesn't register a new build,
but clones the repo and syncs the versions.
Due that `sync_repository_task` is bound to a version,
we always pass the latest version.

:returns: The version that was used to trigger the clone (usually latest).
Copy link
Member

Choose a reason for hiding this comment

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

Returns the version slug.

You can use :rtype: string also here.

"""
try:
version_slug = LATEST
version = project.versions.get(slug=version_slug)
Copy link
Member

Choose a reason for hiding this comment

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

Won't this fail for users that override the latest version?

I don't remember exactly, but I think there was also a way to disable latest, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yup, I was considering using the default branch here instead.

sync_repository_task.delay(version.pk)
return version.slug
except Project.DoesNotExist:
log.info('Unable to sync from %s version', version_slug)
except Exception as e:
log.exception('Unknown sync versions exception')
return None


def get_project_from_url(url):
if not url:
return Project.objects.none()
Expand Down
2 changes: 1 addition & 1 deletion readthedocs/oauth/services/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def get_webhook_data(self, project, integration):
),
'content_type': 'json',
},
'events': ['push', 'pull_request'],
'events': ['push', 'pull_request', 'create', 'delete'],
})

def setup_webhook(self, project):
Expand Down
100 changes: 75 additions & 25 deletions readthedocs/restapi/views/integrations.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,44 @@
"""Endpoints integrating with Github, Bitbucket, and other webhooks."""

from __future__ import absolute_import
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)

import json
import logging
import re

from builtins import object
import six
from django.shortcuts import get_object_or_404
from rest_framework import permissions
from rest_framework.views import APIView
from rest_framework.exceptions import NotFound, ParseError
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from rest_framework.exceptions import ParseError, NotFound

from django.shortcuts import get_object_or_404
from rest_framework.views import APIView

from readthedocs.core.views.hooks import build_branches
from readthedocs.core.signals import (webhook_github, webhook_bitbucket,
webhook_gitlab)
from readthedocs.core.signals import (
webhook_bitbucket,
webhook_github,
webhook_gitlab,
)
from readthedocs.core.views.hooks import build_branches, sync_versions
from readthedocs.integrations.models import HttpExchange, Integration
from readthedocs.integrations.utils import normalize_request_payload
from readthedocs.projects.models import Project
import six


log = logging.getLogger(__name__)

GITHUB_EVENT_HEADER = 'HTTP_X_GITHUB_EVENT'
GITHUB_PUSH = 'push'
GITHUB_CREATE = 'create'
GITHUB_DELETE = 'delete'
GITLAB_PUSH = 'push'
GITLAB_NULL_HASH = '0' * 40
GITLAB_TAG_PUSH = 'tag_push'
BITBUCKET_EVENT_HEADER = 'HTTP_X_EVENT_KEY'
BITBUCKET_PUSH = 'repo:push'


Expand Down Expand Up @@ -124,6 +136,14 @@ def get_response_push(self, project, branches):
'project': project.slug,
'versions': list(to_build)}

def sync_versions(self, project):
version = sync_versions(project)
return {
'build_triggered': False,
'project': project.slug,
'versions': [version],
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Still not sure about what to return, I'm returning the version that was used to do the clone, I guess this isn't so important, I mean, no one uses this, it's just sent to the integration (GitHub)



class GitHubWebhookView(WebhookMixin, APIView):

Expand Down Expand Up @@ -154,7 +174,7 @@ def get_data(self):

def handle_webhook(self):
# Get event and trigger other webhook events
event = self.request.META.get('HTTP_X_GITHUB_EVENT', 'push')
event = self.request.META.get(GITHUB_EVENT_HEADER, GITHUB_PUSH)
webhook_github.send(Project, project=self.project,
data=self.data, event=event)
# Handle push events and trigger builds
Expand All @@ -164,6 +184,9 @@ def handle_webhook(self):
return self.get_response_push(self.project, branches)
except KeyError:
raise ParseError('Parameter "ref" is required')
if event in (GITHUB_CREATE, GITHUB_DELETE):
return self.sync_versions(self.project)
return None

def _normalize_ref(self, ref):
pattern = re.compile(r'^refs/(heads|tags)/')
Expand Down Expand Up @@ -191,15 +214,31 @@ class GitLabWebhookView(WebhookMixin, APIView):
def handle_webhook(self):
# Get event and trigger other webhook events
event = self.request.data.get('object_kind', GITLAB_PUSH)
webhook_gitlab.send(Project, project=self.project,
data=self.request.data, event=event)
webhook_gitlab.send(
Project,
project=self.project,
data=self.request.data,
event=event
)
# Handle push events and trigger builds
if event == GITLAB_PUSH:
if event in (GITLAB_PUSH, GITLAB_TAG_PUSH):
data = self.request.data
before = data['before']
after = data['after']
# Tag/branch created/deleted
if GITLAB_NULL_HASH in (before, after):
stsewd marked this conversation as resolved.
Show resolved Hide resolved
return self.sync_versions(self.project)
# Normal push to master
try:
branches = [self.request.data['ref'].replace('refs/heads/', '')]
branches = [self._normalize_ref(data['ref'])]
return self.get_response_push(self.project, branches)
except KeyError:
raise ParseError('Parameter "ref" is required')
return None

def _normalize_ref(self, ref):
pattern = re.compile(r'^refs/(heads|tags)/')
return pattern.sub('', ref)


class BitbucketWebhookView(WebhookMixin, APIView):
Expand Down Expand Up @@ -230,19 +269,30 @@ class BitbucketWebhookView(WebhookMixin, APIView):

def handle_webhook(self):
# Get event and trigger other webhook events
event = self.request.META.get('HTTP_X_EVENT_KEY', BITBUCKET_PUSH)
webhook_bitbucket.send(Project, project=self.project,
data=self.request.data, event=event)
# Handle push events and trigger builds
event = self.request.META.get(BITBUCKET_EVENT_HEADER, BITBUCKET_PUSH)
webhook_bitbucket.send(
Project,
project=self.project,
data=self.request.data,
event=event
)
if event == BITBUCKET_PUSH:
try:
changes = self.request.data['push']['changes']
branches = [change['new']['name']
for change in changes
if change.get('new')]
return self.get_response_push(self.project, branches)
data = self.request.data
changes = data['push']['changes']
branches = []
for change in changes:
old = change['old']
new = change['new']
# Normal push to master
if old is not None and new is not None:
branches.append(new['name'])
if branches:
return self.get_response_push(self.project, branches)
return self.sync_versions(self.project)
stsewd marked this conversation as resolved.
Show resolved Hide resolved
except KeyError:
raise ParseError('Invalid request')
return None


class IsAuthenticatedOrHasToken(permissions.IsAuthenticated):
Expand Down
Loading