Browse Source

Fix multiple prs found when commit is not head

When receiving a status or checks event we need to find the
corresponding pull request to that event. Github stores the status on
the head commit of a pr and not on the pr itself which leads to
problems if multiple prs exist with the same head but different target
branches. Therefore zuul errors out when more than one pr is found for
that event. However the github search also returns all prs that
contain the sha we search for but have a different head which leads to
the same error as if we had two conflicting prs. This can be solved by
delaying the error and filtering the pr bodies for the head sha first.

Change-Id: Iadafd3cf68a742941e9189e84fca594bc3394084
Tobias Henkel 7 months ago
No known key found for this signature in database GPG Key ID: 3750DEC158E5FA2
4 changed files with 49 additions and 16 deletions
  1. +10
  2. +18
  3. +5
  4. +16

+ 10
- 6
tests/ View File

@ -1909,7 +1909,8 @@ class FakeGithubPullRequest(object):
def __init__(self, github, number, project, branch,
subject, upstream_root, files=[], number_of_commits=1,
writers=[], body=None, body_text=None, draft=False):
writers=[], body=None, body_text=None, draft=False,
"""Creates a new PR with several commits.
Sends an event about opened PR."""
self.github = github
@ -1936,7 +1937,7 @@ class FakeGithubPullRequest(object):
self.merge_message = None
self.state = 'open'
self.url = 'https://%s/%s/pull/%s' % (github.server, project, number)
@ -2103,10 +2104,11 @@ class FakeGithubPullRequest(object):
repo_path = os.path.join(self.upstream_root, self.project)
return git.Repo(repo_path)
def _createPRRef(self):
def _createPRRef(self, base_sha=None):
base_sha = base_sha or 'refs/tags/init'
repo = self._getRepo()
repo, self.getPRReference(), 'refs/tags/init')
repo, self.getPRReference(), base_sha)
def _addCommitToRepo(self, files=[], reset=False):
repo = self._getRepo()
@ -2354,11 +2356,13 @@ class FakeGithubConnection(githubconnection.GithubConnection):
self.zuul_web_port = port
def openFakePullRequest(self, project, branch, subject, files=[],
body=None, body_text=None, draft=False):
body=None, body_text=None, draft=False,
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)
files=files, body=body, body_text=body_text, draft=draft,
self.pull_requests[self.pr_number] = pull_request
return pull_request

+ 18
- 3
tests/ View File

@ -687,9 +687,24 @@ class FakeGithubClient(object):
return re.match(r'[a-z0-9]{40}', s)
if query_is_sha(query):
return (FakeIssueSearchResult(FakeIssue(pr))
for pr in self._data.pull_requests.values()
if pr.head_sha == query)
# Github returns all PR's that contain the sha in their history
result = []
for pr in self._data.pull_requests.values():
# Quick check if head sha matches
if pr.head_sha == query:
# If head sha doesn't match it still could be in the pr history
repo = pr._getRepo()
commits = repo.iter_commits(
'%s...%s' % (pr.branch, pr.head_sha))
for commit in commits:
if commit.hexsha == query:
return result
# Non-SHA queries are of the form:

+ 5
- 0
tests/unit/ View File

@ -92,6 +92,11 @@ class TestGithubRequirements(ZuulTestCase):
project = 'org/project2'
A = self.fake_github.openFakePullRequest(project, 'master', 'A')
# Create second PR which contains the head of A in its history. Zuul
# should not get disturbed by the existence of this one.
project, 'master', 'A', base_sha=A.head_sha)
# An error status should not cause it to be enqueued
self.fake_github.setCommitStatus(project, A.head_sha, 'error',

+ 16
- 7
zuul/driver/github/ View File

@ -1660,16 +1660,25 @@ class GithubConnection(BaseConnection):
issues = list(github.search_issues(sha))
log.debug('Got PR on project %s for sha %s', project_name, sha)
if len(issues) > 1:
raise Exception('Multiple pulls found with head sha %s' % sha)
if len(issues) == 0:
return None
pr_body = self._getChange(
project, issues.pop().issue.number, sha, event=event).pr
self._sha_pr_cache.update(project_name, pr_body)
return pr_body
# Github returns all issues that contain the sha, not only the ones
# with that sha as head_sha so we need to get and update all those
# changes and then filter for the head sha before we can error out
# with multiple pulls found.
found_pr_body = None
for item in issues:
pr_body = self._getChange(
project, item.issue.number, sha, event=event).pr
self._sha_pr_cache.update(project_name, pr_body)
if pr_body['head']['sha'] == sha:
if found_pr_body:
raise Exception(
'Multiple pulls found with head sha %s' % sha)
found_pr_body = pr_body
return found_pr_body
def getPullReviews(self, pr_obj, project, number, event):
log = get_annotated_logger(self.log, event)