Check if Github detected a merge conflict for a PR
Github uses libgit2 to compute merges without requiring a worktree [0]. In some cases this can lead to Github detecting a merge conflict while for Zuul the PR merges fine. To prevent such changes from entering dependent pipelines and e.g. cause a gate reset, we'll also check if Github detected any merge conflicts. [0] https://github.blog/2022-10-03-highlights-from-git-2-38/ Change-Id: I22275f24c903a8548bb0ef6c32a2e15ba9eadac8changes/12/865012/6
parent
4d555ca675
commit
c8aac6a118
|
@ -2340,7 +2340,7 @@ class FakeGithubPullRequest(object):
|
|||
def __init__(self, github, number, project, branch,
|
||||
subject, upstream_root, files=None, number_of_commits=1,
|
||||
writers=[], body=None, body_text=None, draft=False,
|
||||
base_sha=None):
|
||||
mergeable=True, base_sha=None):
|
||||
"""Creates a new PR with several commits.
|
||||
Sends an event about opened PR.
|
||||
|
||||
|
@ -2356,6 +2356,7 @@ class FakeGithubPullRequest(object):
|
|||
self.body = body
|
||||
self.body_text = body_text
|
||||
self.draft = draft
|
||||
self.mergeable = mergeable
|
||||
self.number_of_commits = 0
|
||||
self.upstream_root = upstream_root
|
||||
# Dictionary of FakeFile -> content
|
||||
|
@ -2911,12 +2912,12 @@ class FakeGithubConnection(githubconnection.GithubConnection):
|
|||
|
||||
def openFakePullRequest(self, project, branch, subject, files=[],
|
||||
body=None, body_text=None, draft=False,
|
||||
base_sha=None):
|
||||
mergeable=True, base_sha=None):
|
||||
self.pr_number += 1
|
||||
pull_request = FakeGithubPullRequest(
|
||||
self, self.pr_number, project, branch, subject, self.upstream_root,
|
||||
files=files, body=body, body_text=body_text, draft=draft,
|
||||
base_sha=base_sha)
|
||||
mergeable=mergeable, base_sha=base_sha)
|
||||
self.pull_requests[self.pr_number] = pull_request
|
||||
return pull_request
|
||||
|
||||
|
|
|
@ -181,10 +181,14 @@ class FakeCommit(ObjectType):
|
|||
class FakePullRequest(ObjectType):
|
||||
isDraft = Boolean()
|
||||
reviewDecision = String()
|
||||
mergeable = String()
|
||||
|
||||
def resolve_isDraft(parent, info):
|
||||
return parent.draft
|
||||
|
||||
def resolve_mergeable(parent, info):
|
||||
return "MERGEABLE" if parent.mergeable else "CONFLICTING"
|
||||
|
||||
def resolve_reviewDecision(parent, info):
|
||||
if hasattr(info.context, 'version') and info.context.version:
|
||||
if info.context.version < (2, 21, 0):
|
||||
|
|
|
@ -547,7 +547,7 @@ class FakePull(object):
|
|||
'login': 'octocat'
|
||||
},
|
||||
'draft': pr.draft,
|
||||
'mergeable': True,
|
||||
'mergeable': pr.mergeable,
|
||||
'state': pr.state,
|
||||
'head': {
|
||||
'sha': pr.head_sha,
|
||||
|
|
|
@ -785,6 +785,18 @@ class TestGithubDriver(ZuulTestCase):
|
|||
self.assertFalse(A.is_merged)
|
||||
self.assertHistory([])
|
||||
|
||||
@simple_layout('layouts/dependent-github.yaml', driver='github')
|
||||
def test_non_mergeable_pr(self):
|
||||
# pipeline merges the pull request on success
|
||||
A = self.fake_github.openFakePullRequest('org/project', 'master',
|
||||
'PR title', mergeable=False)
|
||||
self.fake_github.emitEvent(A.addLabel('merge'))
|
||||
self.waitUntilSettled()
|
||||
|
||||
# A non-mergeable pull request must not enter 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'
|
||||
|
|
|
@ -1692,6 +1692,8 @@ class GithubConnection(ZKChangeCacheMixin, ZKBranchCacheMixin, BaseConnection):
|
|||
|
||||
change.contexts = self._get_contexts(canmerge_data)
|
||||
change.draft = canmerge_data.get('isDraft', False)
|
||||
change.mergeable = (canmerge_data.get('mergeable', 'MERGEABLE').lower()
|
||||
in ('mergeable', 'unknown'))
|
||||
change.review_decision = canmerge_data['reviewDecision']
|
||||
change.required_contexts = set(
|
||||
canmerge_data['requiredStatusCheckContexts']
|
||||
|
@ -1860,6 +1862,11 @@ class GithubConnection(ZKChangeCacheMixin, ZKBranchCacheMixin, BaseConnection):
|
|||
log.debug('Change %s can not merge because it is a draft', change)
|
||||
return False
|
||||
|
||||
if not change.mergeable:
|
||||
log.debug('Change %s can not merge because Github detected a '
|
||||
'merge conflict', change)
|
||||
return False
|
||||
|
||||
missing_status_checks = self._getMissingStatusChecks(
|
||||
change, allow_needs)
|
||||
if missing_status_checks:
|
||||
|
|
|
@ -38,6 +38,7 @@ class PullRequest(Change):
|
|||
self.files = []
|
||||
self.labels = []
|
||||
self.draft = None
|
||||
self.mergeable = True
|
||||
self.review_decision = None
|
||||
self.required_contexts = set()
|
||||
self.contexts = set()
|
||||
|
@ -74,6 +75,7 @@ class PullRequest(Change):
|
|||
"reviews": list(self.reviews),
|
||||
"labels": self.labels,
|
||||
"draft": self.draft,
|
||||
"mergeable": self.mergeable,
|
||||
"review_decision": self.review_decision,
|
||||
"required_contexts": list(self.required_contexts),
|
||||
"contexts": list(self.contexts),
|
||||
|
@ -90,6 +92,7 @@ class PullRequest(Change):
|
|||
self.reviews = data.get("reviews", [])
|
||||
self.labels = data.get("labels", [])
|
||||
self.draft = data.get("draft")
|
||||
self.mergeable = data.get("mergeable", True)
|
||||
self.review_decision = data.get("review_decision")
|
||||
self.required_contexts = set(data.get("required_contexts", []))
|
||||
self.contexts = set(tuple(c) for c in data.get("contexts", []))
|
||||
|
|
|
@ -116,6 +116,11 @@ class GraphQLClient:
|
|||
pull_request = nested_get(repository, 'pullRequest')
|
||||
result['isDraft'] = nested_get(pull_request, 'isDraft', default=False)
|
||||
|
||||
# Check if Github detected a merge conflict. Possible enum values
|
||||
# are CONFLICTING, MERGEABLE and UNKNOWN.
|
||||
result['mergeable'] = nested_get(pull_request, 'mergeable',
|
||||
default='MERGEABLE')
|
||||
|
||||
# Get review decision. This is supported since GHE 2.21. Default to
|
||||
# None to signal if the field is not present.
|
||||
result['reviewDecision'] = nested_get(
|
||||
|
|
|
@ -20,6 +20,7 @@ query canMergeData(
|
|||
}
|
||||
pullRequest(number: $pull) {
|
||||
isDraft
|
||||
mergeable
|
||||
reviewDecision
|
||||
}
|
||||
object(expression: $head_sha) {
|
||||
|
|
Loading…
Reference in New Issue