Skip to content

Commit 7e70459

Browse files
committed
Added feature: automagical configuration of the tool
- handles getting the authorization token from the service - adds the alias in the configuration
1 parent c407c17 commit 7e70459

File tree

7 files changed

+134
-1
lines changed

7 files changed

+134
-1
lines changed

git_repo/repo.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
{self} [--path=<path>] [-v...] <target> gist fetch <gist> [<gist_file>]
2626
{self} [--path=<path>] [-v...] <target> gist create [--secret] <description> [<gist_path> <gist_path>...]
2727
{self} [--path=<path>] [-v...] <target> gist delete <gist> [-f]
28+
{self} [--path=<path>] [-v...] <target> config [--config=<gitconfig>]
29+
{self} [-v...] config [--config=<gitconfig>]
2830
{self} --help
2931
3032
Tool for managing remote repository services.
@@ -38,6 +40,7 @@
3840
gist Manages gist files
3941
request Handles requests for merge
4042
open Open the given or current repository in a browser
43+
config Run authentication process and configure the tool
4144
4245
Options:
4346
<user>/<repo> Repository to work with
@@ -239,6 +242,10 @@ def set_name(self, name):
239242
def set_gist_ref(self, gist):
240243
self.gist_ref = gist
241244

245+
@store_parameter('--config')
246+
def store_gitconfig(self, val):
247+
self.config = val or os.path.join(os.environ['HOME'], '.gitconfig')
248+
242249
'''Actions'''
243250

244251
@register_action('add')
@@ -410,6 +417,58 @@ def do_gist_delete(self):
410417
log.info('Successfully deleted gist!')
411418
return 0
412419

420+
@register_action('config')
421+
def do_config(self):
422+
from getpass import getpass
423+
424+
def loop_input(*args, method=input, **kwarg):
425+
out = ''
426+
while len(out) == 0:
427+
out = method(*args, **kwarg)
428+
return out
429+
430+
def setup_service(service):
431+
conf = service.get_config(self.config)
432+
if 'token' in conf:
433+
raise Exception('A token has been generated for this service. Please revoke and delete before proceeding.')
434+
435+
print('Please enter your credentials to connect to the service:')
436+
username = loop_input('username> ')
437+
password = loop_input('password> ', method=getpass)
438+
439+
token = service.get_auth_token(username, password)
440+
print('Great! You\'ve been identified 🍻')
441+
442+
print('Do you want to give a custom name for this service\'s remote?')
443+
if 'y' in input(' [yN]> ').lower():
444+
print('Enter the remote\'s name:')
445+
loop_input('[{}]> '.format(service.name))
446+
447+
print('Do you want to configure a git alias?')
448+
print('N.B.: instead of typing `git repo {0}` you\'ll be able to type `git {0}`'.format(service.command))
449+
if 'n' in input(' [Yn]> ').lower():
450+
set_alias = False
451+
else:
452+
set_alias = True
453+
454+
service.store_config(self.config, token=token)
455+
if set_alias:
456+
service.set_alias(self.config)
457+
458+
if self.target:
459+
services = [self.get_service(lookup_repository=False)]
460+
else:
461+
services = RepositoryService.service_map.values()
462+
463+
for service in services:
464+
print('Do you want to configure the {} service?'.format(service.name))
465+
if 'n' in input(' [Yn]> ').lower():
466+
continue
467+
setup_service(service)
468+
469+
print('🍻 The configuration is done!')
470+
return 0
471+
413472

414473
def main(args):
415474
try:

git_repo/services/ext/bitbucket.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@ def get_repository(self, user, repo):
154154
return r
155155
#raise ResourceNotFoundError('Cannot retrieve repository: {}/{} does not exists.'.format(user, repo))
156156

157+
@classmethod
158+
def get_auth_token(cls, login, password):
159+
log.warn("/!\\ Due to API limitations, the bitbucket login/password is stored as plaintext in configuration.")
160+
return "{}:{}".format(login, password)
161+
157162
@property
158163
def user(self):
159164
ret, user = self.bb.get_user()

git_repo/services/ext/github.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,15 @@ def request_fetch(self, user, repo, request, pull=False):
202202
raise ResourceNotFoundError('Could not find opened request #{}'.format(request)) from err
203203
raise err
204204

205+
@classmethod
206+
def get_auth_token(cls, login, password):
207+
import platform
208+
auth = github3.GitHub().authorize(login, password,
209+
scopes=[ 'repo', 'delete_repo', 'gist' ],
210+
note='git-repo token used on {}'.format(platform.node()),
211+
note_url='https://github.com/guyzmo/git-repo')
212+
return auth.token
213+
205214
@property
206215
def user(self):
207216
return self.gh.user().name

git_repo/services/ext/gitlab.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ def get_repository(self, user, repo):
7777
if err.response_code == 404:
7878
raise ResourceNotFoundError("Cannot delete: repository {}/{} does not exists.".format(user, repo)) from err
7979

80+
@classmethod
81+
def get_auth_token(cls, login, password):
82+
gl = gitlab.Gitlab(url='https://{}'.format(cls.fqdn), email=login, password=password)
83+
gl.auth()
84+
return gl.user.private_token
85+
8086
@property
8187
def user(self):
8288
return self.gl.user.username

git_repo/services/service.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,33 @@ class RepositoryService:
5454
# this symbol is made available for testing purposes
5555
_current = None
5656

57+
config_options = ['type', 'token', 'alias', 'fqdn']
58+
59+
@classmethod
60+
def get_config(cls, config):
61+
out = {}
62+
with git_config.GitConfigParser(config, read_only=True) as config:
63+
section = 'gitrepo "{}"'.format(cls.name)
64+
if config.has_section(section):
65+
for option in cls.config_options:
66+
if config.has_option(section, option):
67+
out[option] = config.get(section, option)
68+
return out
69+
70+
@classmethod
71+
def store_config(cls, config, **kwarg):
72+
with git_config.GitConfigParser(config, read_only=False) as config:
73+
section = 'gitrepo "{}"'.format(cls.name)
74+
for option, value in kwarg.items():
75+
if option not in cls.config_options:
76+
raise ArgumentError('Option {} is invalid and cannot be setup.')
77+
config.set_value(section, option, value)
78+
79+
@classmethod
80+
def set_alias(cls, config):
81+
with git_config.GitConfigParser(config, read_only=False) as config:
82+
config.set_value('alias', cls.command, 'repo {}'.format(cls.command))
83+
5784
@classmethod
5885
def get_service(cls, repository, command):
5986
'''Accessor for a repository given a command
@@ -94,6 +121,10 @@ def get_service(cls, repository, command):
94121
cls._current = service(repository, config)
95122
return cls._current
96123

124+
@classmethod
125+
def get_auth_token(cls, login, password):
126+
raise NotImplementedError
127+
97128
def __init__(self, r=None, c=None):
98129
'''
99130
:param r: git-python repository instance

tests/helpers.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from git_repo.repo import RepositoryService, main
1616

1717
class RepositoryMockup(RepositoryService):
18+
name = 'test_name'
19+
command = 'test_command'
1820
fqdn = 'http://example.org'
1921
def __init__(self, *args, **kwarg):
2022
super(RepositoryMockup, self).__init__(*args, **kwarg)
@@ -117,6 +119,10 @@ def request_create(self, *args, **kwarg):
117119
raise Exception('bad branch to request!')
118120
return 42
119121

122+
@classmethod
123+
def get_auth_token(cls, login, password):
124+
return '{}:{}'.format(login, password)
125+
120126
@property
121127
def user(self):
122128
self._did_user = True
@@ -315,6 +321,14 @@ def main_open(self, repo=None, rc=0, args={}):
315321
}, args)), "Non {} result for open".format(rc)
316322
return RepositoryService._current._did_open
317323

324+
def main_config(self, target, rc=0, args={}):
325+
assert rc == main(self.setup_args({
326+
'config': True,
327+
'--config': os.path.join(self.tempdir.name, 'gitconfig')
328+
}, args)), "Non {} result for config".format(rc)
329+
with open(os.path.join(self.tempdir.name, 'gitconfig')) as f:
330+
return f.readlines()
331+
318332
def main_noop(self, repo, rc=1, args={}):
319333
assert rc == main(self.setup_args({
320334
'<user>/<repo>': repo,
@@ -696,4 +710,3 @@ def action_open(self, cassette_name, namespace, repository):
696710
with Replace('subprocess.Popen', self.Popen):
697711
self.service.open(user=namespace, repo=repository)
698712

699-

tests/integration/test_main.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,16 @@ def test_request_create__no_repo_slug(self, capsys, caplog):
474474
assert out == ''
475475
assert 'Successfully created request of `pr-test` onto `guyzmo/git-repo`, with id `42`!' in caplog.text
476476

477+
def test_config(self, capsys, caplog):
478+
import sys, io, getpass
479+
getpass.getpass = input
480+
sys.stdin = io.StringIO('\n'.join(['y', 'user', 'pass', 'y', 'fubar', 'y']))
481+
#
482+
conf = self.main_config(target='hub', rc=0)
483+
assert ['[gitrepo "github"]\n',
484+
'\ttoken = user:pass\n',
485+
'[alias]\n',
486+
'\ttest_command = repo test_command\n'] == conf
477487

478488
def test_z_noop(self):
479489
self.main_noop('guyzmo/git-repo', 1)

0 commit comments

Comments
 (0)