Handle draft pull requests in canMerge

Github now supports draft pull requests which are blocked from merging
by github. Currently those can end up in a gate loop if all
requirements for the gate pipeline are fulfilled except for
un-drafting it.  Zuul needs to handle those in the canMerge check in
order to handle them correctly.

Change-Id: Ie9164ad5127d19ca3d75660b9718979da7cc344c
This commit is contained in:
Tobias Henkel 2020-01-24 13:52:51 +01:00
parent 2b4770e8cc
commit e78a31e85b
No known key found for this signature in database
GPG Key ID: 03750DEC158E5FA2
4 changed files with 44 additions and 3 deletions

View File

@ -1503,7 +1503,7 @@ class FakeGithubPullRequest(object):
def __init__(self, github, number, project, branch,
subject, upstream_root, files=[], number_of_commits=1,
writers=[], body=None):
writers=[], body=None, draft=False):
"""Creates a new PR with several commits.
Sends an event about opened PR."""
self.github = github
@ -1513,6 +1513,7 @@ class FakeGithubPullRequest(object):
self.branch = branch
self.subject = subject
self.body = body
self.draft = draft
self.number_of_commits = 0
self.upstream_root = upstream_root
self.files = []
@ -1887,11 +1888,11 @@ class FakeGithubConnection(githubconnection.GithubConnection):
self.zuul_web_port = port
def openFakePullRequest(self, project, branch, subject, files=[],
body=None):
body=None, draft=False):
self.pr_number += 1
pull_request = FakeGithubPullRequest(
self, self.pr_number, project, branch, subject, self.upstream_root,
files=files, body=body)
files=files, body=body, draft=draft)
self.pull_requests[self.pr_number] = pull_request
return pull_request

View File

@ -19,6 +19,7 @@ import re
import time
from requests import HTTPError
from requests.structures import CaseInsensitiveDict
FAKE_BASE_URL = 'https://example.com/api/v3/'
@ -380,6 +381,7 @@ class FakePull(object):
},
'ref': pr.branch,
},
'draft': pr.draft,
'mergeable': True,
'state': pr.state,
'head': {
@ -421,6 +423,7 @@ class FakeGithubSession(object):
def __init__(self, data):
self._data = data
self.headers = CaseInsensitiveDict()
def build_url(self, *args):
fakepath = '/'.join(args)

View File

@ -613,6 +613,18 @@ class TestGithubDriver(ZuulTestCase):
self.assertEqual(len(D.comments), 1)
self.assertEqual(D.comments[0], 'Merge failed')
@simple_layout('layouts/dependent-github.yaml', driver='github')
def test_draft_pr(self):
# pipeline merges the pull request on success
A = self.fake_github.openFakePullRequest('org/project', 'master',
'PR title', draft=True)
self.fake_github.emitEvent(A.addLabel('merge'))
self.waitUntilSettled()
# A draft pull request must not enter the gate
self.assertFalse(A.is_merged)
self.assertHistory([])
@simple_layout('layouts/reporting-multiple-github.yaml', driver='github')
def test_reporting_multiple_github(self):
project = 'org/project1'

View File

@ -45,6 +45,7 @@ from zuul.driver.github.githubmodel import PullRequest, GithubTriggerEvent
GITHUB_BASE_URL = 'https://api.github.com'
PREVIEW_JSON_ACCEPT = 'application/vnd.github.machine-man-preview+json'
PREVIEW_DRAFT_ACCEPT = 'application/vnd.github.shadow-cat-preview+json'
def _sign_request(body, secret):
@ -868,6 +869,15 @@ class GithubConnection(BaseConnection):
if app_key:
self.app_key = app_key
@staticmethod
def _append_accept_header(github, value):
old_header = github.session.headers.get('Accept', None)
if old_header:
new_value = '%s,%s' % (old_header, value)
else:
new_value = value
github.session.headers['Accept'] = new_value
def _get_app_auth_headers(self):
now = datetime.datetime.now(utc)
expiry = now + datetime.timedelta(minutes=5)
@ -1414,10 +1424,25 @@ class GithubConnection(BaseConnection):
# conflicts which would block merging finally will be detected by
# the zuul-mergers anyway.
log = get_annotated_logger(self.log, event)
github = self.getGithubClient(change.project.name, zuul_event_id=event)
# Append accept header so we get the draft status
self._append_accept_header(github, PREVIEW_DRAFT_ACCEPT)
owner, proj = change.project.name.split('/')
pull = github.pull_request(owner, proj, change.number)
# If the PR is a draft it cannot be merged.
# TODO: This uses the dict instead of the pull object since github3.py
# has no support for draft PRs yet. Replace this with pull.draft when
# support has been added.
# https://github.com/sigmavirus24/github3.py/issues/926
if pull.as_dict().get('draft', False):
log.debug('Change %s can not merge because it is a draft', change)
return False
protection = self._getBranchProtection(
change.project.name, change.branch, zuul_event_id=event)