Merge "Improve error reporting when pr merge fails"

This commit is contained in:
Zuul 2020-03-06 19:12:51 +00:00 committed by Gerrit Code Review
commit 89e42edeb3
5 changed files with 51 additions and 38 deletions

View File

@ -87,7 +87,6 @@ import zuul.nodepool
import zuul.rpcclient
import zuul.zk
import zuul.configloader
from zuul.exceptions import MergeFailure
from zuul.lib.config import get_default
from zuul.lib.logutil import get_annotated_logger
@ -2252,19 +2251,6 @@ class FakeGithubConnection(githubconnection.GithubConnection):
pull_request = self.pull_requests[int(pr_number)]
pull_request.addComment(message)
def mergePull(self, project, pr_number, commit_message='', sha=None,
method='merge', zuul_event_id=None):
# record that this got reported
self.reports.append((project, pr_number, 'merge', method))
pull_request = self.pull_requests[int(pr_number)]
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.setMerged(commit_message)
def setCommitStatus(self, project, sha, state, url='', description='',
context='default', user='zuul', zuul_event_id=None):
# record that this got reported and call original method

View File

@ -28,6 +28,16 @@ from tests.fake_graphql import FakeGithubQuery
FAKE_BASE_URL = 'https://example.com/api/v3/'
class ErrorResponse:
status_code = 0
message = ''
def json(self):
return {
'message': self.message
}
class FakeUser(object):
def __init__(self, login):
self.login = login
@ -276,16 +286,7 @@ class FakeRepository(object):
if self.fail_not_found > 0:
self.fail_not_found -= 1
class Response:
status_code = 0
message = ''
def json(self):
return {
'message': self.message
}
resp = Response()
resp = ErrorResponse()
resp.status_code = 404
resp.message = 'Not Found'
@ -472,6 +473,23 @@ class FakePull(object):
# with the head_sha as the only commit
return [self.head]
def merge(self, commit_message=None, sha=None, merge_method=None):
conn = self._fake_pull_request.github
pr = self._fake_pull_request
# record that this got reported
conn.reports.append((pr.project, pr.number, 'merge', merge_method))
if conn.merge_failure:
raise Exception('Unknown merge failure')
if conn.merge_not_allowed_count > 0:
conn.merge_not_allowed_count -= 1
resp = ErrorResponse()
resp.status_code = 403
resp.message = 'Merge not allowed'
raise github3.exceptions.MethodNotAllowed(resp)
pr.setMerged(commit_message)
return True
def as_dict(self):
pr = self._fake_pull_request
connection = pr.github

View File

@ -620,6 +620,7 @@ class TestGithubDriver(ZuulTestCase):
self.assertTrue(A.is_merged)
self.assertThat(A.merge_message,
MatchesRegex(r'.*PR title.*Reviewed-by.*', re.DOTALL))
self.assertEqual(len(A.comments), 0)
# pipeline merges the pull request on success after failure
self.fake_github.merge_failure = True
@ -627,6 +628,9 @@ class TestGithubDriver(ZuulTestCase):
self.fake_github.emitEvent(B.getCommentAddedEvent('merge me'))
self.waitUntilSettled()
self.assertFalse(B.is_merged)
self.assertEqual(len(B.comments), 1)
self.assertEqual(B.comments[0],
'Pull request merge failed: Unknown merge failure')
self.fake_github.merge_failure = False
# pipeline merges the pull request on second run of merge
@ -645,7 +649,10 @@ class TestGithubDriver(ZuulTestCase):
self.waitUntilSettled()
self.assertFalse(D.is_merged)
self.assertEqual(len(D.comments), 1)
self.assertEqual(D.comments[0], 'Merge failed')
# Validate that the merge failure comment contains the message github
# returned
self.assertEqual(D.comments[0],
'Pull request merge failed: 403 Merge not allowed')
@simple_layout('layouts/dependent-github.yaml', driver='github')
def test_draft_pr(self):
@ -1173,9 +1180,11 @@ class TestGithubDriver(ZuulTestCase):
# the change should have entered the gate
self.assertEqual(2, len(self.history))
# Merge should have failed because cherry-pick is not supported
self.assertEqual(2, len(A.comments))
self.assertFalse(A.is_merged)
self.assertIn('Merge Failed', A.comments[1])
self.assertEquals(A.comments[1],
'Merge mode cherry-pick not supported by Github')
@simple_layout('layouts/gate-github-squash-merge.yaml', driver='github')
def test_merge_method_squash_merge(self):

View File

@ -1736,13 +1736,12 @@ class GithubConnection(BaseConnection):
try:
result = pull_request.merge(commit_message=commit_message, sha=sha,
merge_method=method)
except github3.exceptions.MethodNotAllowed as e:
raise MergeFailure('Merge was not successful due to mergeability'
' conflict, original error is %s' % e)
except Exception as e:
raise MergeFailure('Pull request merge failed: %s' % e)
log.debug("Merged PR %s/%s#%s", owner, proj, pr_number)
if not result:
raise Exception('Pull request was not merged')
raise MergeFailure('Pull request was not merged')
log.debug("Merged PR %s/%s#%s", owner, proj, pr_number)
def _getCommit(self, repository, sha, retries=5):
try:

View File

@ -95,10 +95,10 @@ class GithubReporter(BaseReporter):
if self._create_comment or errors_received:
self.addPullComment(item)
if (self._merge):
self.mergePull(item)
if not item.change.is_merged:
msg = self._formatItemReportMergeFailure(item)
self.addPullComment(item, msg)
try:
self.mergePull(item)
except Exception as e:
self.addPullComment(item, str(e))
def _formatItemReportJobs(self, item):
# Return the list of jobs portion of the report
@ -161,8 +161,7 @@ class GithubReporter(BaseReporter):
if merge_mode not in self.merge_modes:
mode = [x[0] for x in MERGER_MAP.items() if x[1] == merge_mode][0]
self.log.warning('Merge mode %s not supported by Github', mode)
# TODO(tobiash): report error to user
return
raise MergeFailure('Merge mode %s not supported by Github' % mode)
merge_mode = self.merge_modes[merge_mode]
project = item.change.project.name
@ -179,13 +178,15 @@ class GithubReporter(BaseReporter):
zuul_event_id=item.event)
item.change.is_merged = True
return
except MergeFailure:
except MergeFailure as e:
log.exception('Merge attempt of change %s %s/2 failed.',
item.change, i, exc_info=True)
error_message = str(e)
if i == 1:
time.sleep(2)
log.warning('Merge of change %s failed after 2 attempts, giving up',
item.change)
raise MergeFailure(error_message)
def addReview(self, item):
log = get_annotated_logger(self.log, item.event)