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
This commit is contained in:
Tobias Henkel 2020-07-16 10:26:15 +02:00
parent a0de74bef8
commit 0093aabd4e
No known key found for this signature in database
GPG Key ID: 03750DEC158E5FA2
4 changed files with 49 additions and 16 deletions

View File

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

View File

@ -687,9 +687,24 @@ class FakeGithubClient(object):
return re.match(r'[a-z0-9]{40}', s) return re.match(r'[a-z0-9]{40}', s)
if query_is_sha(query): if query_is_sha(query):
return (FakeIssueSearchResult(FakeIssue(pr)) # Github returns all PR's that contain the sha in their history
for pr in self._data.pull_requests.values() result = []
if pr.head_sha == query) for pr in self._data.pull_requests.values():
# Quick check if head sha matches
if pr.head_sha == query:
result.append(FakeIssueSearchResult(FakeIssue(pr)))
continue
# 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:
result.append(FakeIssueSearchResult(FakeIssue(pr)))
continue
return result
# Non-SHA queries are of the form: # Non-SHA queries are of the form:
# #

View File

@ -92,6 +92,11 @@ class TestGithubRequirements(ZuulTestCase):
project = 'org/project2' project = 'org/project2'
A = self.fake_github.openFakePullRequest(project, 'master', 'A') 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.
self.fake_github.openFakePullRequest(
project, 'master', 'A', base_sha=A.head_sha)
# An error status should not cause it to be enqueued # An error status should not cause it to be enqueued
self.fake_github.setCommitStatus(project, A.head_sha, 'error', self.fake_github.setCommitStatus(project, A.head_sha, 'error',
context='tenant-one/check') context='tenant-one/check')

View File

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