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
diff --git a/tests/base.py b/tests/base.py
index c567b03..937d60f 100755
--- a/tests/base.py
+++ b/tests/base.py
@@ -547,7 +547,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
return data
+ def getPullFileNames(self, project, number):
+ pr = self.pull_requests[number - 1]
+ return pr.files
+
def getUser(self, login):
data = {
'username': login,
diff --git a/tests/fixtures/layouts/files-github.yaml b/tests/fixtures/layouts/files-github.yaml
new file mode 100644
index 0000000..734b945
--- /dev/null
+++ b/tests/fixtures/layouts/files-github.yaml
@@ -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
diff --git a/tests/unit/test_change_matcher.py b/tests/unit/test_change_matcher.py
index 0585322..6b161a1 100644
--- a/tests/unit/test_change_matcher.py
+++ b/tests/unit/test_change_matcher.py
@@ -125,12 +125,18 @@
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):
diff --git a/tests/unit/test_github_driver.py b/tests/unit/test_github_driver.py
index 7267b83..605a479 100644
--- a/tests/unit/test_github_driver.py
+++ b/tests/unit/test_github_driver.py
@@ -65,6 +65,22 @@
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')
diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py
index d8480ea..5f968b4 100644
--- a/tests/unit/test_model.py
+++ b/tests/unit/test_model.py
@@ -73,11 +73,21 @@
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)
diff --git a/zuul/change_matcher.py b/zuul/change_matcher.py
index 1da1d2c..baea217 100644
--- a/zuul/change_matcher.py
+++ b/zuul/change_matcher.py
@@ -108,7 +108,9 @@
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
diff --git a/zuul/driver/github/githubconnection.py b/zuul/driver/github/githubconnection.py
index e8162cd..0c4434f 100644
--- a/zuul/driver/github/githubconnection.py
+++ b/zuul/driver/github/githubconnection.py
@@ -288,6 +288,7 @@
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 @@
# 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)
diff --git a/zuul/driver/github/githubsource.py b/zuul/driver/github/githubsource.py
index a638122..312ee87 100644
--- a/zuul/driver/github/githubsource.py
+++ b/zuul/driver/github/githubsource.py
@@ -84,5 +84,9 @@
"""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')