GitHub file matching support

Allow to configure jobs to run only when certain files are changed.

Github does not list the /COMMIT_MSG in the changed files as gerrit
does. Therefore the matcher now returns False only if the single file is
the /COMMIT_MSG one.

Change-Id: I4fa8a328f2ba430c25377e50e1eff7c45829eba6
This commit is contained in:
Jan Hruban 2016-03-10 21:51:32 +01:00 committed by Jesse Keating
parent 3b415926d2
commit 570d01c5fb
8 changed files with 84 additions and 11 deletions

View File

@ -547,7 +547,7 @@ class GithubChangeReference(git.Reference):
class FakeGithubPullRequest(object):
def __init__(self, github, number, project, branch,
subject, upstream_root, number_of_commits=1):
subject, upstream_root, files=[], number_of_commits=1):
"""Creates a new PR with several commits.
Sends an event about opened PR."""
self.github = github
@ -558,6 +558,7 @@ class FakeGithubPullRequest(object):
self.subject = subject
self.number_of_commits = 0
self.upstream_root = upstream_root
self.files = []
self.comments = []
self.labels = []
self.statuses = {}
@ -566,18 +567,18 @@ class FakeGithubPullRequest(object):
self.is_merged = False
self.merge_message = None
self._createPRRef()
self._addCommitToRepo()
self._addCommitToRepo(files=files)
self._updateTimeStamp()
def addCommit(self):
def addCommit(self, files=[]):
"""Adds a commit on top of the actual PR head."""
self._addCommitToRepo()
self._addCommitToRepo(files=files)
self._updateTimeStamp()
self._clearStatuses()
def forcePush(self):
def forcePush(self, files=[]):
"""Clears actual commits and add a commit on top of the base."""
self._addCommitToRepo(reset=True)
self._addCommitToRepo(files=files, reset=True)
self._updateTimeStamp()
self._clearStatuses()
@ -690,7 +691,7 @@ class FakeGithubPullRequest(object):
GithubChangeReference.create(
repo, self._getPRReference(), 'refs/tags/init')
def _addCommitToRepo(self, reset=False):
def _addCommitToRepo(self, files=[], reset=False):
repo = self._getRepo()
ref = repo.references[self._getPRReference()]
if reset:
@ -701,7 +702,12 @@ class FakeGithubPullRequest(object):
zuul.merger.merger.reset_repo_to_head(repo)
repo.git.clean('-x', '-f', '-d')
fn = '%s-%s' % (self.branch.replace('/', '_'), self.number)
if files:
fn = files[0]
self.files = files
else:
fn = '%s-%s' % (self.branch.replace('/', '_'), self.number)
self.files = [fn]
msg = self.subject + '-' + str(self.number_of_commits)
fn = os.path.join(repo.working_dir, fn)
f = open(fn, 'w')
@ -776,10 +782,11 @@ class FakeGithubConnection(githubconnection.GithubConnection):
self.merge_failure = False
self.merge_not_allowed_count = 0
def openFakePullRequest(self, project, branch, subject):
def openFakePullRequest(self, project, branch, subject, files=[]):
self.pr_number += 1
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)
self.pull_requests.append(pull_request)
return pull_request
@ -830,6 +837,10 @@ class FakeGithubConnection(githubconnection.GithubConnection):
}
return data
def getPullFileNames(self, project, number):
pr = self.pull_requests[number - 1]
return pr.files
def getUser(self, login):
data = {
'username': login,

View File

@ -0,0 +1,18 @@
- pipeline:
name: check
manager: independent
trigger:
github:
- event: pull_request
action: opened
- job:
name: project-test1
files:
- '.*-requires'
- project:
name: org/project
check:
jobs:
- project-test1

View File

@ -125,12 +125,18 @@ class TestMatchAllFiles(BaseTestMatcher):
def test_matches_returns_false_when_not_all_files_match(self):
self._test_matches(False, files=['/COMMIT_MSG', 'docs/foo', 'foo/bar'])
def test_matches_returns_true_when_single_file_does_not_match(self):
self._test_matches(True, files=['docs/foo'])
def test_matches_returns_false_when_commit_message_matches(self):
self._test_matches(False, files=['/COMMIT_MSG'])
def test_matches_returns_true_when_all_files_match(self):
self._test_matches(True, files=['/COMMIT_MSG', 'docs/foo'])
def test_matches_returns_true_when_single_file_matches(self):
self._test_matches(True, files=['docs/foo'])
class TestMatchAll(BaseTestMatcher):

View File

@ -65,6 +65,22 @@ class TestGithubDriver(ZuulTestCase):
self.assertEqual(2, len(self.history))
@simple_layout('layouts/files-github.yaml', driver='github')
def test_pull_matched_file_event(self):
A = self.fake_github.openFakePullRequest(
'org/project', 'master', 'A',
files=['random.txt', 'build-requires'])
self.fake_github.emitEvent(A.getPullRequestOpenedEvent())
self.waitUntilSettled()
self.assertEqual(1, len(self.history))
# test_pull_unmatched_file_event
B = self.fake_github.openFakePullRequest('org/project', 'master', 'B',
files=['random.txt'])
self.fake_github.emitEvent(B.getPullRequestOpenedEvent())
self.waitUntilSettled()
self.assertEqual(1, len(self.history))
@simple_layout('layouts/basic-github.yaml', driver='github')
def test_comment_event(self):
A = self.fake_github.openFakePullRequest('org/project', 'master', 'A')

View File

@ -73,11 +73,21 @@ class TestJob(BaseTestCase):
change.files = ['/COMMIT_MSG', 'docs/foo']
self.assertFalse(self.job.changeMatches(change))
def test_change_matches_returns_false_for_single_matched_skip_if(self):
change = model.Change('project')
change.files = ['docs/foo']
self.assertFalse(self.job.changeMatches(change))
def test_change_matches_returns_true_for_unmatched_skip_if(self):
change = model.Change('project')
change.files = ['/COMMIT_MSG', 'foo']
self.assertTrue(self.job.changeMatches(change))
def test_change_matches_returns_true_for_single_unmatched_skip_if(self):
change = model.Change('project')
change.files = ['foo']
self.assertTrue(self.job.changeMatches(change))
def test_job_sets_defaults_for_boolean_attributes(self):
self.assertIsNotNone(self.job.voting)

View File

@ -108,7 +108,9 @@ class MatchAllFiles(AbstractMatcherCollection):
yield self.commit_regex
def matches(self, change):
if not (hasattr(change, 'files') and len(change.files) > 1):
if not (hasattr(change, 'files') and change.files):
return False
if len(change.files) == 1 and self.commit_regex.match(change.files[0]):
return False
for file_ in change.files:
matched_file = False

View File

@ -288,6 +288,7 @@ class GithubConnection(BaseConnection):
change.url = event.change_url
change.updated_at = self._ghTimestampToDate(event.updated_at)
change.patchset = event.patch_number
change.files = self.getPullFileNames(project, change.number)
change.title = event.title
change.source_event = event
elif event.ref:
@ -347,6 +348,11 @@ class GithubConnection(BaseConnection):
# For now, just send back a True value.
return True
def getPullFileNames(self, project, number):
owner, proj = project.name.split('/')
return [f.filename for f in
self.github.pull_request(owner, proj, number).files()]
def getUser(self, login):
return GithubUser(self.github, login)

View File

@ -84,5 +84,9 @@ class GithubSource(BaseSource):
"""Get the git-web url for a project."""
return self.connection.getGitwebUrl(project, sha)
def getPullFiles(self, project, number):
"""Get filenames of the pull request"""
return self.connection.getPullFileNames(project, number)
def _ghTimestampToDate(self, timestamp):
return time.strptime(timestamp, '%Y-%m-%dT%H:%M:%SZ')