Skip to content

Commit 2d1dd1f

Browse files
committed
👍 Merge branch 'devel' ; 🔖 Version bump to 1.7.4
Features * Added group support for the command: git lab create (fixes #52) * Added organization support for the command: git hub create * Added github two factor authentication support (fixes #39) Bugfixes * Fix open command check on OSX (fixes #47) * Fix of the tests configuration (fixes #53) * Made the git config keep the same order (fixes #48) Signed-off-by: Guyzmo <[email protected]>
2 parents f9653cb + 985a4ee commit 2d1dd1f

File tree

46 files changed

+1275
-472
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1275
-472
lines changed

.travis.yml

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,40 @@
11
language: python
2-
python:
3-
- "3.4"
4-
- "3.5"
5-
- "3.5-dev" # 3.5 development branch
2+
matrix:
3+
include:
4+
- os: linux
5+
python: "3.4"
6+
- os: linux
7+
python: "3.5"
8+
- os: linux
9+
python: "3.5-dev"
10+
- os: linux
11+
python: "3.6-dev"
12+
- os: linux
13+
python: "nightly"
14+
- os: linux
15+
python: "pypy3"
16+
17+
- os: osx
18+
sudo: required
19+
language: generic
20+
21+
allow_failures:
22+
- python: "3.6-dev"
23+
- python: "nightly"
24+
- python: "pypy3"
25+
- os: "osx"
626
addons:
727
apt:
828
packages:
929
- pandoc
30+
before_install: |
31+
if [[ $TRAVIS_OS_NAME == 'osx' ]]; then
32+
brew update;
33+
brew install python3 pandoc;
34+
python3 -m venv venv;
35+
source venv/bin/activate;
36+
pip install . test
37+
fi
1038
# command to install dependencies
1139
install:
1240
- "pip install --upgrade pip" # upgrade to latest pip (needed on py3.4)

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ so that you can push your code to all your remote repositories in one command:
119119

120120
### Installation
121121

122-
You can get the tool using pypi:
122+
You can get the tool using pypi (use `pip3` if you have both Python2 and Python3 installed):
123123

124124
% pip install git-repo
125125

@@ -276,10 +276,11 @@ With code contributions coming from:
276276

277277
* [@guyhughes](https://github.com/guyhughes) [commits](https://github.com/guyzmo/git-repo/commits?author=guyhughes)
278278
* [@buaazp](https://github.com/buaazp) [commits](https://github.com/guyzmo/git-repo/commits?author=buaazp)
279+
* [@peterazmanov](https://github.com/peterazmanov) [commits](https://github.com/guyzmo/git-repo/commits?author=peterazmanov)
279280

280281
### License
281282

282-
Copyright ©
283+
Copyright ©2016 Bernard `Guyzmo` Pratz <[email protected]>
283284

284285
This program is free software; you can redistribute it and/or
285286
modify it under the terms of the GNU General Public License

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.7.3
1+
1.7.4

git_repo/repo.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -242,14 +242,6 @@ def set_repo_slug(self, repo_slug):
242242
self.user_name = None
243243
self.repo_name = self.repo_slug
244244

245-
@store_parameter('<branch>')
246-
def set_branch(self, branch):
247-
# FIXME workaround for default value that is not correctly parsed in docopt
248-
if branch == None:
249-
branch = 'master'
250-
251-
self.branch = branch
252-
253245
@store_parameter('<branch>')
254246
@store_parameter('--branch')
255247
def set_branch(self, branch):
@@ -491,7 +483,7 @@ def setup_service(service):
491483
username = loop_input('username> ')
492484
password = loop_input('password> ', method=getpass)
493485

494-
token = service.get_auth_token(username, password)
486+
token = service.get_auth_token(username, password, prompt=loop_input)
495487
print('Great! You\'ve been identified 🍻')
496488

497489
print('Do you want to give a custom name for this service\'s remote?')
@@ -515,7 +507,7 @@ def setup_service(service):
515507
else:
516508
services = RepositoryService.service_map.values()
517509

518-
for service in services:
510+
for service in sorted(services, key=lambda s: s.name):
519511
print('Do you want to configure the {} service?'.format(service.name))
520512
if 'n' in input(' [Yn]> ').lower():
521513
continue

git_repo/services/ext/bitbucket.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ def get_repository(self, user, repo):
146146
#raise ResourceNotFoundError('Cannot retrieve repository: {}/{} does not exists.'.format(user, repo))
147147

148148
@classmethod
149-
def get_auth_token(cls, login, password):
149+
def get_auth_token(cls, login, password, prompt=None):
150150
log.warn("/!\\ Due to API limitations, the bitbucket login/password is stored as plaintext in configuration.")
151151
return "{}:{}".format(login, password)
152152

git_repo/services/ext/github.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,15 @@ def connect(self):
3333
'Check your configuration and try again.') from err
3434

3535
def create(self, user, repo, add=False):
36-
if user != self.username:
37-
raise NotImplementedError("Project creation supported for authentified user only!")
3836
try:
39-
self.gh.create_repo(repo)
37+
if user != self.username:
38+
org = self.gh.organization(user)
39+
if org:
40+
org.create_repo(repo)
41+
else:
42+
raise ResourceNotFoundError("Namespace {} neither an organization or current user.".format(user))
43+
else:
44+
self.gh.create_repo(repo)
4045
except github3.models.GitHubError as err:
4146
if err.code == 422 or err.message == 'name already exists on this account':
4247
raise ResourceExistsError("Project already exists.") from err
@@ -194,13 +199,21 @@ def request_fetch(self, user, repo, request, pull=False):
194199
raise err
195200

196201
@classmethod
197-
def get_auth_token(cls, login, password):
202+
def get_auth_token(cls, login, password, prompt=None):
198203
import platform
199-
auth = github3.GitHub().authorize(login, password,
200-
scopes=[ 'repo', 'delete_repo', 'gist' ],
201-
note='git-repo token used on {}'.format(platform.node()),
202-
note_url='https://github.com/guyzmo/git-repo')
203-
return auth.token
204+
gh = github3.GitHub()
205+
gh.login(login, password, two_factor_callback=lambda: prompt('2FA code> '))
206+
try:
207+
auth = gh.authorize(login, password,
208+
scopes=[ 'repo', 'delete_repo', 'gist' ],
209+
note='git-repo2 token used on {}'.format(platform.node()),
210+
note_url='https://github.com/guyzmo/git-repo')
211+
return auth.token
212+
except github3.models.GitHubError as err:
213+
if len(err.args) > 0 and 422 == err.args[0].status_code:
214+
raise ResourceExistsError("A token already exist for this machine on your github account.")
215+
else:
216+
raise err
204217

205218
@property
206219
def user(self):

git_repo/services/ext/gitlab.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ def connect(self):
2727

2828
def create(self, user, repo, add=False):
2929
try:
30-
self.gl.projects.create(data={
31-
'name': repo,
32-
# 'namespace_id': user, # TODO does not work, cannot create on
33-
# another namespace yet
34-
})
30+
group = self.gl.groups.search(user)
31+
data = {'name': repo}
32+
if group:
33+
data['namespace_id'] = group[0].id
34+
self.gl.projects.create(data=data)
3535
except GitlabCreateError as err:
3636
if json.loads(err.response_body.decode('utf-8'))['message']['name'][0] == 'has already been taken':
3737
raise ResourceExistsError("Project already exists.") from err
@@ -74,7 +74,7 @@ def get_repository(self, user, repo):
7474
raise ResourceNotFoundError("Cannot delete: repository {}/{} does not exists.".format(user, repo)) from err
7575

7676
@classmethod
77-
def get_auth_token(cls, login, password):
77+
def get_auth_token(cls, login, password, prompt=None):
7878
gl = gitlab.Gitlab(url='https://{}'.format(cls.fqdn), email=login, password=password)
7979
gl.auth()
8080
return gl.user.private_token

git_repo/services/service.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
'''select open command'''
1717

18-
if 'mac' in sys.platform: #pragma: no cover
18+
if 'darwin' in sys.platform: #pragma: no cover
1919
OPEN_COMMAND = 'open'
2020
else: #pragma: no cover
2121
OPEN_COMMAND = 'xdg-open'
@@ -122,7 +122,7 @@ def get_service(cls, repository, command):
122122
return cls._current
123123

124124
@classmethod
125-
def get_auth_token(cls, login, password):
125+
def get_auth_token(cls, login, password, prompt=None):
126126
raise NotImplementedError
127127

128128
def __init__(self, r=None, c=None):

tests/conftest.py

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,49 +7,54 @@
77

88
record_mode = 'once'
99

10+
from git_repo.services.service import RepositoryService
11+
services = list(RepositoryService.service_map.keys())
12+
1013
if os.environ.get('TRAVIS_GH3'):
1114
# create default bogus values for tokens and namespaces if missing for pytest
1215
# to run without environment values
1316
# also if an environment variable is not set, then we don't want to record cassettes
1417
record_mode = 'never'
15-
for service_name in ('github', 'gitlab', 'bitbucket'):
16-
token_name = 'PRIVATE_KEY_{}'.format(service_name.upper())
17-
namespace_name = '{}_NAMESPACE'.format(service_name.upper())
18+
for service in services:
19+
token_name = 'PRIVATE_KEY_{}'.format(service.upper())
20+
namespace_name = '{}_NAMESPACE'.format(service.upper())
1821
if token_name not in os.environ:
19-
os.environ[token_name] = 'not_configured:test' # using a : for bitbucket's case
22+
os.environ[token_name] = '_namespace_{}_:_private_'.format(service) # using a : for bitbucket's case
2023
if namespace_name not in os.environ:
21-
os.environ[namespace_name] = 'not_configured'
24+
os.environ[namespace_name] = '_namespace_{}_'.format(service)
2225
else:
26+
# if running tests "locally" and not in travis, let's try to extract the keys from
27+
# the local configuration if there is some local configuration. And exposes them as
28+
# environment variables.
2329
import git, getpass
2430
config = git.config.GitConfigParser(os.path.join(os.environ['HOME'], '.gitconfig'))
25-
conf_section = list(filter(lambda n: 'gitrepo' in n, config.sections()))
26-
key_dict = {section.split('"')[1] :config.get_value(section, 'privatekey') for section in conf_section}
27-
for service, key in key_dict.items():
31+
32+
# handle the different forms of token configuration item (yup, technical debt bites here)
33+
get_section = lambda s: 'gitrepo "{}"'.format(s)
34+
get_token = lambda s: config.get_value(get_section(s), 'token',
35+
config.get_value(get_section(s), 'private_token',
36+
config.get_value(get_section(s), 'privatekey',
37+
'_namespace_{}_:_private_'.format(s) # using a : for bitbucket's case
38+
)))
39+
# XXX temporary fix that should not be necessary when refactoring with pybitbucket
40+
get_default_namespace = lambda s: os.environ[token_name].split(':')[0] if s == 'bitbucket' else '_namespace_{}_'.format(s)
41+
42+
for service in services:
2843
token_name = 'PRIVATE_KEY_{}'.format(service.upper())
2944
namespace_name = '{}_NAMESPACE'.format(service.upper())
30-
os.environ[token_name] = key
31-
os.environ[namespace_name] = os.environ.get('GITREPO_NAMESPACE', getpass.getuser())
32-
33-
api_token_github = os.environ['PRIVATE_KEY_GITHUB']
34-
api_token_gitlab = os.environ['PRIVATE_KEY_GITLAB']
35-
api_token_bitbucket = os.environ['PRIVATE_KEY_BITBUCKET']
36-
37-
github_namespace = os.environ['GITHUB_NAMESPACE']
38-
gitlab_namespace = os.environ['GITLAB_NAMESPACE']
39-
bitbucket_namespace = os.environ['BITBUCKET_NAMESPACE']
45+
if token_name not in os.environ:
46+
os.environ[token_name] = get_token(service)
47+
if namespace_name not in os.environ:
48+
os.environ[namespace_name] = os.environ.get('GITREPO_NAMESPACE', get_default_namespace(service))
4049

4150
betamax.Betamax.register_serializer(pretty_json.PrettyJSONSerializer)
4251

4352
with betamax.Betamax.configure() as config:
4453
config.default_cassette_options['record_mode'] = record_mode
4554
config.cassette_library_dir = 'tests/integration/cassettes'
4655
config.default_cassette_options['serialize_with'] = 'prettyjson'
47-
config.define_cassette_placeholder('<PRIVATE_KEY_GITHUB>', api_token_github)
48-
config.define_cassette_placeholder('<PRIVATE_KEY_GITLAB>', api_token_gitlab)
49-
config.define_cassette_placeholder('<PRIVATE_KEY_BITBUCKET>', api_token_bitbucket)
50-
config.define_cassette_placeholder('<GITHUB_NAMESPACE>', github_namespace)
51-
config.define_cassette_placeholder('<GITLAB_NAMESPACE>', gitlab_namespace)
52-
config.define_cassette_placeholder('<BITBUCKET_NAMESPACE>', bitbucket_namespace)
53-
54-
56+
# generating placeholders in betamax configuration for each service's key and default namespace
57+
for service in services:
58+
config.define_cassette_placeholder('<PRIVATE_KEY_{}>'.format(service.upper()), os.environ.get('PRIVATE_KEY_{}'.format(service.upper())))
59+
config.define_cassette_placeholder('<{}_NAMESPACE>'.format(service.upper()), os.environ.get('{}_NAMESPACE'.format(service.upper())))
5560

0 commit comments

Comments
 (0)