We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Buildbot schedules build when a GitLab merge request is deleted
My master installation is running 28958cffbdf703e29b76fb24344848bd65e8ea8a.
In the Buildbot configuration a "gitlab" change hook is registered and triggered by a GitLab configured "Merge Requests Event".

Currently builds are triggering when a merge request is created, updated or deleted.
Build should not be scheduled when a merge request is deleted.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `master/buildbot/www/hooks/gitlab.py`
Content:
```
1 # This file is part of Buildbot.  Buildbot is free software: you can
2 # redistribute it and/or modify it under the terms of the GNU General Public
3 # License as published by the Free Software Foundation, version 2.
4 #
5 # This program is distributed in the hope that it will be useful, but WITHOUT
6 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
7 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
8 # details.
9 #
10 # You should have received a copy of the GNU General Public License along with
11 # this program; if not, write to the Free Software Foundation, Inc., 51
12 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
13 #
14 # Copyright Buildbot Team Members
15 
16 from __future__ import absolute_import
17 from __future__ import print_function
18 
19 import json
20 import re
21 
22 from dateutil.parser import parse as dateparse
23 
24 from twisted.python import log
25 
26 from buildbot.util import bytes2unicode
27 from buildbot.www.hooks.base import BaseHookHandler
28 
29 _HEADER_EVENT = b'X-Gitlab-Event'
30 _HEADER_GITLAB_TOKEN = b'X-Gitlab-Token'
31 
32 
33 class GitLabHandler(BaseHookHandler):
34 
35     def _process_change(self, payload, user, repo, repo_url, project, event,
36                         codebase=None):
37         """
38         Consumes the JSON as a python object and actually starts the build.
39 
40         :arguments:
41             payload
42                 Python Object that represents the JSON sent by GitLab Service
43                 Hook.
44         """
45         changes = []
46         refname = payload['ref']
47 
48         # We only care about regular heads or tags
49         match = re.match(r"^refs/(heads|tags)/(.+)$", refname)
50         if not match:
51             log.msg("Ignoring refname `%s': Not a branch" % refname)
52             return changes
53 
54         branch = match.group(2)
55         if payload.get('deleted'):
56             log.msg("Branch `%s' deleted, ignoring" % branch)
57             return changes
58 
59         for commit in payload['commits']:
60             if not commit.get('distinct', True):
61                 log.msg('Commit `%s` is a non-distinct commit, ignoring...' %
62                         (commit['id'],))
63                 continue
64 
65             files = []
66             for kind in ('added', 'modified', 'removed'):
67                 files.extend(commit.get(kind, []))
68 
69             when_timestamp = dateparse(commit['timestamp'])
70 
71             log.msg("New revision: %s" % commit['id'][:8])
72 
73             change = {
74                 'author': '%s <%s>' % (commit['author']['name'],
75                                        commit['author']['email']),
76                 'files': files,
77                 'comments': commit['message'],
78                 'revision': commit['id'],
79                 'when_timestamp': when_timestamp,
80                 'branch': branch,
81                 'revlink': commit['url'],
82                 'repository': repo_url,
83                 'project': project,
84                 'category': event,
85                 'properties': {
86                     'event': event,
87                 },
88             }
89 
90             if codebase is not None:
91                 change['codebase'] = codebase
92 
93             changes.append(change)
94 
95         return changes
96 
97     def _process_merge_request_change(self, payload, project, event, codebase=None):
98         """
99         Consumes the merge_request JSON as a python object and turn it into a buildbot change.
100 
101         :arguments:
102             payload
103                 Python Object that represents the JSON sent by GitLab Service
104                 Hook.
105         """
106         attrs = payload['object_attributes']
107         commit = attrs['last_commit']
108         when_timestamp = dateparse(commit['timestamp'])
109         # @todo provide and document a way to choose between http and ssh url
110         repo_url = attrs['source']['git_http_url']
111         changes = [{
112             'author': '%s <%s>' % (commit['author']['name'],
113                                    commit['author']['email']),
114             'files': [],  # @todo use rest API
115             'comments': "MR#{}: {}\n\n{}".format(attrs['iid'], attrs['title'], attrs['description']),
116             'revision': commit['id'],
117             'when_timestamp': when_timestamp,
118             'branch': attrs['source_branch'],
119             'repository': repo_url,
120             'project': project,
121             'category': event,
122             'revlink': attrs['url'],
123             'properties': {
124                 'target_branch': attrs['target_branch'],
125                 'target_repository': attrs['target']['git_http_url'],
126                 'event': event,
127             },
128         }]
129         if codebase is not None:
130             changes[0]['codebase'] = codebase
131         return changes
132 
133     def getChanges(self, request):
134         """
135         Reponds only to POST events and starts the build process
136 
137         :arguments:
138             request
139                 the http request object
140         """
141         expected_secret = isinstance(self.options, dict) and self.options.get('secret')
142         if expected_secret:
143             received_secret = request.getHeader(_HEADER_GITLAB_TOKEN)
144             received_secret = bytes2unicode(received_secret)
145             if received_secret != expected_secret:
146                 raise ValueError("Invalid secret")
147         try:
148             content = request.content.read()
149             payload = json.loads(bytes2unicode(content))
150         except Exception as e:
151             raise ValueError("Error loading JSON: " + str(e))
152         event_type = request.getHeader(_HEADER_EVENT)
153         event_type = bytes2unicode(event_type)
154         # newer version of gitlab have a object_kind parameter,
155         # which allows not to use the http header
156         event_type = payload.get('object_kind', event_type)
157         project = request.args.get(b'project', [''])[0]
158         project = bytes2unicode(project)
159         codebase = request.args.get(b'codebase', [None])[0]
160         codebase = bytes2unicode(codebase)
161         if event_type in ("push", "tag_push", "Push Hook"):
162             user = payload['user_name']
163             repo = payload['repository']['name']
164             repo_url = payload['repository']['url']
165             changes = self._process_change(
166                 payload, user, repo, repo_url, project, event_type, codebase=codebase)
167         elif event_type == 'merge_request':
168             changes = self._process_merge_request_change(
169                 payload, project, event_type, codebase=codebase)
170         else:
171             changes = []
172         if changes:
173             log.msg("Received {} changes from {} gitlab event".format(
174                 len(changes), event_type))
175         return (changes, 'git')
176 
177 
178 gitlab = GitLabHandler
179
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/master/buildbot/www/hooks/gitlab.py b/master/buildbot/www/hooks/gitlab.py
--- a/master/buildbot/www/hooks/gitlab.py
+++ b/master/buildbot/www/hooks/gitlab.py
@@ -108,6 +108,17 @@
         when_timestamp = dateparse(commit['timestamp'])
         # @todo provide and document a way to choose between http and ssh url
         repo_url = attrs['source']['git_http_url']
+
+        # Filter out uninteresting events
+        state = attrs['state']
+        if re.match('^(closed|merged|approved)$', state):
+            log.msg("GitLab MR#{}: Ignoring because state is {}".format(attrs['iid'], state))
+            return []
+        action = attrs['action']
+        if not re.match('^(open|reopen)$', action) and not (action == "update" and "oldrev" in attrs):
+            log.msg("GitLab MR#{}: Ignoring because action {} was not open or reopen or an update that added code".format(attrs['iid'], action))
+            return []
+
         changes = [{
             'author': '%s <%s>' % (commit['author']['name'],
                                    commit['author']['email']),

{"golden_diff": "diff --git a/master/buildbot/www/hooks/gitlab.py b/master/buildbot/www/hooks/gitlab.py\n--- a/master/buildbot/www/hooks/gitlab.py\n+++ b/master/buildbot/www/hooks/gitlab.py\n@@ -108,6 +108,17 @@\n         when_timestamp = dateparse(commit['timestamp'])\n         # @todo provide and document a way to choose between http and ssh url\n         repo_url = attrs['source']['git_http_url']\n+\n+        # Filter out uninteresting events\n+        state = attrs['state']\n+        if re.match('^(closed|merged|approved)$', state):\n+            log.msg(\"GitLab MR#{}: Ignoring because state is {}\".format(attrs['iid'], state))\n+            return []\n+        action = attrs['action']\n+        if not re.match('^(open|reopen)$', action) and not (action == \"update\" and \"oldrev\" in attrs):\n+            log.msg(\"GitLab MR#{}: Ignoring because action {} was not open or reopen or an update that added code\".format(attrs['iid'], action))\n+            return []\n+\n         changes = [{\n             'author': '%s <%s>' % (commit['author']['name'],\n                                    commit['author']['email']),\n", "issue": "Buildbot schedules build when a GitLab merge request is deleted\nMy master installation is running 28958cffbdf703e29b76fb24344848bd65e8ea8a.\r\nIn the Buildbot configuration a \"gitlab\" change hook is registered and triggered by a GitLab configured \"Merge Requests Event\".\r\n\r\nCurrently builds are triggering when a merge request is created, updated or deleted.\r\nBuild should not be scheduled when a merge request is deleted.\n", "before_files": [{"content": "# This file is part of Buildbot.  Buildbot is free software: you can\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\nfrom __future__ import absolute_import\nfrom __future__ import print_function\n\nimport json\nimport re\n\nfrom dateutil.parser import parse as dateparse\n\nfrom twisted.python import log\n\nfrom buildbot.util import bytes2unicode\nfrom buildbot.www.hooks.base import BaseHookHandler\n\n_HEADER_EVENT = b'X-Gitlab-Event'\n_HEADER_GITLAB_TOKEN = b'X-Gitlab-Token'\n\n\nclass GitLabHandler(BaseHookHandler):\n\n    def _process_change(self, payload, user, repo, repo_url, project, event,\n                        codebase=None):\n        \"\"\"\n        Consumes the JSON as a python object and actually starts the build.\n\n        :arguments:\n            payload\n                Python Object that represents the JSON sent by GitLab Service\n                Hook.\n        \"\"\"\n        changes = []\n        refname = payload['ref']\n\n        # We only care about regular heads or tags\n        match = re.match(r\"^refs/(heads|tags)/(.+)$\", refname)\n        if not match:\n            log.msg(\"Ignoring refname `%s': Not a branch\" % refname)\n            return changes\n\n        branch = match.group(2)\n        if payload.get('deleted'):\n            log.msg(\"Branch `%s' deleted, ignoring\" % branch)\n            return changes\n\n        for commit in payload['commits']:\n            if not commit.get('distinct', True):\n                log.msg('Commit `%s` is a non-distinct commit, ignoring...' %\n                        (commit['id'],))\n                continue\n\n            files = []\n            for kind in ('added', 'modified', 'removed'):\n                files.extend(commit.get(kind, []))\n\n            when_timestamp = dateparse(commit['timestamp'])\n\n            log.msg(\"New revision: %s\" % commit['id'][:8])\n\n            change = {\n                'author': '%s <%s>' % (commit['author']['name'],\n                                       commit['author']['email']),\n                'files': files,\n                'comments': commit['message'],\n                'revision': commit['id'],\n                'when_timestamp': when_timestamp,\n                'branch': branch,\n                'revlink': commit['url'],\n                'repository': repo_url,\n                'project': project,\n                'category': event,\n                'properties': {\n                    'event': event,\n                },\n            }\n\n            if codebase is not None:\n                change['codebase'] = codebase\n\n            changes.append(change)\n\n        return changes\n\n    def _process_merge_request_change(self, payload, project, event, codebase=None):\n        \"\"\"\n        Consumes the merge_request JSON as a python object and turn it into a buildbot change.\n\n        :arguments:\n            payload\n                Python Object that represents the JSON sent by GitLab Service\n                Hook.\n        \"\"\"\n        attrs = payload['object_attributes']\n        commit = attrs['last_commit']\n        when_timestamp = dateparse(commit['timestamp'])\n        # @todo provide and document a way to choose between http and ssh url\n        repo_url = attrs['source']['git_http_url']\n        changes = [{\n            'author': '%s <%s>' % (commit['author']['name'],\n                                   commit['author']['email']),\n            'files': [],  # @todo use rest API\n            'comments': \"MR#{}: {}\\n\\n{}\".format(attrs['iid'], attrs['title'], attrs['description']),\n            'revision': commit['id'],\n            'when_timestamp': when_timestamp,\n            'branch': attrs['source_branch'],\n            'repository': repo_url,\n            'project': project,\n            'category': event,\n            'revlink': attrs['url'],\n            'properties': {\n                'target_branch': attrs['target_branch'],\n                'target_repository': attrs['target']['git_http_url'],\n                'event': event,\n            },\n        }]\n        if codebase is not None:\n            changes[0]['codebase'] = codebase\n        return changes\n\n    def getChanges(self, request):\n        \"\"\"\n        Reponds only to POST events and starts the build process\n\n        :arguments:\n            request\n                the http request object\n        \"\"\"\n        expected_secret = isinstance(self.options, dict) and self.options.get('secret')\n        if expected_secret:\n            received_secret = request.getHeader(_HEADER_GITLAB_TOKEN)\n            received_secret = bytes2unicode(received_secret)\n            if received_secret != expected_secret:\n                raise ValueError(\"Invalid secret\")\n        try:\n            content = request.content.read()\n            payload = json.loads(bytes2unicode(content))\n        except Exception as e:\n            raise ValueError(\"Error loading JSON: \" + str(e))\n        event_type = request.getHeader(_HEADER_EVENT)\n        event_type = bytes2unicode(event_type)\n        # newer version of gitlab have a object_kind parameter,\n        # which allows not to use the http header\n        event_type = payload.get('object_kind', event_type)\n        project = request.args.get(b'project', [''])[0]\n        project = bytes2unicode(project)\n        codebase = request.args.get(b'codebase', [None])[0]\n        codebase = bytes2unicode(codebase)\n        if event_type in (\"push\", \"tag_push\", \"Push Hook\"):\n            user = payload['user_name']\n            repo = payload['repository']['name']\n            repo_url = payload['repository']['url']\n            changes = self._process_change(\n                payload, user, repo, repo_url, project, event_type, codebase=codebase)\n        elif event_type == 'merge_request':\n            changes = self._process_merge_request_change(\n                payload, project, event_type, codebase=codebase)\n        else:\n            changes = []\n        if changes:\n            log.msg(\"Received {} changes from {} gitlab event\".format(\n                len(changes), event_type))\n        return (changes, 'git')\n\n\ngitlab = GitLabHandler\n", "path": "master/buildbot/www/hooks/gitlab.py"}], "after_files": [{"content": "# This file is part of Buildbot.  Buildbot is free software: you can\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\nfrom __future__ import absolute_import\nfrom __future__ import print_function\n\nimport json\nimport re\n\nfrom dateutil.parser import parse as dateparse\n\nfrom twisted.python import log\n\nfrom buildbot.util import bytes2unicode\nfrom buildbot.www.hooks.base import BaseHookHandler\n\n_HEADER_EVENT = b'X-Gitlab-Event'\n_HEADER_GITLAB_TOKEN = b'X-Gitlab-Token'\n\n\nclass GitLabHandler(BaseHookHandler):\n\n    def _process_change(self, payload, user, repo, repo_url, project, event,\n                        codebase=None):\n        \"\"\"\n        Consumes the JSON as a python object and actually starts the build.\n\n        :arguments:\n            payload\n                Python Object that represents the JSON sent by GitLab Service\n                Hook.\n        \"\"\"\n        changes = []\n        refname = payload['ref']\n\n        # We only care about regular heads or tags\n        match = re.match(r\"^refs/(heads|tags)/(.+)$\", refname)\n        if not match:\n            log.msg(\"Ignoring refname `%s': Not a branch\" % refname)\n            return changes\n\n        branch = match.group(2)\n        if payload.get('deleted'):\n            log.msg(\"Branch `%s' deleted, ignoring\" % branch)\n            return changes\n\n        for commit in payload['commits']:\n            if not commit.get('distinct', True):\n                log.msg('Commit `%s` is a non-distinct commit, ignoring...' %\n                        (commit['id'],))\n                continue\n\n            files = []\n            for kind in ('added', 'modified', 'removed'):\n                files.extend(commit.get(kind, []))\n\n            when_timestamp = dateparse(commit['timestamp'])\n\n            log.msg(\"New revision: %s\" % commit['id'][:8])\n\n            change = {\n                'author': '%s <%s>' % (commit['author']['name'],\n                                       commit['author']['email']),\n                'files': files,\n                'comments': commit['message'],\n                'revision': commit['id'],\n                'when_timestamp': when_timestamp,\n                'branch': branch,\n                'revlink': commit['url'],\n                'repository': repo_url,\n                'project': project,\n                'category': event,\n                'properties': {\n                    'event': event,\n                },\n            }\n\n            if codebase is not None:\n                change['codebase'] = codebase\n\n            changes.append(change)\n\n        return changes\n\n    def _process_merge_request_change(self, payload, project, event, codebase=None):\n        \"\"\"\n        Consumes the merge_request JSON as a python object and turn it into a buildbot change.\n\n        :arguments:\n            payload\n                Python Object that represents the JSON sent by GitLab Service\n                Hook.\n        \"\"\"\n        attrs = payload['object_attributes']\n        commit = attrs['last_commit']\n        when_timestamp = dateparse(commit['timestamp'])\n        # @todo provide and document a way to choose between http and ssh url\n        repo_url = attrs['source']['git_http_url']\n\n        # Filter out uninteresting events\n        state = attrs['state']\n        if re.match('^(closed|merged|approved)$', state):\n            log.msg(\"GitLab MR#{}: Ignoring because state is {}\".format(attrs['iid'], state))\n            return []\n        action = attrs['action']\n        if not re.match('^(open|reopen)$', action) and not (action == \"update\" and \"oldrev\" in attrs):\n            log.msg(\"GitLab MR#{}: Ignoring because action {} was not open or reopen or an update that added code\".format(attrs['iid'], action))\n            return []\n\n        changes = [{\n            'author': '%s <%s>' % (commit['author']['name'],\n                                   commit['author']['email']),\n            'files': [],  # @todo use rest API\n            'comments': \"MR#{}: {}\\n\\n{}\".format(attrs['iid'], attrs['title'], attrs['description']),\n            'revision': commit['id'],\n            'when_timestamp': when_timestamp,\n            'branch': attrs['source_branch'],\n            'repository': repo_url,\n            'project': project,\n            'category': event,\n            'revlink': attrs['url'],\n            'properties': {\n                'target_branch': attrs['target_branch'],\n                'target_repository': attrs['target']['git_http_url'],\n                'event': event,\n            },\n        }]\n        if codebase is not None:\n            changes[0]['codebase'] = codebase\n        return changes\n\n    def getChanges(self, request):\n        \"\"\"\n        Reponds only to POST events and starts the build process\n\n        :arguments:\n            request\n                the http request object\n        \"\"\"\n        expected_secret = isinstance(self.options, dict) and self.options.get('secret')\n        if expected_secret:\n            received_secret = request.getHeader(_HEADER_GITLAB_TOKEN)\n            received_secret = bytes2unicode(received_secret)\n            if received_secret != expected_secret:\n                raise ValueError(\"Invalid secret\")\n        try:\n            content = request.content.read()\n            payload = json.loads(bytes2unicode(content))\n        except Exception as e:\n            raise ValueError(\"Error loading JSON: \" + str(e))\n        event_type = request.getHeader(_HEADER_EVENT)\n        event_type = bytes2unicode(event_type)\n        # newer version of gitlab have a object_kind parameter,\n        # which allows not to use the http header\n        event_type = payload.get('object_kind', event_type)\n        project = request.args.get(b'project', [''])[0]\n        project = bytes2unicode(project)\n        codebase = request.args.get(b'codebase', [None])[0]\n        codebase = bytes2unicode(codebase)\n        if event_type in (\"push\", \"tag_push\", \"Push Hook\"):\n            user = payload['user_name']\n            repo = payload['repository']['name']\n            repo_url = payload['repository']['url']\n            changes = self._process_change(\n                payload, user, repo, repo_url, project, event_type, codebase=codebase)\n        elif event_type == 'merge_request':\n            changes = self._process_merge_request_change(\n                payload, project, event_type, codebase=codebase)\n        else:\n            changes = []\n        if changes:\n            log.msg(\"Received {} changes from {} gitlab event\".format(\n                len(changes), event_type))\n        return (changes, 'git')\n\n\ngitlab = GitLabHandler\n", "path": "master/buildbot/www/hooks/gitlab.py"}]}