-
Notifications
You must be signed in to change notification settings - Fork 0
Story/70/alex #73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Story/70/alex #73
Changes from all commits
59c5740
a46948a
1533294
05ca666
34bb155
89fabfa
1d7035d
78c4027
5719681
97f471e
98457ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import re | ||
|
|
||
|
|
||
| def create_single_regex(feature_id, 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. | ||
|
|
||
| :param feature_id: | ||
| The feature to filter the branch names on. | ||
|
|
||
| :param branches: | ||
| A list of branch names to filter. | ||
|
|
||
| :param 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. | ||
| """ | ||
| regex = create_single_regex(feature_id, hierarchical_regexes) | ||
|
|
||
| result = re.match(regex, branch) | ||
| if not result: | ||
| return None | ||
|
|
||
| for level, match in result.groupdict().items(): | ||
| if match: | ||
| index = int(level.split('level_')[1]) | ||
| return index |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,9 @@ | ||
| import github3 | ||
| import re | ||
| from zope import interface | ||
|
|
||
| from deploystream.providers.interfaces import IPlanningProvider | ||
| from deploystream.lib import transforms | ||
| from deploystream.lib import transforms, hierarchy | ||
|
|
||
|
|
||
| __all__ = ['GithubProvider'] | ||
|
|
@@ -25,21 +26,39 @@ class GithubProvider(object): | |
| name = 'github' | ||
| oauth_token_name = name | ||
|
|
||
| def __init__(self, token, organization=None, **kwargs): | ||
| def __init__(self, token, organization=None, repositories=None, **kwargs): | ||
| """ | ||
| Initialise the provider by giving it GitHub credentials and repos. | ||
|
|
||
| :param organization: | ||
| The name of the organization who's repository issues should be | ||
| identified in GitHub. If ``None`` then the authenticated | ||
| user's issues will be tracked. | ||
| identified in GitHub. If ``None`` and no ``repositories`` given, | ||
| then the authenticated user's issues will be tracked. | ||
|
|
||
| :param repositories: | ||
| A list of tuples containing (<owner>, <name>) that identify | ||
| a repository in GitHub. This is only looked at if ``organization`` | ||
| is ``None``. | ||
| """ | ||
| self.github = github3.login(token=token) | ||
| if not organization: | ||
| self.repositories = list(self.github.iter_repos()) | ||
|
|
||
| if token is None and "username" in kwargs and "password" in kwargs: | ||
| # We can login using username and password for testing purposes | ||
| self.github = github3.login( | ||
| kwargs['username'], | ||
| password=kwargs['password'] | ||
| ) | ||
| else: | ||
| self.github = github3.login(token=token) | ||
|
|
||
| if organization: | ||
| org = self.github.organization(organization) | ||
| self.repositories = list(org.iter_repos()) | ||
| elif repositories: | ||
| self.repositories = [] | ||
| for owner, repo in repositories: | ||
| self.repositories.append(self.github.repository(owner, repo)) | ||
| else: | ||
| self.repositories = list(self.github.iter_repos()) | ||
|
|
||
| def get_features(self, **filters): | ||
| """ | ||
|
|
@@ -85,3 +104,21 @@ def get_oauth_data(self): | |
| 'scope': 'repo' | ||
| }, | ||
| } | ||
|
|
||
| def get_repo_branches_involved(self, feature_id, hierarchy_regexes): | ||
| branch_list = [] | ||
|
|
||
| for repo in self.repositories: | ||
| for branch in repo.iter_branches(): | ||
| level = hierarchy.match_with_levels( | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think about this? It goes against the philosophy that I've been adopting to keep providers as dumb as possible. An alternative would be that the provider defines a The second approach would make test writing a lot easier too. Which way do you prefer?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I'm going to take the view that each source code provider is responsible for it's own level producing - and that they may have different ways of calculating that - so therefore leaving the call to
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK I haven't given this enough thought TBH. |
||
| feature_id, branch.name, hierarchy_regexes) | ||
| if level is None: | ||
| continue | ||
| branch_list.append({ | ||
| "repo_name": repo.name, | ||
| "branch_name": branch.name, | ||
| "latest_commit": branch.commit.sha, | ||
| "level": level, | ||
| }) | ||
|
|
||
| return branch_list | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| from mock import Mock, patch | ||
| from nose.tools import assert_equal, assert_true | ||
|
|
||
| from deploystream.providers.github import GithubProvider | ||
| from deploystream.providers.interfaces import ( | ||
| IPlanningProvider, IOAuthProvider, is_implementation) | ||
| from tests import DEFAULT_HIERARCHY_REGEXES | ||
| from deploystream import app | ||
|
|
||
|
|
||
| def test_get_repo_branches_involved(): | ||
| "Test ``get_repo_branches_involved`` using ``pretenders/dummyrepo`` repo." | ||
| github_provider = GithubProvider( | ||
| token=None, | ||
| username=app.config['GITHUB_CONFIG']['username'], | ||
| password=app.config['GITHUB_CONFIG']['password'], | ||
| repositories=[('pretenders', 'dummyrepo')] | ||
| ) | ||
| branches = github_provider.get_repo_branches_involved(101, | ||
| hierarchy_regexes=DEFAULT_HIERARCHY_REGEXES) | ||
|
|
||
| assert_equal(2, len(branches)) | ||
| assert_true({ | ||
| "repo_name": "dummyrepo", | ||
| "branch_name": "master", | ||
| "latest_commit": '0f6eefefc14f362a2c6f804df69aa83bac48c20b', | ||
| "level": 0} in branches) | ||
| assert_true({ | ||
| "repo_name": "dummyrepo", | ||
| "branch_name": "story/101/fred", | ||
| "latest_commit": "0f6eefefc14f362a2c6f804df69aa83bac48c20b", | ||
| "level": 2} in branches) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| #!/usr/bin/env python | ||
| #-*- coding: utf-8 -*- | ||
| from mock import Mock | ||
| from nose.tools import assert_equals | ||
|
|
||
| from deploystream.providers.interfaces import IPlanningProvider | ||
| from deploystream.apps.feature.lib import get_all_features | ||
|
|
||
| NON_ASCII_STRING = u"都بيببيðéáöþ" | ||
|
|
||
|
|
||
| def test_non_ascii_chars(): | ||
| mock_provider = Mock() | ||
| mock_provider.get_features.return_value = [{ | ||
| "project": NON_ASCII_STRING, | ||
| "id": NON_ASCII_STRING, | ||
| 'title': NON_ASCII_STRING | ||
| }] | ||
|
|
||
| resp = get_all_features({IPlanningProvider: [mock_provider]}) | ||
|
|
||
| assert_equals(resp[0].title, NON_ASCII_STRING) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting not to need git clones for this.
I've been thinking of the difficulties of eventually combining git/github providers. Won't be easy to come up with a cleanly layered solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(or even just matching Jira/Sprintly projects to actual collections of repos in e.g. Github).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As long as we treat github repos as an independent source from the local git we should be fine. (Where independence means they have their own hierarchy trees etc)