Merge "Merge pull requests from github reporter" into feature/zuulv3
diff --git a/doc/source/reporters.rst b/doc/source/reporters.rst
index cf4f6e4..ced3b78 100644
--- a/doc/source/reporters.rst
+++ b/doc/source/reporters.rst
@@ -50,6 +50,11 @@
to ``true``.
``comment: false``
+ **merge**
+ Boolean value (``true`` or ``false``) that determines if the reporter should
+ merge the pull reqeust. Defaults to ``false``.
+ ``merge=true``
+
SMTP
----
diff --git a/requirements.txt b/requirements.txt
index 44cef95..2fe6963 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,6 @@
pbr>=1.1.0
-Github3.py
+Github3.py==1.0.0a2
PyYAML>=3.1.0
Paste
WebOb>=1.2.3
diff --git a/tests/base.py b/tests/base.py
index 0ad1ec1..d7bf467 100755
--- a/tests/base.py
+++ b/tests/base.py
@@ -70,6 +70,7 @@
import zuul.merger.server
import zuul.nodepool
import zuul.zk
+from zuul.exceptions import MergeFailure
FIXTURE_DIR = os.path.join(os.path.dirname(__file__),
'fixtures')
@@ -559,6 +560,7 @@
self.statuses = {}
self.updated_at = None
self.head_sha = None
+ self.is_merged = False
self._createPRRef()
self._addCommitToRepo()
self._updateTimeStamp()
@@ -693,6 +695,8 @@
self.pr_number = 0
self.pull_requests = []
self.upstream_root = upstream_root
+ self.merge_failure = False
+ self.merge_not_allowed_count = 0
def openFakePullRequest(self, project, branch):
self.pr_number += 1
@@ -762,6 +766,16 @@
pull_request = self.pull_requests[pr_number - 1]
pull_request.addComment(message)
+ def mergePull(self, project, pr_number, sha=None):
+ pull_request = self.pull_requests[pr_number - 1]
+ if self.merge_failure:
+ raise Exception('Pull request was not merged')
+ if self.merge_not_allowed_count > 0:
+ self.merge_not_allowed_count -= 1
+ raise MergeFailure('Merge was not successful due to mergeability'
+ ' conflict')
+ pull_request.is_merged = True
+
def setCommitStatus(self, project, sha, state,
url='', description='', context=''):
owner, proj = project.split('/')
diff --git a/tests/fixtures/layouts/merging-github.yaml b/tests/fixtures/layouts/merging-github.yaml
new file mode 100644
index 0000000..4e13063
--- /dev/null
+++ b/tests/fixtures/layouts/merging-github.yaml
@@ -0,0 +1,19 @@
+- pipeline:
+ name: merge
+ description: Pipeline for merging the pull request
+ manager: independent
+ trigger:
+ github:
+ - event: pull_request
+ action: comment
+ comment: 'merge me'
+ success:
+ github:
+ merge: true
+ comment: false
+
+- project:
+ name: org/project
+ merge:
+ jobs:
+ - noop
diff --git a/tests/unit/test_github_driver.py b/tests/unit/test_github_driver.py
index 9017ce9..409d966 100644
--- a/tests/unit/test_github_driver.py
+++ b/tests/unit/test_github_driver.py
@@ -165,3 +165,35 @@
self.waitUntilSettled()
self.assertNotIn('reporting', pr.statuses)
self.assertEqual(2, len(pr.comments))
+
+ @simple_layout('layouts/merging-github.yaml', driver='github')
+ def test_report_pull_merge(self):
+ # pipeline merges the pull request on success
+ A = self.fake_github.openFakePullRequest('org/project', 'master')
+ self.fake_github.emitEvent(A.getCommentAddedEvent('merge me'))
+ self.waitUntilSettled()
+ self.assertTrue(A.is_merged)
+
+ # pipeline merges the pull request on success after failure
+ self.fake_github.merge_failure = True
+ B = self.fake_github.openFakePullRequest('org/project', 'master')
+ self.fake_github.emitEvent(B.getCommentAddedEvent('merge me'))
+ self.waitUntilSettled()
+ self.assertFalse(B.is_merged)
+ self.fake_github.merge_failure = False
+
+ # pipeline merges the pull request on second run of merge
+ # first merge failed on 405 Method Not Allowed error
+ self.fake_github.merge_not_allowed_count = 1
+ C = self.fake_github.openFakePullRequest('org/project', 'master')
+ self.fake_github.emitEvent(C.getCommentAddedEvent('merge me'))
+ self.waitUntilSettled()
+ self.assertTrue(C.is_merged)
+
+ # pipeline does not merge the pull request
+ # merge failed on 405 Method Not Allowed error - twice
+ self.fake_github.merge_not_allowed_count = 2
+ D = self.fake_github.openFakePullRequest('org/project', 'master')
+ self.fake_github.emitEvent(D.getCommentAddedEvent('merge me'))
+ self.waitUntilSettled()
+ self.assertFalse(D.is_merged)
diff --git a/zuul/driver/github/githubconnection.py b/zuul/driver/github/githubconnection.py
index 6604d81..3c1faff 100644
--- a/zuul/driver/github/githubconnection.py
+++ b/zuul/driver/github/githubconnection.py
@@ -21,9 +21,11 @@
import webob.dec
import voluptuous as v
import github3
+from github3.exceptions import MethodNotAllowed
from zuul.connection import BaseConnection
from zuul.model import PullRequest, Ref, TriggerEvent
+from zuul.exceptions import MergeFailure
class GithubWebhookListener():
@@ -283,6 +285,17 @@
pull_request = repository.issue(pr_number)
pull_request.create_comment(message)
+ def mergePull(self, project, pr_number, sha=None):
+ owner, proj = project.split('/')
+ pull_request = self.github.pull_request(owner, proj, pr_number)
+ try:
+ result = pull_request.merge(sha=sha)
+ except MethodNotAllowed as e:
+ raise MergeFailure('Merge was not successful due to mergeability'
+ ' conflict, original error is %s' % e)
+ if not result:
+ raise Exception('Pull request was not merged')
+
def setCommitStatus(self, project, sha, state, url='', description='',
context=''):
owner, proj = project.split('/')
diff --git a/zuul/driver/github/githubreporter.py b/zuul/driver/github/githubreporter.py
index ecbb486..80ab3c7 100644
--- a/zuul/driver/github/githubreporter.py
+++ b/zuul/driver/github/githubreporter.py
@@ -14,8 +14,10 @@
import logging
import voluptuous as v
+import time
from zuul.reporter import BaseReporter
+from zuul.exceptions import MergeFailure
class GithubReporter(BaseReporter):
@@ -28,6 +30,7 @@
super(GithubReporter, self).__init__(driver, connection, config)
self._commit_status = self.config.get('status', None)
self._create_comment = self.config.get('comment', True)
+ self._merge = self.config.get('merge', False)
def report(self, source, pipeline, item):
"""Comment on PR and set commit status."""
@@ -37,6 +40,9 @@
hasattr(item.change, 'patchset') and
item.change.patchset is not None):
self.setPullStatus(pipeline, item)
+ if (self._merge and
+ hasattr(item.change, 'number')):
+ self.mergePull(item)
def addPullComment(self, pipeline, item):
message = self._formatItemReport(pipeline, item)
@@ -68,10 +74,25 @@
self.connection.setCommitStatus(
project, sha, state, url, description, context)
+ def mergePull(self, item):
+ project = item.change.project.name
+ pr_number = item.change.number
+ sha = item.change.patchset
+ self.log.debug('Reporting change %s, params %s, merging via API' %
+ (item.change, self.config))
+ try:
+ self.connection.mergePull(project, pr_number, sha)
+ except MergeFailure:
+ time.sleep(2)
+ self.log.debug('Trying to merge change %s again...' % item.change)
+ self.connection.mergePull(project, pr_number, sha)
+ item.change.is_merged = True
+
def getSchema():
github_reporter = v.Schema({
'status': v.Any('pending', 'success', 'failure'),
- 'comment': bool
+ 'comment': bool,
+ 'merge': bool
})
return github_reporter