diff --git a/config/local_settings_alex.py b/config/local_settings_alex.py index cf6d360..1006b62 100644 --- a/config/local_settings_alex.py +++ b/config/local_settings_alex.py @@ -4,12 +4,20 @@ } -GIT_CONFIG = { - 'code_dir': None, - 'feature_breakup_regex': "(?P[a-zA-Z]+)-?(?P[0-9]+)", - 'branch_finder_template': ".*(?i){project}.*" -} +# GIT_CONFIG = { +# 'feature_breakup_regex': "(?P[a-zA-Z]+)-?(?P[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': '', diff --git a/config/settings.py b/config/settings.py index 33843ed..a7f261b 100644 --- a/config/settings.py +++ b/config/settings.py @@ -7,6 +7,7 @@ # The following is the programmatic equivalent of # from deploystream.local_settings_ import * GITHUB_CONFIG = GIT_CONFIG = SPRINTLY_CONFIG = JIRA_CONFIG = None +HIERARCHY_REGEXES = [] try: CONFIG = environ.get('CONFIG', 'sample') @@ -51,4 +52,4 @@ provider_config - a dictionary of provider name to config required. """ -HIERARCHY_REGEXES = [] + diff --git a/deploystream/apps/feature/lib.py b/deploystream/apps/feature/lib.py index 4a05916..a4ad4b0 100644 --- a/deploystream/apps/feature/lib.py +++ b/deploystream/apps/feature/lib.py @@ -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. @@ -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 diff --git a/deploystream/apps/feature/models.py b/deploystream/apps/feature/models.py index 284f543..46ded1f 100644 --- a/deploystream/apps/feature/models.py +++ b/deploystream/apps/feature/models.py @@ -1,3 +1,6 @@ +from collections import defaultdict + + class Feature(object): """ The class used for encapsulating ``Feature`` data across repos & branches. @@ -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): @@ -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: @@ -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): """ diff --git a/deploystream/lib/hierarchy.py b/deploystream/lib/hierarchy.py index 611a5ce..5e94308 100644 --- a/deploystream/lib/hierarchy.py +++ b/deploystream/lib/hierarchy.py @@ -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^{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. @@ -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 diff --git a/deploystream/lib/transforms.py b/deploystream/lib/transforms.py index 82486f8..ba12d0d 100644 --- a/deploystream/lib/transforms.py +++ b/deploystream/lib/transforms.py @@ -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: diff --git a/deploystream/providers/git_provider/__init__.py b/deploystream/providers/git_provider/__init__.py index 6b322f1..eb90689 100644 --- a/deploystream/providers/git_provider/__init__.py +++ b/deploystream/providers/git_provider/__init__.py @@ -4,115 +4,63 @@ import git +from deploystream.lib import hierarchy + class GitProvider(object): name = 'git' oauth_token_name = None - def __init__(self, code_dir='.', - feature_breakup_regex='', - branch_finder_template=''): + def __init__(self, code_dir): """ Create a GitProvider. :param code_dir: The filesystem path to the directory within which all repositories live that are to be queried. - - :param feature_breakup_regex: - A regular expression to be used to breakup feature ids into - understandable parts. The regex should use named groups to be - of use to the ``branch_finder_template``. - - eg. "(?P[a-zA-Z]+)-?(?P[0-9]+)" - - :param branch_finder_template: - A template regular expression with named gaps to be filled by the - outcome of breaking up the feature. - - eg. ".*{id}.*" - """ self.code_dir = code_dir - self.feature_breakup_regex = feature_breakup_regex - self.branch_finder_template = branch_finder_template - def get_repo_branches_involved(self, feature_id): + def get_repo_branches_involved(self, feature_id, hierarchy_regexes): """ Get all the repo branches involved. For each repository in each repo location defined in configuration, call ``get_branches_involved`` and return a list of tuples. - :returns: - A list of iterables containing at position: - 0: repo name - 1: branch name - 2: latest commit + :returns: + A list of dictionaries containing keys for: + - repository + - name + - parent_name + - commit_id """ - # Every folder inside self.code_dir that is a git repo will be looked - # at - repo_branches = [] + branch_list = [] + for repo_name in os.listdir(self.code_dir): repo_location = join(self.code_dir, repo_name) if exists(join(repo_location, ".git")): - branches = self.get_branches_involved(repo_location, - feature_id) - repo_branches.extend([ - (repo_name, ) + branch for branch in branches]) - return repo_branches - - def _get_feature_breakdown(self, feature_id): - """ - Break up the feature_id using the regex in configuration. - """ - match = re.search(self.feature_breakup_regex, feature_id) - if match: - return match.groupdict() + repo_branches = {} - def get_branches_involved(self, repo_location, feature_id): - """ - Get the set of brances involved in the given repo and feature. - - :param repo_location: - The location of the repository to search for branches. - - :param feature_id: - The id of the feature to look for in branches. - - :returns: - A list of iterables containing at position: - - 0: branch name - 1: latest commit - """ - repo = git.Repo("{repo_location}/.git" + repo = git.Repo("{repo_location}/.git" .format(repo_location=repo_location)) - remote = git.remote.Remote(repo, 'origin') - affected = [] - feature_breakup = self._get_feature_breakdown(feature_id) - regex = self.branch_finder_template.format(**feature_breakup) - for remote_ref in remote.refs: - if re.search(regex, remote_ref.remote_head): - affected.append((remote_ref.remote_head, - str(remote_ref.commit))) - - return affected - - def set_merged_status(self, repo_name, hierarchy_tree, **kwargs): - """ - Set the merged status of the given tree in the repo. - - :param repo_name: - The name of the repository to search for branches. - - :param hierarchy_tree: - A tree-like object. - - TODO: - more definition here... (traversing, etc) - - """ - pass + remote = git.remote.Remote(repo, 'origin') + for remote_ref in remote.refs: + repo_branches[remote_ref.remote_head] = { + 'sha': str(remote_ref.commit) + } + + genealogy = hierarchy.match_with_genealogy( + feature_id, repo_branches.keys(), hierarchy_regexes) + + for branch, parent in genealogy: + branch_list.append({ + "repository": repo_name, + "name": branch, + "commit_id": repo_branches[branch]['sha'], + "parent_name": parent, + }) + + return branch_list diff --git a/deploystream/providers/github/__init__.py b/deploystream/providers/github/__init__.py index 3396e42..d451bb8 100644 --- a/deploystream/providers/github/__init__.py +++ b/deploystream/providers/github/__init__.py @@ -91,7 +91,12 @@ def get_features(self, **filters): return features def get_feature_info(self, feature_id): - pass + # Feature ID will need to have org in it. + # For now we'll do a really crude search through the get_features + # results + for feat in self.get_features(): + if str(feat['id']) == str(feature_id): + return feat @classmethod def get_oauth_data(self): @@ -106,19 +111,67 @@ def get_oauth_data(self): } def get_repo_branches_involved(self, feature_id, hierarchy_regexes): + """ + Get the list of branches involved for the given ``feature_id``. + + :returns: + A list of dictionaries containing keys for: + - repository + - name + - parent_name + - commit_id + - has_parent + - in_parent + + Look through each repo and get a set of branches that match the + ``hierarchy_regexes``. + + Go through matching branches finding their merge status. + + .. note:: + We loop through all the commits for every matching branch exactly + once. This could be optimized to only check back as far as some + ancestor's HEAD. + + """ branch_list = [] for repo in self.repositories: + repo_branches = {} for branch in repo.iter_branches(): - level = hierarchy.match_with_levels( - feature_id, branch.name, hierarchy_regexes) - if level is None: - continue + repo_branches[branch.name] = { + 'sha': branch.commit.sha, + } + + genealogy = hierarchy.match_with_genealogy( + feature_id, repo_branches.keys(), hierarchy_regexes) + + for branch, parent in genealogy: + has_parent = None + in_parent = None + branch_data = repo_branches[branch] + + if parent: + for sha in [branch, parent]: + # Loop through all the commits for branch and parent if + # we haven't already done so and store them in the + # temporary ``repo_branches`` dict + if repo_branches[sha].get('commits') is None: + repo_branches[sha]['commits'] = [ + c.sha for c in repo.iter_commits(sha=sha) + ] + # Check if we're merged in + parent_data = repo_branches[parent] + has_parent = parent_data['sha'] in branch_data['commits'] + in_parent = branch_data['sha'] in parent_data['commits'] + branch_list.append({ - "repo_name": repo.name, - "branch_name": branch.name, - "latest_commit": branch.commit.sha, - "level": level, + "repository": repo.name, + "name": branch, + "commit_id": branch_data['sha'], + "parent_name": parent, + "has_parent": has_parent, + "in_parent": in_parent, }) return branch_list diff --git a/deploystream/providers/interfaces/source_code_control.py b/deploystream/providers/interfaces/source_code_control.py index d67df29..8f6c131 100644 --- a/deploystream/providers/interfaces/source_code_control.py +++ b/deploystream/providers/interfaces/source_code_control.py @@ -3,7 +3,7 @@ class ISourceCodeControlProvider(ProviderInterface): - def get_repo_branches_involved(feature_id): + def get_repo_branches_involved(feature_id, hierarchy_tree): """ Get the set of repo, branches involved in the given feature. @@ -18,19 +18,3 @@ def get_repo_branches_involved(feature_id): 2: latest commit """ pass - - def set_merged_status(repo_name, hierarchy_tree): - """ - Set the merged status of the given tree in the repo. - - :param repo_name: - The name of the repository to search for branches. - - :param hierarchy_tree: - A tree-like object. - - TODO: - more definition here... (traversing, etc) - - """ - pass diff --git a/tests/__init__.py b/tests/__init__.py index 4deb8f3..8788515 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -12,7 +12,7 @@ def load_fixture(filename): DEFAULT_HIERARCHY_REGEXES = [ 'master', 'develop', - 'story/{FEATURE_ID}(/[a-z]*)?', - 'dev/{FEATURE_ID}/[a-z]*', - '[a-zA-Z]*/{FEATURE_ID}/[a-zA-Z]*' + 'story/{FEATURE_ID}', + 'story/{FEATURE_ID}/[a-z]*', + '{PARENT}/[a-z]*' ] diff --git a/tests/integration/test_github.py b/tests/integration/test_github.py index a86fe29..744ef35 100644 --- a/tests/integration/test_github.py +++ b/tests/integration/test_github.py @@ -21,12 +21,18 @@ def test_get_repo_branches_involved(): assert_equal(2, len(branches)) assert_true({ - "repo_name": "dummyrepo", - "branch_name": "master", - "latest_commit": '0f6eefefc14f362a2c6f804df69aa83bac48c20b', - "level": 0} in branches) + "repository": "dummyrepo", + "name": "master", + "commit_id": '0f6eefefc14f362a2c6f804df69aa83bac48c20b', + "parent_name": None, + "has_parent": None, + "in_parent": None, + } in branches) assert_true({ - "repo_name": "dummyrepo", - "branch_name": "story/101/fred", - "latest_commit": "0f6eefefc14f362a2c6f804df69aa83bac48c20b", - "level": 2} in branches) + "repository": "dummyrepo", + "name": "story/101/fred", + "commit_id": "2f82934a1b47430af63df871b9155d8a977c6936", + "parent_name": "master", + "has_parent": True, + "in_parent": False + } in branches) diff --git a/tests/test_feature/test_models.py b/tests/test_feature/test_models.py new file mode 100644 index 0000000..8b5bd3e --- /dev/null +++ b/tests/test_feature/test_models.py @@ -0,0 +1,25 @@ +from nose.tools import assert_equals + +from deploystream.apps.feature.models import Feature, Branch + + +def test_feature_branches(): + f = Feature(provider=None, project=None, id=None, title=None) + for branch_name, parent_name in [('alex', 'master'), ('master', None), + ('something', 'alex'), ('sthg2', 'alex')]: + b = Branch(provider="test", + repository="repo1", + name=branch_name, + commit_id="commit", + parent_name=parent_name + ) + + f.add_branch(b) + + f.create_hierarchy_trees() + assert_equals(b.as_tree_string(), + "- master\n" + " - alex\n" + " - something\n" + " - sthg2\n" + ) diff --git a/tests/test_feature/test_view.py b/tests/test_feature/test_view.py index 363331d..4e7a902 100644 --- a/tests/test_feature/test_view.py +++ b/tests/test_feature/test_view.py @@ -1,5 +1,8 @@ from datetime import datetime +import json + from mock import patch +from nose.tools import assert_equal, assert_true import deploystream @@ -8,8 +11,21 @@ class SourceCodeProvider(object): name = 'source' oauth_token_name = None - def get_repo_branches_involved(self, feature_id, **kwargs): - return [('repo_01', "{0}_branch".format(feature_id), "232323")] + def get_repo_branches_involved(self, feature_id, hierarchy_tree, **kwargs): + return [ + { + 'repository': 'repo_01', + 'name': '{0}_branch'.format(feature_id), + 'parent_name': 'master', + 'commit_id': '222222', + }, + { + 'repository': 'repo_01', + 'name': 'master', + 'parent_name': None, + 'commit_id': '222223', + }, + ] def get_merged_status(self, repo_name, hierarchy_tree, **kwargs): return {} @@ -66,7 +82,20 @@ def setUp(self): 'testbuild': BuildInfoProvider}) def test_feature_view_shows_details(self): response = self.client.get('/features/plan/FT101') - assert "Amazing feature that will blow your mind" in response.data + feature_dict = json.loads(response.data) + assert_true("Amazing feature that will blow your mind" in + feature_dict['title']) + + ft101_branch_output = ( + feature_dict['branches']['repo_01']['FT101_branch']) + assert_equal(ft101_branch_output['parent_name'], 'master') + assert_equal(ft101_branch_output['children'], []) + + master_branch_output = ( + feature_dict['branches']['repo_01']['master']) + assert_equal(master_branch_output['parent_name'], None) + assert_equal(master_branch_output['children'][0]['name'], + "FT101_branch") @patch("deploystream.providers.ALL_PROVIDER_CLASSES", {'testplan': PlanningProvider, diff --git a/tests/test_lib/test_hierarchy.py b/tests/test_lib/test_hierarchy.py index cacd50f..5fe6460 100644 --- a/tests/test_lib/test_hierarchy.py +++ b/tests/test_lib/test_hierarchy.py @@ -1,24 +1,96 @@ -from nose.tools import assert_equals +from nose.tools import assert_items_equal -from deploystream.lib.hierarchy import match_with_levels -from tests import DEFAULT_HIERARCHY_REGEXES +from deploystream.lib.hierarchy import match_with_genealogy -def test_match_with_levels(): - "Test that we get back what we'd expect when matching branches" - branches_results = [ - ('master', 0), - ('develop', 1), - ('story/23', 2), - ('dev/23/alex', 3), - ('dev/23/carles', 3), +def test_genealogy(): + regexes = ['master', 'develop', 'story/{FEATURE_ID}', + 'story/{FEATURE_ID}/[a-z]*', + '{PARENT}/[a-z]*'] - ('somestory/234/carles', None), - ('story/234/carles', None), - ('story/45/alex', None), - ('dev/99/carles', None), + branches = ['master', 'develop', 'story/12', 'story/12/alex', + 'story/12/bill', + 'story/12/bill/something', + 'story/223/bill'] + + expected = [('master', None), + ('develop', 'master'), + ('story/12', 'develop'), + ('story/12/alex', 'story/12'), + ('story/12/bill', 'story/12'), + ('story/12/bill/something', 'story/12/bill')] + + results = match_with_genealogy(12, branches, regexes) + assert_items_equal(results, expected) + + +def test_genealogy_unordered_list(): + regexes = ['master', 'develop', 'story/{FEATURE_ID}', + 'story/{FEATURE_ID}/[a-z]*', + '{PARENT}/[a-z]*'] + + branches = ['story/12/bill', 'master', 'story/12/bill/something', + 'story/12', 'story/12/alex', 'develop'] + + expected = [('master', None), + ('develop', 'master'), + ('story/12', 'develop'), + ('story/12/alex', 'story/12'), + ('story/12/bill', 'story/12'), + ('story/12/bill/something', 'story/12/bill')] + + results = match_with_genealogy(12, branches, regexes) + results.sort() + assert_items_equal(results, expected) + + +def test_genealogy_missing_steps(): + regexes = ['master', 'develop', 'story/{FEATURE_ID}', + 'story/{FEATURE_ID}/[a-z]*', + '{PARENT}/[a-z]*'] + + branches = ['master', + 'story/12/bill'] + + expected = [('master', None), + ('story/12/bill', 'master')] + + results = match_with_genealogy(12, branches, regexes) + assert_items_equal(results, expected) + + +def test_gets_underscores(): + regexes = [ + 'master', + 'develop', + 'story/{FEATURE_ID}', + 'story/{FEATURE_ID}/[a-z_]*', + '{PARENT}[/_][a-z]*', + 'dev/{FEATURE_ID}/[a-z]*', + '{PARENT}[/_][a-z]*', + ] + + branches = [ + 'alex/19', + 'dev/70/alex', + 'dev/70/alex_hierarchy', + 'dev_alex', + 'develop', + 'master', + 'story/23/carles', + 'story/43/carles', + 'story/53/carles', + 'story/58/carles', + 'story/70/alex', + 'story/72/carles', + ] + expected = [ + ('master', None), + ('develop', 'master'), + ('story/70/alex', 'develop'), + ('dev/70/alex', 'story/70/alex'), + ('dev/70/alex_hierarchy', 'dev/70/alex'), ] - for branch, expected in branches_results: - result = match_with_levels('23', branch, DEFAULT_HIERARCHY_REGEXES) - assert_equals(result, expected) + results = match_with_genealogy(70, branches, regexes) + assert_items_equal(results, expected) diff --git a/tests/test_providers/test_git_provider.py b/tests/test_providers/test_git_provider.py index c38e765..99e010b 100644 --- a/tests/test_providers/test_git_provider.py +++ b/tests/test_providers/test_git_provider.py @@ -1,7 +1,7 @@ import os from os.path import join, exists, dirname -from nose.tools import assert_equal, with_setup +from nose.tools import assert_equal, assert_true, with_setup from deploystream.providers.git_provider import GitProvider @@ -33,27 +33,22 @@ def test_git_provider_finds_branches_across_repos(): Clone the dummyrepo into the data folder if not already there. The data in this test is found by looking at the dummyrepo and getting - the branch names and latest commit of any branches that match "FeAtUrE". + the branch names and latest commit of any branches that match "feature-99". """ - provider = GitProvider(code_dir=DUMMY_CODE_DIR, - feature_breakup_regex="(?P[a-zA-Z]+)-?(?P[0-9]+)", - branch_finder_template=".*(?i){project}.*") - branches = provider.get_repo_branches_involved('FeAtUrE-99') - - assert_equal([ - ('dummyrepo', 'my/feature_branch', - 'cf9130d3c07b061a88569153f10a7c7779338cfa'), - ], branches) - - -def test_git_provider_feature_breakup_regex(): - """ - Test that GitProvider breaks up feature ids into appropriate parts. - """ - provider = GitProvider( - feature_breakup_regex="(?P[a-zA-Z]+)-?(?P[0-9]+)") - for feature, expected in [ - ('DD-334', {'id': '334', 'project':'DD'}), - ('DD334', {'id': '334', 'project':'DD'}), - ]: - assert_equal(provider._get_feature_breakdown('DD-334'), expected) + provider = GitProvider(code_dir=DUMMY_CODE_DIR) + branches = provider.get_repo_branches_involved( + 'feature-99', + hierarchy_regexes=["master", "[a-z]*/{FEATURE_ID}"], + ) + + assert_equal(2, len(branches)) + assert_true({ + 'name': 'master', + 'commit_id': "0f6eefefc14f362a2c6f804df69aa83bac48c20b", + 'parent_name': None, + 'repository': 'dummyrepo'} in branches) + assert_true({ + 'name': 'my/feature-99', + 'commit_id': "7098fa31bf9663343c723d9d155c0dc6e6e28174", + 'parent_name': 'master', + 'repository': 'dummyrepo'} in branches) diff --git a/tests/test_providers/test_github_provider.py b/tests/test_providers/test_github_provider.py index f337802..9cddb95 100644 --- a/tests/test_providers/test_github_provider.py +++ b/tests/test_providers/test_github_provider.py @@ -11,6 +11,7 @@ def mock_github3(github3): mock_repo = Mock() mock_repo.has_issues = True mock_repo.name = 'repo_1' + mock_repo.iter_commits.return_value = [Mock(sha="CoMmItHaSh-MaStEr")] issue1 = { 'title': 'Hello', @@ -81,16 +82,22 @@ def test_implements_expected_interfaces(_): def test_get_repo_branches_involved(github3): mock_github3(github3) github_provider = GithubProvider('token') - branches = github_provider.get_repo_branches_involved(5, - hierarchy_regexes=DEFAULT_HIERARCHY_REGEXES) + branches = github_provider.get_repo_branches_involved("5", + DEFAULT_HIERARCHY_REGEXES) assert_equal(2, len(branches)) assert_true({ - "repo_name": "repo_1", - "branch_name": "master", - "latest_commit": 'CoMmItHaSh-MaStEr', - "level": 0} in branches) + "repository": "repo_1", + "name": "master", + "parent_name": None, + "commit_id": 'CoMmItHaSh-MaStEr', + "has_parent": None, + "in_parent": None, + } in branches) assert_true({ - "repo_name": "repo_1", - "branch_name": "story/5/alex", - "latest_commit": "CoMmItHaSh-5", - "level": 2} in branches) + "repository": "repo_1", + "name": "story/5/alex", + "parent_name": "master", + "commit_id": "CoMmItHaSh-5", + "has_parent": True, + "in_parent": False, + } in branches) diff --git a/tests/test_providers/test_interfaces.py b/tests/test_providers/test_interfaces.py index 60eb839..6bc6823 100644 --- a/tests/test_providers/test_interfaces.py +++ b/tests/test_providers/test_interfaces.py @@ -13,7 +13,7 @@ def test_implements_source_control_provider(self): class MyProvider(object): name = "provider" oauth_token_name = "oauth" - def get_repo_branches_involved(self, feature_id): + def get_repo_branches_involved(self, feature_id, hierarchy_regex): pass def set_merged_status(self, repo_name, hierarchy_tree):