Repository: readthedocs/readthedocs.org

Issue:
Add GitLab Web Hook End-Point
Need to add an end-point to Read the Docs to received on-push events from a GitLab installation.

The GitLab end-point would be similar to Read the Docs' existing [GitHub support](https://github.com/rtfd/readthedocs.org/blob/1511716c9c984e7a53521c0fcc91e50f49e1473c/readthedocs/core/views.py#L197-L274) with only minor changes needed to support the slightly different response sent by a GitLab web hook POST.

POST body sent as a result of a push event is documented [here](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/web_hooks/web_hooks.md).



Discussion:

Resolving patch:
diff --git a/readthedocs/core/views.py b/readthedocs/core/views.py
--- a/readthedocs/core/views.py
+++ b/readthedocs/core/views.py
@@ -249,6 +249,31 @@ def github_build(request):
         return HttpResponse("You must POST to this resource.")
 
 
+@csrf_exempt
+def gitlab_build(request):
+    """
+    A post-commit hook for GitLab.
+    """
+    if request.method == 'POST':
+        try:
+            # GitLab RTD integration
+            obj = json.loads(request.POST['payload'])
+        except:
+            # Generic post-commit hook
+            obj = json.loads(request.body)
+        url = obj['repository']['homepage']
+        ghetto_url = url.replace('http://', '').replace('https://', '')
+        branch = obj['ref'].replace('refs/heads/', '')
+        pc_log.info("(Incoming GitLab Build) %s [%s]" % (ghetto_url, branch))
+        try:
+            return _build_url(ghetto_url, [branch])
+        except NoProjectException:
+            pc_log.error(
+                "(Incoming GitLab Build) Repo not found:  %s" % ghetto_url)
+            return HttpResponseNotFound('Repo not found: %s' % ghetto_url)
+    else:
+        return HttpResponse("You must POST to this resource.")
+
 @csrf_exempt
 def bitbucket_build(request):
     if request.method == 'POST':
diff --git a/readthedocs/urls.py b/readthedocs/urls.py
--- a/readthedocs/urls.py
+++ b/readthedocs/urls.py
@@ -101,6 +101,7 @@
     url(r'^admin/', include(admin.site.urls)),
     url(r'^dashboard/', include('projects.urls.private')),
     url(r'^github', 'core.views.github_build', name='github_build'),
+    url(r'^gitlab', 'core.views.gitlab_build', name='gitlab_build'),
     url(r'^bitbucket', 'core.views.bitbucket_build', name='bitbucket_build'),
     url((r'^build/'
          r'(?P<project_id_or_slug>{project_slug})'.format(**pattern_opts)),


Test patch:
diff --git a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py
--- a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py
+++ b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py
@@ -8,6 +8,101 @@
 log = logging.getLogger(__name__)
 
 
+class GitLabWebHookTest(TestCase):
+    fixtures = ["eric", "test_data"]
+
+    def tearDown(self):
+        tasks.update_docs = self.old_bd
+
+    def setUp(self):
+        self.old_bd = tasks.update_docs
+
+        def mock(*args, **kwargs):
+            log.info("Mocking for great profit and speed.")
+        tasks.update_docs = mock
+        tasks.update_docs.delay = mock
+
+        self.client.login(username='eric', password='test')
+        self.payload = {
+            "object_kind": "push",
+            "before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
+            "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+            "ref": "refs/heads/awesome",
+            "user_id": 4,
+            "user_name": "John Smith",
+            "user_email": "john@example.com",
+            "project_id": 15,
+            "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://github.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+                    "author": {
+                        "name": "Jordi Mallach",
+                        "email": "jordi@softcatala.org"
+                    }
+                },
+                {
+                    "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+                    "message": "fixed readme",
+                    "timestamp": "2012-01-03T23:36:29+02:00",
+                    "url": "http://github.com/mike/diaspora/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+                    "author": {
+                        "name": "GitLab dev user",
+                        "email": "gitlabdev@dv6700.(none)"
+                    }
+                }
+            ],
+            "total_commits_count": 4
+        }
+
+    def test_gitlab_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('/gitlab/', {'payload': json.dumps(self.payload)})
+        self.assertEqual(r.status_code, 200)
+        self.assertEqual(r.content, '(URL Build) Build Started: github.com/rtfd/readthedocs.org [awesome]')
+        self.payload['ref'] = 'refs/heads/not_ok'
+        r = self.client.post('/gitlab/', {'payload': json.dumps(self.payload)})
+        self.assertEqual(r.status_code, 200)
+        self.assertEqual(r.content, '(URL Build) Not Building: github.com/rtfd/readthedocs.org [not_ok]')
+        self.payload['ref'] = 'refs/heads/unknown'
+        r = self.client.post('/gitlab/', {'payload': json.dumps(self.payload)})
+        self.assertEqual(r.status_code, 200)
+        self.assertEqual(r.content, '(URL Build) Not Building: github.com/rtfd/readthedocs.org []')
+
+    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/', {'payload': json.dumps(self.payload)})
+        self.assertEqual(r.status_code, 200)
+        self.assertEqual(r.content, '(URL Build) Build Started: github.com/rtfd/readthedocs.org [latest]')
+
+        rtd.default_branch = old_default
+        rtd.save()
+
+
 class PostCommitTest(TestCase):
     fixtures = ["eric", "test_data"]