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
158 lines
4.1 KiB
Python
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
|