Files
zuul/zuul/change_matcher.py
James E. Blair 1edfd97943 Fix implied branch matchers and tags
Adding an implied branch matcher to jobs on in-repo project defs
works great when an item *has* a branch.  But some items, such as
tags, don't.  With recent changes, it is now impossible for a
project to add a job in-repo that runs in a tag pipeline.

To correct this, we need to drop some of the optimizations which
assumed we could match the implied branch against existing branch
matchers, and instead, when adding a job in-repo, simply add a new
kind of branch matcher, an ImpliedBranchMatcher, that is evaluated
in a boolean 'and' with any existing branch matchers.

The ImpliedBranchMatcher only fails if the item has a branch, and
the branch doesn't match.  If the item doesn't have a branch, it
always succeeds.

This means that when a project adds a job to a tag pipeline in-repo,
it will most likely only have the ImpliedBranchMatcher, which will
simply succeed.

It also means that the multiple project configurations present in
the project's multiple branches can all add jobs to tag pipelines,
and so to remove such a job, changes may need to be made to all
branches of a project.  However, there's not much that can be done
about that at the moment.

Change-Id: Id51ddfce7ef0a6d5e3273da784e407ac72a669db
2017-12-01 15:54:24 -08:00

158 lines
4.1 KiB
Python

# Copyright 2015 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
This module defines classes used in matching changes based on job
configuration.
"""
import re
class AbstractChangeMatcher(object):
def __init__(self, regex):
self._regex = regex
self.regex = re.compile(regex)
def matches(self, change):
"""Return a boolean indication of whether change matches
implementation-specific criteria.
"""
raise NotImplementedError()
def copy(self):
return self.__class__(self._regex)
def __deepcopy__(self, memo):
return self.copy()
def __eq__(self, other):
return str(self) == str(other)
def __ne__(self, other):
return not self.__eq__(other)
def __str__(self):
return '{%s:%s}' % (self.__class__.__name__, self._regex)
def __repr__(self):
return '<%s %s>' % (self.__class__.__name__, self._regex)
class ProjectMatcher(AbstractChangeMatcher):
def matches(self, change):
return self.regex.match(str(change.project))
class BranchMatcher(AbstractChangeMatcher):
def matches(self, change):
if hasattr(change, 'branch'):
if self.regex.match(change.branch):
return True
return False
if self.regex.match(change.ref):
return True
return False
class ImpliedBranchMatcher(AbstractChangeMatcher):
"""
A branch matcher that only considers branch refs, and always
succeeds on other types (e.g., tags).
"""
def matches(self, change):
if hasattr(change, 'branch'):
if self.regex.match(change.branch):
return True
return False
return True
class FileMatcher(AbstractChangeMatcher):
def matches(self, change):
if not hasattr(change, 'files'):
return False
for file_ in change.files:
if self.regex.match(file_):
return True
return False
class AbstractMatcherCollection(AbstractChangeMatcher):
def __init__(self, matchers):
self.matchers = matchers
def __eq__(self, other):
return str(self) == str(other)
def __str__(self):
return '{%s:%s}' % (self.__class__.__name__,
','.join([str(x) for x in self.matchers]))
def __repr__(self):
return '<%s>' % self.__class__.__name__
def copy(self):
return self.__class__(self.matchers[:])
class MatchAllFiles(AbstractMatcherCollection):
commit_regex = re.compile('^/COMMIT_MSG$')
@property
def regexes(self):
for matcher in self.matchers:
yield matcher.regex
yield self.commit_regex
def matches(self, change):
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
for regex in self.regexes:
if regex.match(file_):
matched_file = True
break
if not matched_file:
return False
return True
class MatchAll(AbstractMatcherCollection):
def matches(self, change):
for matcher in self.matchers:
if not matcher.matches(change):
return False
return True
class MatchAny(AbstractMatcherCollection):
def matches(self, change):
for matcher in self.matchers:
if matcher.matches(change):
return True
return False