Repository: readthedocs/readthedocs.org

Issue:
Remove deprecated build URLs
Once #4568 gets merged, deployed and after 31st January we can remove all of these URLs and all the code associated to them since they will stop working and won't be used anymore.

Similar PR for corporate site at https://github.com/rtfd/readthedocs-corporate/pull/433


Discussion:
Note: After Jan 31st, we can remove the github endpoint, after Apr 1st, we can remove the rest or at least throw an exception from them.

Resolving patch:
diff --git a/readthedocs/core/urls/__init__.py b/readthedocs/core/urls/__init__.py
--- a/readthedocs/core/urls/__init__.py
+++ b/readthedocs/core/urls/__init__.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
 """URL configuration for core app."""
 
 from __future__ import absolute_import
@@ -7,7 +5,7 @@
 
 from readthedocs.constants import pattern_opts
 from readthedocs.core import views
-from readthedocs.core.views import hooks, serve
+from readthedocs.core.views import serve
 from readthedocs.projects.feeds import LatestProjectsFeed, NewProjectsFeed
 
 docs_urls = [
@@ -43,18 +41,6 @@
 ]
 
 core_urls = [
-    # Hooks
-    url(r'^github', hooks.github_build, name='github_build'),
-    url(r'^gitlab', hooks.gitlab_build, name='gitlab_build'),
-    url(r'^bitbucket', hooks.bitbucket_build, name='bitbucket_build'),
-    url(
-        (
-            r'^build/'
-            r'(?P<project_id_or_slug>{project_slug})'.format(**pattern_opts)
-        ),
-        hooks.generic_build,
-        name='generic_build',
-    ),
     # Random other stuff
     url(
         r'^random/(?P<project_slug>{project_slug})'.format(**pattern_opts),
diff --git a/readthedocs/core/views/__init__.py b/readthedocs/core/views/__init__.py
--- a/readthedocs/core/views/__init__.py
+++ b/readthedocs/core/views/__init__.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
 """
 Core views, including the main homepage,
 
@@ -87,7 +85,7 @@ def wipe_version(request, project_slug, version_slug):
     if request.method == 'POST':
         wipe_version_via_slugs(
             version_slug=version_slug,
-            project_slug=project_slug
+            project_slug=project_slug,
         )
         return redirect('project_version_list', project_slug)
     return render(
diff --git a/readthedocs/core/views/hooks.py b/readthedocs/core/views/hooks.py
--- a/readthedocs/core/views/hooks.py
+++ b/readthedocs/core/views/hooks.py
@@ -1,31 +1,14 @@
 """Views pertaining to builds."""
 
-import json
 import logging
-import re
 
-from django.http import HttpResponse, HttpResponseNotFound
-from django.shortcuts import redirect
-from django.views.decorators.csrf import csrf_exempt
-
-from readthedocs.builds.constants import LATEST
 from readthedocs.core.utils import trigger_build
-from readthedocs.projects import constants
-from readthedocs.projects.models import Feature, Project
 from readthedocs.projects.tasks import sync_repository_task
 
 
 log = logging.getLogger(__name__)
 
 
-class NoProjectException(Exception):
-    pass
-
-
-def _allow_deprecated_webhook(project):
-    return project.has_feature(Feature.ALLOW_DEPRECATED_WEBHOOKS)
-
-
 def _build_version(project, slug, already_built=()):
     """
     Where we actually trigger builds for a project and slug.
@@ -105,284 +88,3 @@ def sync_versions(project):
     except Exception:
         log.exception('Unknown sync versions exception')
     return None
-
-
-def get_project_from_url(url):
-    if not url:
-        return Project.objects.none()
-    projects = (
-        Project.objects.filter(repo__iendswith=url) |
-        Project.objects.filter(repo__iendswith=url + '.git')
-    )
-    return projects
-
-
-def log_info(project, msg):
-    log.info(
-        constants.LOG_TEMPLATE,
-        {
-            'project': project,
-            'version': '',
-            'msg': msg,
-        }
-    )
-
-
-def _build_url(url, projects, branches):
-    """
-    Map a URL onto specific projects to build that are linked to that URL.
-
-    Check each of the ``branches`` to see if they are active and should be
-    built.
-    """
-    ret = ''
-    all_built = {}
-    all_not_building = {}
-
-    # This endpoint doesn't require authorization, we shouldn't allow builds to
-    # be triggered from this any longer. Deprecation plan is to selectively
-    # allow access to this endpoint for now.
-    if not any(_allow_deprecated_webhook(project) for project in projects):
-        return HttpResponse('This API endpoint is deprecated', status=403)
-
-    for project in projects:
-        (built, not_building) = build_branches(project, branches)
-        if not built:
-            # Call sync_repository_task to update tag/branch info
-            version = project.versions.get(slug=LATEST)
-            sync_repository_task.delay(version.pk)
-            msg = '(URL Build) Syncing versions for %s' % project.slug
-            log.info(msg)
-        all_built[project.slug] = built
-        all_not_building[project.slug] = not_building
-
-    for project_slug, built in list(all_built.items()):
-        if built:
-            msg = '(URL Build) Build Started: {} [{}]'.format(
-                url,
-                ' '.join(built),
-            )
-            log_info(project_slug, msg=msg)
-            ret += msg
-
-    for project_slug, not_building in list(all_not_building.items()):
-        if not_building:
-            msg = '(URL Build) Not Building: {} [{}]'.format(
-                url,
-                ' '.join(not_building),
-            )
-            log_info(project_slug, msg=msg)
-            ret += msg
-
-    if not ret:
-        ret = '(URL Build) No known branches were pushed to.'
-
-    return HttpResponse(ret)
-
-
-@csrf_exempt
-def github_build(request):  # noqa: D205
-    """
-    GitHub webhook consumer.
-
-    .. warning:: **DEPRECATED**
-        Use :py:class:`readthedocs.api.v2.views.integrations.GitHubWebhookView`
-        instead of this view function
-
-    This will search for projects matching either a stripped down HTTP or SSH
-    URL. The search is error prone, use the API v2 webhook for new webhooks.
-
-    Old webhooks may not have specified the content type to POST with, and
-    therefore can use ``application/x-www-form-urlencoded`` to pass the JSON
-    payload. More information on the API docs here:
-    https://developer.github.com/webhooks/creating/#content-type
-    """
-    if request.method == 'POST':
-        try:
-            if request.META['CONTENT_TYPE'] == 'application/x-www-form-urlencoded':
-                data = json.loads(request.POST.get('payload'))
-            else:
-                data = json.loads(request.body)
-            http_url = data['repository']['url']
-            http_search_url = http_url.replace('http://', '').replace('https://', '')
-            ssh_url = data['repository']['ssh_url']
-            ssh_search_url = ssh_url.replace('git@', '').replace('.git', '')
-            branches = [data['ref'].replace('refs/heads/', '')]
-        except (ValueError, TypeError, KeyError):
-            log.exception('Invalid GitHub webhook payload')
-            return HttpResponse('Invalid request', status=400)
-        try:
-            repo_projects = get_project_from_url(http_search_url)
-            if repo_projects:
-                log.info(
-                    'GitHub webhook search: url=%s branches=%s',
-                    http_search_url,
-                    branches,
-                )
-            ssh_projects = get_project_from_url(ssh_search_url)
-            if ssh_projects:
-                log.info(
-                    'GitHub webhook search: url=%s branches=%s',
-                    ssh_search_url,
-                    branches,
-                )
-            projects = repo_projects | ssh_projects
-            return _build_url(http_search_url, projects, branches)
-        except NoProjectException:
-            log.exception('Project match not found: url=%s', http_search_url)
-            return HttpResponseNotFound('Project not found')
-    else:
-        return HttpResponse('Method not allowed, POST is required', status=405)
-
-
-@csrf_exempt
-def gitlab_build(request):  # noqa: D205
-    """
-    GitLab webhook consumer.
-
-    .. warning:: **DEPRECATED**
-        Use :py:class:`readthedocs.api.v2.views.integrations.GitLabWebhookView`
-        instead of this view function
-
-    Search project repository URLs using the site URL from GitLab webhook payload.
-    This search is error-prone, use the API v2 webhook view for new webhooks.
-    """
-    if request.method == 'POST':
-        try:
-            data = json.loads(request.body)
-            url = data['project']['http_url']
-            search_url = re.sub(r'^https?://(.*?)(?:\.git|)$', '\\1', url)
-            branches = [data['ref'].replace('refs/heads/', '')]
-        except (ValueError, TypeError, KeyError):
-            log.exception('Invalid GitLab webhook payload')
-            return HttpResponse('Invalid request', status=400)
-        log.info(
-            'GitLab webhook search: url=%s branches=%s',
-            search_url,
-            branches,
-        )
-        projects = get_project_from_url(search_url)
-        if projects:
-            return _build_url(search_url, projects, branches)
-
-        log.info('Project match not found: url=%s', search_url)
-        return HttpResponseNotFound('Project match not found')
-    return HttpResponse('Method not allowed, POST is required', status=405)
-
-
-@csrf_exempt
-def bitbucket_build(request):
-    """
-    Consume webhooks from multiple versions of Bitbucket's API.
-
-    .. warning:: **DEPRECATED**
-        Use :py:class:`readthedocs.api.v2.views.integrations.BitbucketWebhookView`
-        instead of this view function
-
-    New webhooks are set up with v2, but v1 webhooks will still point to this
-    endpoint. There are also "services" that point here and submit
-    ``application/x-www-form-urlencoded`` data.
-
-    API v1
-        https://confluence.atlassian.com/bitbucket/events-resources-296095220.html
-
-    API v2
-        https://confluence.atlassian.com/bitbucket/event-payloads-740262817.html#EventPayloads-Push
-
-    Services
-        https://confluence.atlassian.com/bitbucket/post-service-management-223216518.html
-    """
-    if request.method == 'POST':
-        try:
-            if request.META['CONTENT_TYPE'] == 'application/x-www-form-urlencoded':
-                data = json.loads(request.POST.get('payload'))
-            else:
-                data = json.loads(request.body)
-
-            version = 2 if request.META.get('HTTP_USER_AGENT') == 'Bitbucket-Webhooks/2.0' else 1  # yapf: disabled  # noqa
-            if version == 1:
-                branches = [
-                    commit.get('branch', '') for commit in data['commits']
-                ]
-                repository = data['repository']
-                if not repository['absolute_url']:
-                    return HttpResponse('Invalid request', status=400)
-                search_url = 'bitbucket.org{}'.format(
-                    repository['absolute_url'].rstrip('/'),
-                )
-            elif version == 2:
-                changes = data['push']['changes']
-                branches = [change['new']['name'] for change in changes]
-                if not data['repository']['full_name']:
-                    return HttpResponse('Invalid request', status=400)
-                search_url = 'bitbucket.org/{}'.format(
-                    data['repository']['full_name'],
-                )
-        except (TypeError, ValueError, KeyError):
-            log.exception('Invalid Bitbucket webhook payload')
-            return HttpResponse('Invalid request', status=400)
-
-        log.info(
-            'Bitbucket webhook search: url=%s branches=%s',
-            search_url,
-            branches,
-        )
-        log.debug('Bitbucket webhook payload:\n\n%s\n\n', data)
-
-        projects = get_project_from_url(search_url)
-        if projects and branches:
-            return _build_url(search_url, projects, branches)
-
-        if not branches:
-            log.info(
-                'Commit/branch not found url=%s branches=%s',
-                search_url,
-                branches,
-            )
-            return HttpResponseNotFound('Commit/branch not found')
-
-        log.info('Project match not found: url=%s', search_url)
-        return HttpResponseNotFound('Project match not found')
-    return HttpResponse('Method not allowed, POST is required', status=405)
-
-
-@csrf_exempt
-def generic_build(request, project_id_or_slug=None):
-    """
-    Generic webhook build endpoint.
-
-    .. warning:: **DEPRECATED**
-
-      Use :py:class:`readthedocs.api.v2.views.integrations.GenericWebhookView`
-      instead of this view function
-    """
-    try:
-        project = Project.objects.get(pk=project_id_or_slug)
-    # Allow slugs too
-    except (Project.DoesNotExist, ValueError):
-        try:
-            project = Project.objects.get(slug=project_id_or_slug)
-        except (Project.DoesNotExist, ValueError):
-            log.exception(
-                '(Incoming Generic Build) Repo not found:  %s',
-                project_id_or_slug,
-            )
-            return HttpResponseNotFound(
-                'Repo not found: %s' % project_id_or_slug,
-            )
-    # This endpoint doesn't require authorization, we shouldn't allow builds to
-    # be triggered from this any longer. Deprecation plan is to selectively
-    # allow access to this endpoint for now.
-    if not _allow_deprecated_webhook(project):
-        return HttpResponse('This API endpoint is deprecated', status=403)
-    if request.method == 'POST':
-        slug = request.POST.get('version_slug', project.default_version)
-        log.info(
-            '(Incoming Generic Build) %s [%s]',
-            project.slug,
-            slug,
-        )
-        _build_version(project, slug)
-    else:
-        return HttpResponse('You must POST to this resource.')
-    return redirect('builds_project_list', project.slug)


Test patch:
diff --git a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py
deleted file mode 100644
--- a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py
+++ /dev/null
@@ -1,655 +0,0 @@
-# -*- coding: utf-8 -*-
-import json
-import logging
-from urllib.parse import urlencode
-
-import mock
-from django.test import TestCase
-from django_dynamic_fixture import get
-
-from readthedocs.builds.models import Version
-from readthedocs.projects.models import Feature, Project
-
-
-log = logging.getLogger(__name__)
-
-
-class BasePostCommitTest(TestCase):
-    def _setup(self):
-        self.rtfd = get(
-            Project, repo='https://github.com/rtfd/readthedocs.org', slug='read-the-docs',
-        )
-        self.rtfd_not_ok = get(
-            Version, project=self.rtfd, slug='not_ok', identifier='not_ok', active=False,
-        )
-        self.rtfd_awesome = get(
-            Version, project=self.rtfd, slug='awesome', identifier='awesome', active=True,
-        )
-
-        self.pip = get(Project, repo='https://bitbucket.org/pip/pip', repo_type='hg')
-        self.pip_not_ok = get(
-            Version, project=self.pip, slug='not_ok', identifier='not_ok', active=False,
-        )
-        self.sphinx = get(Project, repo='https://bitbucket.org/sphinx/sphinx', repo_type='git')
-
-        self.mocks = [mock.patch('readthedocs.core.views.hooks.trigger_build')]
-        self.patches = [m.start() for m in self.mocks]
-
-        # Remove all possible Features added by a migration
-        Feature.objects.all().delete()
-
-        self.feature = get(
-            Feature,
-            feature_id=Feature.ALLOW_DEPRECATED_WEBHOOKS,
-            default_true=True,
-        )
-        self.feature.projects.add(self.pip)
-        self.feature.projects.add(self.rtfd)
-        self.feature.projects.add(self.sphinx)
-
-        self.client.login(username='eric', password='test')
-
-
-class GitLabWebHookTest(BasePostCommitTest):
-    fixtures = ['eric']
-
-    def setUp(self):
-        self._setup()
-
-        self.payload = {
-            'object_kind': 'push',
-            'before': '95790bf891e76fee5e1747ab589903a6a1f80f22',
-            'after': 'da1560886d4f094c3e6c9ef40349f7d38b5d27d7',
-            'ref': 'refs/heads/awesome',
-            'checkout_sha': 'da1560886d4f094c3e6c9ef40349f7d38b5d27d7',
-            'user_id': 4,
-            'user_name': 'John Smith',
-            'user_email': 'john@example.com',
-            'project_id': 15,
-            'project':{
-                'name':'readthedocs',
-                'description':'',
-                'web_url':'http://example.com/mike/diaspora',
-                'avatar_url': None,
-                'git_ssh_url':'git@github.com:rtfd/readthedocs.org.git',
-                'git_http_url':'http://github.com/rtfd/readthedocs.org.git',
-                'namespace':'Mike',
-                'visibility_level':0,
-                'path_with_namespace':'mike/diaspora',
-                'default_branch':'master',
-                'homepage':'http://example.com/mike/diaspora',
-                'url':'git@github.com/rtfd/readthedocs.org.git',
-                'ssh_url':'git@github.com/rtfd/readthedocs.org.git',
-                'http_url':'http://github.com/rtfd/readthedocs.org.git',
-            },
-            'repository':{
-                'name': 'Diaspora',
-                'url': 'git@github.com:rtfd/readthedocs.org.git',
-                'description': '',
-                'homepage': 'http://github.com/rtfd/readthedocs.org',
-                'git_http_url': 'http://github.com/rtfd/readthedocs.org.git',
-                'git_ssh_url': 'git@github.com:rtfd/readthedocs.org.git',
-                'visibility_level': 0,
-            },
-            'commits': [
-                {
-                    'id': 'b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327',
-                    'message': 'Update Catalan translation to e38cb41.',
-                    'timestamp': '2011-12-12T14:27:31+02:00',
-                    'url': 'http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327',
-                    'author': {
-                        'name': 'Jordi Mallach',
-                        'email': 'jordi@softcatala.org',
-                    },
-                    'added': ['CHANGELOG'],
-                    'modified': ['app/controller/application.rb'],
-                    'removed': [],
-                },
-                {
-                    'id': 'da1560886d4f094c3e6c9ef40349f7d38b5d27d7',
-                    'message': 'fixed readme',
-                    'timestamp': '2012-01-03T23:36:29+02:00',
-                    'url': 'http://example.com/mike/diaspora/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7',
-                    'author': {
-                        'name': 'GitLab dev user',
-                        'email': 'gitlabdev@dv6700.(none)',
-                    },
-                    'added': ['CHANGELOG'],
-                    'modified': ['app/controller/application.rb'],
-                    'removed': [],
-                },
-            ],
-            'total_commits_count': 4,
-        }
-
-    def test_gitlab_post_commit_hook_builds_branch_docs_if_it_should(self):
-        """GitLab webhook should only build active versions."""
-        r = self.client.post(
-            '/gitlab/', data=json.dumps(self.payload),
-            content_type='application/json',
-        )
-        self.assertContains(r, '(URL Build) Build Started: github.com/rtfd/readthedocs.org [awesome]')
-
-        self.payload['ref'] = 'refs/heads/not_ok'
-        r = self.client.post(
-            '/gitlab/', data=json.dumps(self.payload),
-            content_type='application/json',
-        )
-        self.assertContains(r, '(URL Build) Not Building: github.com/rtfd/readthedocs.org [not_ok]')
-
-        self.payload['ref'] = 'refs/heads/unknown'
-        r = self.client.post(
-            '/gitlab/', data=json.dumps(self.payload),
-            content_type='application/json',
-        )
-        self.assertContains(r, '(URL Build) No known branches were pushed to.')
-
-    def test_gitlab_post_commit_knows_default_branches(self):
-        """
-        Test the gitlab post commit hook so that the default branch
-        will be respected and built as the latest version.
-        """
-        rtd = Project.objects.get(slug='read-the-docs')
-        old_default = rtd.default_branch
-        rtd.default_branch = 'master'
-        rtd.save()
-        self.payload['ref'] = 'refs/heads/master'
-
-        r = self.client.post(
-            '/gitlab/', data=json.dumps(self.payload),
-            content_type='application/json',
-        )
-        self.assertContains(r, '(URL Build) Build Started: github.com/rtfd/readthedocs.org [latest]')
-
-        rtd.default_branch = old_default
-        rtd.save()
-
-    def test_gitlab_request_empty_url(self):
-        """
-        The gitlab hook shouldn't build any project
-        if the url, ssh_url or ref are empty.
-        """
-        self.payload['project']['http_url'] = ''
-        r = self.client.post(
-            '/gitlab/', data=json.dumps(self.payload),
-            content_type='application/json',
-        )
-        self.assertEqual(r.status_code, 404)
-
-    def test_gitlab_webhook_is_deprecated(self):
-        # Project is created after feature, not included in historical allowance
-        url = 'https://github.com/rtfd/readthedocs-build'
-        payload = self.payload.copy()
-        payload['project']['http_url'] = url
-        project = get(
-            Project,
-            main_language_project=None,
-            repo=url,
-        )
-        r = self.client.post(
-            '/gitlab/',
-            data=json.dumps(payload),
-            content_type='application/json',
-        )
-        self.assertEqual(r.status_code, 403)
-
-
-class GitHubWebHookTest(BasePostCommitTest):
-    fixtures = ['eric']
-
-    def setUp(self):
-        self._setup()
-
-        self.payload = {
-            'after': '5ad757394b926e5637ffeafe340f952ef48bd270',
-            'base_ref': 'refs/heads/master',
-            'before': '5b4e453dc913b08642b1d4fb10ed23c9d6e5b129',
-            'commits': [
-                {
-                    'added': [],
-                    'author': {
-                        'email': 'eric@ericholscher.com',
-                        'name': 'Eric Holscher',
-                        'username': 'ericholscher',
-                    },
-                    'distinct': False,
-                    'id': '11f229c6a78f5bc8cb173104a3f7a68cdb7eb15a',
-                    'message': 'Fix it on the front list as well.',
-                    'modified': [
-                        'readthedocs/templates/core/project_list_detailed.html',
-                    ],
-                    'removed': [],
-                    'timestamp': '2011-09-12T19:38:55-07:00',
-                    'url': (
-                        'https://github.com/wraithan/readthedocs.org/'
-                        'commit/11f229c6a78f5bc8cb173104a3f7a68cdb7eb15a'
-                    ),
-                },
-            ],
-            'compare': (
-                'https://github.com/wraithan/readthedocs.org/compare/'
-                '5b4e453...5ad7573'
-            ),
-            'created': False,
-            'deleted': False,
-            'forced': False,
-            'pusher': {
-                'name': 'none',
-            },
-            'ref': 'refs/heads/awesome',
-            'repository': {
-                'created_at': '2011/09/09 14:20:13 -0700',
-                'description': 'source code to readthedocs.org',
-                'fork': True,
-                'forks': 0,
-                'has_downloads': True,
-                'has_issues': False,
-                'has_wiki': True,
-                'homepage': 'http://rtfd.org/',
-                'language': 'Python',
-                'name': 'readthedocs.org',
-                'open_issues': 0,
-                'owner': {
-                    'email': 'XWraithanX@gmail.com',
-                    'name': 'wraithan',
-                },
-                'private': False,
-                'pushed_at': '2011/09/12 22:33:34 -0700',
-                'size': 140,
-                'url': 'https://github.com/rtfd/readthedocs.org',
-                'ssh_url': 'git@github.com:rtfd/readthedocs.org.git',
-                'watchers': 1,
-
-            },
-        }
-
-    def test_post_types(self):
-        """Ensure various POST formats."""
-        r = self.client.post(
-            '/github/',
-            data=json.dumps(self.payload),
-            content_type='application/json',
-        )
-        self.assertEqual(r.status_code, 200)
-        r = self.client.post(
-            '/github/',
-            data=urlencode({'payload': json.dumps(self.payload)}),
-            content_type='application/x-www-form-urlencoded',
-        )
-        self.assertEqual(r.status_code, 200)
-
-    def test_github_upper_case_repo(self):
-        """
-        Test the github post commit hook will build properly with upper case
-        repository.
-
-        This allows for capitalization differences in post-commit hook URL's.
-        """
-        payload = self.payload.copy()
-        payload['repository']['url'] = payload['repository']['url'].upper()
-        r = self.client.post(
-            '/github/', data=json.dumps(payload),
-            content_type='application/json',
-        )
-        self.assertContains(r, '(URL Build) Build Started: HTTPS://GITHUB.COM/RTFD/READTHEDOCS.ORG [awesome]')
-        self.payload['ref'] = 'refs/heads/not_ok'
-
-    def test_400_on_no_ref(self):
-        """
-        GitHub sometimes sends us a post-commit hook without a ref.
-
-        This means we don't know what branch to build, so return a 400.
-        """
-        payload = self.payload.copy()
-        del payload['ref']
-        r = self.client.post(
-            '/github/', data=json.dumps(payload),
-            content_type='application/json',
-        )
-        self.assertEqual(r.status_code, 400)
-
-    def test_github_request_empty_url(self):
-        """
-        The github hook shouldn't build any project
-        if the url, ssh_url or ref are empty.
-        """
-        self.payload['repository']['url'] = ''
-        self.payload['repository']['ssh_url'] = ''
-        r = self.client.post(
-            '/github/', data=json.dumps(self.payload),
-            content_type='application/json',
-        )
-        self.assertEqual(r.status_code, 403)
-
-    def test_private_repo_mapping(self):
-        """
-        Test for private GitHub repo mapping.
-
-        Previously we were missing triggering post-commit hooks because we only
-        compared against the *public* ``github.com/user/repo`` URL. Users can
-        also enter a ``github.com:user/repo`` URL, which we should support.
-        """
-        self.rtfd.repo = 'git@github.com:rtfd/readthedocs.org'
-        self.rtfd.save()
-        payload = self.payload.copy()
-        r = self.client.post(
-            '/github/', data=json.dumps(payload),
-            content_type='application/json',
-        )
-        self.assertContains(r, '(URL Build) Build Started: github.com/rtfd/readthedocs.org [awesome]')
-
-    def test_github_post_commit_hook_builds_branch_docs_if_it_should(self):
-        """
-        Test the github post commit hook to see if it will only build
-        versions that are set to be built if the branch they refer to
-        is updated. Otherwise it is no op.
-        """
-        r = self.client.post(
-            '/github/', data=json.dumps(self.payload),
-            content_type='application/json',
-        )
-        self.assertContains(r, '(URL Build) Build Started: github.com/rtfd/readthedocs.org [awesome]')
-
-        self.payload['ref'] = 'refs/heads/not_ok'
-        r = self.client.post(
-            '/github/', data=json.dumps(self.payload),
-            content_type='application/json',
-        )
-        self.assertContains(r, '(URL Build) Not Building: github.com/rtfd/readthedocs.org [not_ok]')
-
-        self.payload['ref'] = 'refs/heads/unknown'
-        r = self.client.post(
-            '/github/', data=json.dumps(self.payload),
-            content_type='application/json',
-        )
-        self.assertContains(r, '(URL Build) No known branches were pushed to.')
-
-    def test_github_post_commit_knows_default_branches(self):
-        """
-        Test the github post commit hook so that the default branch
-        will be respected and built as the latest version.
-        """
-        rtd = Project.objects.get(slug='read-the-docs')
-        old_default = rtd.default_branch
-        rtd.default_branch = 'master'
-        rtd.save()
-        self.payload['ref'] = 'refs/heads/master'
-
-        r = self.client.post(
-            '/github/', data=json.dumps(self.payload),
-            content_type='application/json',
-        )
-        self.assertContains(r, '(URL Build) Build Started: github.com/rtfd/readthedocs.org [latest]')
-
-        rtd.default_branch = old_default
-        rtd.save()
-
-    def test_github_webhook_is_deprecated(self):
-        # Project is created after feature, not included in historical allowance
-        url = 'https://github.com/rtfd/readthedocs-build'
-        payload = self.payload.copy()
-        payload['repository']['url'] = url
-        project = get(
-            Project,
-            main_language_project=None,
-            repo=url,
-        )
-        r = self.client.post(
-            '/github/',
-            data=json.dumps(payload),
-            content_type='application/json',
-        )
-        self.assertEqual(r.status_code, 403)
-
-
-class CorePostCommitTest(BasePostCommitTest):
-    fixtures = ['eric']
-
-    def setUp(self):
-        self._setup()
-
-    def test_core_commit_hook(self):
-        rtd = Project.objects.get(slug='read-the-docs')
-        rtd.default_branch = 'master'
-        rtd.save()
-        r = self.client.post('/build/%s' % rtd.pk, {'version_slug': 'master'})
-        self.assertEqual(r.status_code, 302)
-        self.assertEqual(
-            r._headers['location'][1],
-            '/projects/read-the-docs/builds/',
-        )
-
-    def test_hook_state_tracking(self):
-        rtd = Project.objects.get(slug='read-the-docs')
-        self.assertEqual(Project.objects.get(slug='read-the-docs').has_valid_webhook, False)
-        self.client.post('/build/%s' % rtd.pk, {'version_slug': 'latest'})
-        # Need to re-query to get updated DB entry
-        self.assertEqual(Project.objects.get(slug='read-the-docs').has_valid_webhook, True)
-
-    def test_generic_webhook_is_deprecated(self):
-        # Project is created after feature, not included in historical allowance
-        project = get(Project, main_language_project=None)
-        r = self.client.post('/build/%s' % project.pk, {'version_slug': 'master'})
-        self.assertEqual(r.status_code, 403)
-
-
-class BitBucketWebHookTest(BasePostCommitTest):
-
-    def setUp(self):
-        self._setup()
-
-        self.hg_payload = {
-            'canon_url': 'https://bitbucket.org',
-            'commits': [
-                {
-                    'author': 'marcus',
-                    'branch': 'default',
-                    'files': [
-                        {
-                            'file': 'somefile.py',
-                            'type': 'modified',
-                        },
-                    ],
-                    'message': 'Added some feature things',
-                    'node': 'd14d26a93fd2',
-                    'parents': [
-                            '1b458191f31a',
-                    ],
-                    'raw_author': 'Marcus Bertrand <marcus@somedomain.com>',
-                    'raw_node': 'd14d26a93fd28d3166fa81c0cd3b6f339bb95bfe',
-                    'revision': 3,
-                    'size': -1,
-                    'timestamp': '2012-05-30 06:07:03',
-                    'utctimestamp': '2012-05-30 04:07:03+00:00',
-                },
-            ],
-            'repository': {
-                'absolute_url': '/pip/pip/',
-                'fork': False,
-                'is_private': True,
-                'name': 'Project X',
-                'owner': 'marcus',
-                'scm': 'hg',
-                'slug': 'project-x',
-                'website': '',
-            },
-            'user': 'marcus',
-        }
-
-        self.git_payload = {
-            'canon_url': 'https://bitbucket.org',
-            'commits': [
-                {
-                    'author': 'marcus',
-                    'branch': 'master',
-                    'files': [
-                        {
-                            'file': 'somefile.py',
-                            'type': 'modified',
-                        },
-                    ],
-                    'message': 'Added some more things to somefile.py\n',
-                    'node': '620ade18607a',
-                    'parents': [
-                            '702c70160afc',
-                    ],
-                    'raw_author': 'Marcus Bertrand <marcus@somedomain.com>',
-                    'raw_node': '620ade18607ac42d872b568bb92acaa9a28620e9',
-                    'revision': None,
-                    'size': -1,
-                    'timestamp': '2012-05-30 05:58:56',
-                    'utctimestamp': '2012-05-30 03:58:56+00:00',
-                },
-            ],
-            'repository': {
-                'absolute_url': '/sphinx/sphinx/',
-                'fork': False,
-                'is_private': True,
-                'name': 'Project X',
-                'owner': 'marcus',
-                'scm': 'git',
-                'slug': 'project-x',
-                'website': 'https://atlassian.com/',
-            },
-            'user': 'marcus',
-        }
-
-    def test_post_types(self):
-        """Ensure various POST formats."""
-        r = self.client.post(
-            '/bitbucket/',
-            data=json.dumps(self.hg_payload),
-            content_type='application/json',
-        )
-        self.assertEqual(r.status_code, 200)
-        r = self.client.post(
-            '/bitbucket/',
-            data=urlencode({'payload': json.dumps(self.hg_payload)}),
-            content_type='application/x-www-form-urlencoded',
-        )
-        self.assertEqual(r.status_code, 200)
-
-    def test_bitbucket_post_commit(self):
-        r = self.client.post(
-            '/bitbucket/', data=json.dumps(self.hg_payload),
-            content_type='application/json',
-        )
-        self.assertContains(r, '(URL Build) Build Started: bitbucket.org/pip/pip [latest]')
-
-        r = self.client.post(
-            '/bitbucket/', data=json.dumps(self.git_payload),
-            content_type='application/json',
-        )
-        self.assertContains(r, '(URL Build) Build Started: bitbucket.org/sphinx/sphinx [latest]')
-
-    def test_bitbucket_post_commit_empty_commit_list(self):
-        self.hg_payload['commits'] = []
-        self.git_payload['commits'] = []
-
-        r = self.client.post(
-            '/bitbucket/', data=json.dumps(self.hg_payload),
-            content_type='application/json',
-        )
-        self.assertContains(r, 'Commit/branch not found', status_code=404)
-
-        r = self.client.post(
-            '/bitbucket/', data=json.dumps(self.git_payload),
-            content_type='application/json',
-        )
-        self.assertContains(r, 'Commit/branch not found', status_code=404)
-
-    def test_bitbucket_post_commit_non_existent_url(self):
-        self.hg_payload['repository']['absolute_url'] = '/invalid/repository'
-        self.git_payload['repository']['absolute_url'] = '/invalid/repository'
-
-        r = self.client.post(
-            '/bitbucket/', data=json.dumps(self.hg_payload),
-            content_type='application/json',
-        )
-        self.assertContains(r, 'Project match not found', status_code=404)
-
-        r = self.client.post(
-            '/bitbucket/', data=json.dumps(self.git_payload),
-            content_type='application/json',
-        )
-        self.assertContains(r, 'Project match not found', status_code=404)
-
-
-    def test_bitbucket_post_commit_hook_builds_branch_docs_if_it_should(self):
-        """
-        Test the bitbucket post commit hook to see if it will only build
-        versions that are set to be built if the branch they refer to
-        is updated. Otherwise it is no op.
-        """
-        r = self.client.post(
-            '/bitbucket/', data=json.dumps(self.hg_payload),
-            content_type='application/json',
-        )
-        self.assertContains(r, '(URL Build) Build Started: bitbucket.org/pip/pip [latest]')
-
-        self.hg_payload['commits'] = [{
-            "branch": "not_ok",
-        }]
-        r = self.client.post(
-            '/bitbucket/', data=json.dumps(self.hg_payload),
-            content_type='application/json',
-        )
-        self.assertContains(r, '(URL Build) Not Building: bitbucket.org/pip/pip [not_ok]')
-
-        self.hg_payload['commits'] = [{
-            "branch": "unknown",
-        }]
-        r = self.client.post(
-            '/bitbucket/', data=json.dumps(self.hg_payload),
-            content_type='application/json',
-        )
-        self.assertContains(r, '(URL Build) No known branches were pushed to.')
-
-    def test_bitbucket_default_branch(self):
-        self.test_project = get(
-            Project, repo='HTTPS://bitbucket.org/test/project', slug='test-project',
-            default_branch='integration', repo_type='git',
-        )
-        self.feature.projects.add(self.test_project)
-
-        self.git_payload['commits'] = [{
-            'branch': 'integration',
-        }]
-        self.git_payload['repository'] = {
-            'absolute_url': '/test/project/',
-        }
-
-        r = self.client.post(
-            '/bitbucket/', data=json.dumps(self.git_payload),
-            content_type='application/json',
-        )
-        self.assertContains(r, '(URL Build) Build Started: bitbucket.org/test/project [latest]')
-
-    def test_bitbucket_request_empty_url(self):
-        """
-        The bitbucket hook shouldn't build any project
-        if the url, ssh_url or ref are empty.
-        """
-        self.git_payload['repository']['absolute_url'] = ''
-        r = self.client.post(
-            '/bitbucket/', data=json.dumps(self.git_payload),
-            content_type='application/json',
-        )
-        self.assertEqual(r.status_code, 400)
-
-    def test_bitbucket_webhook_is_deprecated(self):
-        # Project is created after feature, not included in historical allowance
-        url = 'https://bitbucket.org/rtfd/readthedocs-build'
-        payload = self.git_payload.copy()
-        payload['repository']['absolute_url'] = '/rtfd/readthedocs-build'
-        project = get(
-            Project,
-            main_language_project=None,
-            repo=url,
-        )
-        r = self.client.post(
-            '/bitbucket/',
-            data=json.dumps(payload),
-            content_type='application/json',
-        )
-        self.assertEqual(r.status_code, 403)