Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions config/local_settings_alex.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@
}


GIT_CONFIG = {
'code_dir': None,
'feature_breakup_regex': "(?P<project>[a-zA-Z]+)-?(?P<id>[0-9]+)",
'branch_finder_template': ".*(?i){project}.*"
}
# GIT_CONFIG = {
# 'feature_breakup_regex': "(?P<project>[a-zA-Z]+)-?(?P<id>[0-9]+)",
# 'branch_finder_template': ".*(?i){project}.*"
# }

HIERARCHY_REGEXES = [
'master',
'develop',
'story/{FEATURE_ID}',
'story/{FEATURE_ID}/[a-z_]*',
'{PARENT}[/_][a-z]*',
'dev/{FEATURE_ID}/[a-z]*',
'{PARENT}[/_][a-z]*',
]

JIRA_CONFIG = {
'url': '',
Expand Down
3 changes: 2 additions & 1 deletion config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
# The following is the programmatic equivalent of
# from deploystream.local_settings_<CONFIG> import *
GITHUB_CONFIG = GIT_CONFIG = SPRINTLY_CONFIG = JIRA_CONFIG = None
HIERARCHY_REGEXES = []

try:
CONFIG = environ.get('CONFIG', 'sample')
Expand Down Expand Up @@ -51,4 +52,4 @@
provider_config - a dictionary of provider name to config required.
"""

HIERARCHY_REGEXES = []

9 changes: 4 additions & 5 deletions deploystream/apps/feature/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,11 @@ def get_feature_info(feature_provider, feature_id, providers):

feature = Feature(planning_provider,
**planning_provider.get_feature_info(feature_id))

# Then get any branch info from any source control providers
for provider in providers[ISourceCodeControlProvider]:
for branch_data in provider.get_repo_branches_involved(
feature_id, app.config['HIERARCHY_REGEXES']):
feature.add_branch(Branch(*branch_data, provider=provider))
feature.add_branch(Branch(provider=provider, **branch_data))

# Use that branch info, along with configuration regexes to create a
# hierarchy of the branches involved in the feature.
Expand All @@ -68,9 +67,9 @@ def get_feature_info(feature_provider, feature_id, providers):
for provider in providers[IBuildInfoProvider]:
for branch in feature.branches:
build_info = provider.get_build_information(
branch.repo_name,
branch.branch_name,
branch.latest_commit
branch.repository,
branch.name,
branch.commit_id,
)
branch.build_info = BuildInfo(provider=provider, **build_info)
return feature
87 changes: 61 additions & 26 deletions deploystream/apps/feature/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from collections import defaultdict


class Feature(object):
"""
The class used for encapsulating ``Feature`` data across repos & branches.
Expand Down Expand Up @@ -38,16 +41,19 @@ def __init__(self, provider, project, id, title,
self.url = url
self._extras = kwargs

self.branches = []
self.branches = defaultdict(dict)
self.trees = []

def create_hierarchy_trees(self):
"Create hierarchy trees - one for each repo."
pass

def add_branch(self, branch):
assert isinstance(branch, Branch)
self.branches.append(branch)
self.branches[branch.repository][branch.name] = branch

def create_hierarchy_trees(self):
"Create hierarchy trees - one for each repo."
for branch_set in self.branches.values():
for branch in branch_set.values():
if branch.parent_name:
branch.parent = branch_set[branch.parent_name]


class Branch(object):
Expand All @@ -56,15 +62,13 @@ class Branch(object):

Instances contain values for:

``repo_name`` - The repository that this branch is found in.
``branch_name`` - The name of the branch.
``latest_commit`` - The head commmit, or latest revision in this
branch.
``level`` - The numerical level that this branch falls in the
hierarchy for the feature - where 0 is the highest
level.
``provider`` - The provider instance that found this branch
information.
``repository`` - The repository that this branch is found in.
``name`` - The name of the branch.
``commit_id`` - The head commmit, or latest revision in this
branch.
``parent_name`` - The name of the branches parent.
``provider`` - The provider instance that found this branch
information.


Instances are eventually populated with these values:
Expand All @@ -74,23 +78,54 @@ class Branch(object):
``parent`` - The parent ``Branch`` of this branch (based on
hierarchy rules)
``children`` - A list of children ``Branch`` es of this branch.
``siblings`` - A list of the sibling ``Branch`` es of this branch.
A sibling is a ``Branch`` that has the same parent,
or would have the same parent if one existed.

"""

def __init__(self, repo_name, branch_name, latest_commit, level, provider):
self.parent = None
def __init__(self, provider, repository, name, commit_id,
parent_name,
in_parent=None, has_parent=None):
self._parent = None
self.children = []
self.siblings = [] # Will be needed in the cases where we have no
# parent
self.build_info = None
self.repo_name = repo_name
self.branch_name = branch_name
self.latest_commit = latest_commit
self.level = level
self.repository = repository
self.name = name
self.commit_id = commit_id
self.parent_name = parent_name
self.in_parent = in_parent
self.has_parent = has_parent
self._provider = provider

@property
def parent(self):
return self._parent

@parent.setter
def parent(self, branch):
if self._parent:
# remove first
self._parent.remove_child(self)
self._parent = branch
branch.ensure_child(self)

def ensure_child(self, branch):
if branch not in self.children:
self.children.append(branch)

def remove_child(self, branch):
try:
self.children.remove(branch)
except ValueError:
pass

def as_tree_string(self, indent=0):
if self.parent and not indent:
return self.parent.as_tree_string()
me = "{0}- {1}\n".format(indent * ' ', self.name)

for c in self.children:
me += c.as_tree_string(indent + 4)
return me


class BuildInfo(object):
"""
Expand Down
80 changes: 46 additions & 34 deletions deploystream/lib/hierarchy.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,10 @@
from collections import defaultdict
import re


def create_single_regex(feature_id, hierarchical_regexes):
def match_with_genealogy(feature_id, branches, hierarchical_regexes):
"""
Create a single regex to be used to find which level a branch is on.

:param feature_id:
The id of the feature. This is substituted into the
``hierarchical_regexes`` if they use {FEATURE_ID} anywhere.

:param hierarchical_regexes:
A list of regexes to be joined into one single regex.

:returns:
A single regex for easier matching.
"""
subs = []
for index, regex in enumerate(hierarchical_regexes):
subs.append("(?P<level_{0}>^{1}$)".format(index, regex))
full_regex = "|".join(subs)
full_regex = full_regex.format(FEATURE_ID=feature_id)
return full_regex


def match_with_levels(feature_id, branch, hierarchical_regexes):
"""
Filter and return the branches in appropriate levels.
Filter and return the branches in order with parents attached.

:param feature_id:
The feature to filter the branch names on.
Expand All @@ -37,16 +16,49 @@ def match_with_levels(feature_id, branch, hierarchical_regexes):
A list of regexes assumed to be in descending order of branch status.

:returns:
The positional index that the branch should be found in. Or None if it
does not match.
A list of tuples containing:
- Branch name
- Parent name

Go through the hierarchy regexes in sequence.
Attempt to match each one against all the branches. When a match occurs
remove the branch from the list to be matched against and continue.
Also add parental information as we go along.
"""
regex = create_single_regex(feature_id, hierarchical_regexes)
matched_branches = defaultdict(list)
hierarchy = []
for index, regex in enumerate(hierarchical_regexes):
try:
parent_regex = hierarchical_regexes[max(index - 1, 0)]
except IndexError:
parent_regex = None

# Find the possible parents for any branches found at this level.
# Simply look at the level above, and if not there then keep going back
parent_index = index - 1
fake_parent = False
possible_parents = matched_branches[parent_index]
while not possible_parents:
parent_index -= 1
if parent_index < 0:
fake_parent = True
possible_parents = [parent_regex]
break
possible_parents = matched_branches[parent_index]

result = re.match(regex, branch)
if not result:
return None
# Look through all the branches (that are left to look at) and see
# if any match this regex.
for branch in branches[:]:
for parent in possible_parents:
full_regex = regex.format(FEATURE_ID=feature_id,
PARENT=parent)
result = re.match("^{0}$".format(full_regex), branch)
if result:
matched_branches[index].append(branch)
branches.remove(branch)
if fake_parent:
parent = None
hierarchy.append((branch, parent))
break

for level, match in result.groupdict().items():
if match:
index = int(level.split('level_')[1])
return index
return hierarchy
2 changes: 1 addition & 1 deletion deploystream/lib/transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def nativify(data):
for k, v in data.items() if not k.startswith('_')
}
elif data is None:
return 'null'
return None
elif hasattr(data, '__dict__'):
return nativify(data.__dict__)
else:
Expand Down
Loading