Merge pull requests from github reporter
Github reporter can be configured to merge pull reqeusts. When there are multiple merges called at the same time, it leads to a situation when github returns 405 MethodNotAllowed error becuase github is checking the branch mergeability. When we encounter this situation, we try to wait a bit (2 seconds for now) and try to merge again. Pre-release version of Github3.py has to be used, because the latest released version 9.4 has a bug in merge method. Furthermore the newest merge method supports specifying exact sha to be merged, which is desirable to ensure that the exact commit that went through the pipeline gets merged. Both are already fixed in the stable branch, but not yet released on PyPi. See:90c6b7c265
6ef02cb33f
Change-Id: I0c3abbcce476774a5ba8981c171382eaa4fe0abf
This commit is contained in:
parent
e252a73a46
commit
49bff07ec8
|
@ -50,6 +50,11 @@ reporter. It has the following options:
|
|||
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
|
||||
----
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
pbr>=1.1.0
|
||||
|
||||
Github3.py
|
||||
Github3.py==1.0.0a2
|
||||
PyYAML>=3.1.0
|
||||
Paste
|
||||
WebOb>=1.2.3
|
||||
|
|
|
@ -70,6 +70,7 @@ import zuul.merger.merger
|
|||
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 @@ class FakeGithubPullRequest(object):
|
|||
self.statuses = {}
|
||||
self.updated_at = None
|
||||
self.head_sha = None
|
||||
self.is_merged = False
|
||||
self._createPRRef()
|
||||
self._addCommitToRepo()
|
||||
self._updateTimeStamp()
|
||||
|
@ -693,6 +695,8 @@ class FakeGithubConnection(githubconnection.GithubConnection):
|
|||
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 @@ class FakeGithubConnection(githubconnection.GithubConnection):
|
|||
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('/')
|
||||
|
|
|
@ -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
|
|
@ -165,3 +165,35 @@ class TestGithubDriver(ZuulTestCase):
|
|||
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)
|
||||
|
|
|
@ -21,9 +21,11 @@ import webob
|
|||
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 @@ class GithubConnection(BaseConnection):
|
|||
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('/')
|
||||
|
|
|
@ -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 @@ class GithubReporter(BaseReporter):
|
|||
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 @@ class GithubReporter(BaseReporter):
|
|||
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 @@ class GithubReporter(BaseReporter):
|
|||
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
|
||||
|
|
Loading…
Reference in New Issue