Skip to content

Commit a66405e

Browse files
committed
Merge branch 'devel'; Version bump to 1.10.3
This release is a bit more love for gitlab ♥ Contributors Thanks to @AmandaCameron for the gitlab subgroup support 🙌 and to @fa7ad for the webbrowser module integration 👍 🚧 Features * gitlab subgroup support * webbrowser module support * updated to support python-gitlab >1.0 🚒 Bugfixes * unbounds the listings for gitlab
2 parents c8e452d + 3c2a665 commit a66405e

13 files changed

+354
-76
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@
1212
* [![Issues in Ready](https://badge.waffle.io/guyzmo/git-repo.png?label=ready&title=Ready)](https://waffle.io/guyzmo/git-repo) [![Issues in Progress](https://badge.waffle.io/guyzmo/git-repo.png?label=in%20progress&title=Progress)](https://waffle.io/guyzmo/git-repo) [![Show Travis Build Status](https://travis-ci.org/guyzmo/git-repo.svg)](https://travis-ci.org/guyzmo/git-repo)
1313
* [![Pypi Version](https://img.shields.io/pypi/v/git-repo.svg) ![Pypi Downloads](https://img.shields.io/pypi/dm/git-repo.svg)](https://pypi.python.org/pypi/git-repo)
1414

15+
## Looking for help
16+
17+
For the past few months I've been really busy coding on stuff that puts food on the table…
18+
And sadly, I cannot give this project all the love it deserves. Which is why it's taken me months
19+
to spend a few hours merge and release the PRs featured in this repository.
20+
21+
I'm still using this project daily, but I'm not having enough time to keep on putting all the
22+
effort needed to make it shine (SSH keys, issues support…)
23+
24+
So I'd like to share the maintenance responsibility with someone or more people. If you're
25+
interested, please ping me on IRC or by mail (which is in all my commits). I'm always happy
26+
to guide through the code's design!
27+
1528
### Usage
1629

1730
#### main commands
@@ -380,6 +393,8 @@ With code contributions coming from:
380393
* [@rnestler](https://github.com/rnestler)[commits](https://github.com/guyzmo/git-repo/commits/devel?author=rnestler)
381394
* [@jayvdb](https://github.com/jayvdb)[commits](https://github.com/guyzmo/git-repo/commits/devel?author=jayvdb)
382395
* [@kounoike](https://github.com/kounoike)[commits](https://github.com/guyzmo/git-repo/commits/devel?author=kounoike)
396+
* [@AmandaCameron](https://github.com/AmandaCameron)[commits](https://github.com/guyzmo/git-repo/commits/devel?author=AmandaCameron)
397+
* [@fa7ad](https://github.com/fa7ad)[commits](https://github.com/guyzmo/git-repo/commits/devel?author=fa7ad)
383398

384399
### License
385400

git_repo/repo.py

Lines changed: 51 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,24 @@
77
{self} [--path=<path>] [-v...] <target> delete [-f]
88
{self} [--path=<path>] [-v...] <target> open
99
{self} [--path=<path>] [-v...] <target> (list|ls) [-l] <user>
10-
{self} [--path=<path>] [-v...] <target> fork <user>/<repo> [--branch=<branch>]
11-
{self} [--path=<path>] [-v...] <target> fork <user>/<repo> <repo> [--branch=<branch>]
12-
{self} [--path=<path>] [-v...] <target> create <user>/<repo> [--add]
13-
{self} [--path=<path>] [-v...] <target> delete <user>/<repo> [-f]
14-
{self} [--path=<path>] [-v...] <target> open <user>/<repo>
15-
{self} [--path=<path>] [-v...] <target> clone <user>/<repo> [<repo> [<branch>]]
10+
{self} [--path=<path>] [-v...] <target> fork <namespace>/<repo> [--branch=<branch>]
11+
{self} [--path=<path>] [-v...] <target> fork <namespace>/<repo> <repo> [--branch=<branch>]
12+
{self} [--path=<path>] [-v...] <target> create <namespace>/<repo> [--add]
13+
{self} [--path=<path>] [-v...] <target> delete <namespace>/<repo> [-f]
14+
{self} [--path=<path>] [-v...] <target> open <namespace>/<repo>
15+
{self} [--path=<path>] [-v...] <target> clone <namespace>/<repo> [<repo> [<branch>]]
1616
{self} [--path=<path>] [-v...] <target> add
17-
{self} [--path=<path>] [-v...] <target> add <user>/<repo> [<name>] [--tracking=<branch>] [-a]
17+
{self} [--path=<path>] [-v...] <target> add <namespace>/<repo> [<name>] [--tracking=<branch>] [-a]
1818
{self} [--path=<path>] [-v...] <target> request (list|ls)
1919
{self} [--path=<path>] [-v...] <target> request fetch <request> [-f]
2020
{self} [--path=<path>] [-v...] <target> request create [--title=<title>] [--message=<message>]
2121
{self} [--path=<path>] [-v...] <target> request create <local_branch> [--title=<title>] [--message=<message>]
2222
{self} [--path=<path>] [-v...] <target> request create <remote_branch> <local_branch> [--title=<title>] [--message=<message>]
23-
{self} [--path=<path>] [-v...] <target> request <user>/<repo> (list|ls)
24-
{self} [--path=<path>] [-v...] <target> request <user>/<repo> fetch <request> [-f]
25-
{self} [--path=<path>] [-v...] <target> request <user>/<repo> create [--title=<title>] [--branch=<remote>] [--message=<message>]
26-
{self} [--path=<path>] [-v...] <target> request <user>/<repo> create <local_branch> [--title=<title>] [--branch=<remote>] [--message=<message>]
27-
{self} [--path=<path>] [-v...] <target> request <user>/<repo> create <remote_branch> <local_branch> [--title=<title>] [--branch=<remote>] [--message=<message>]
23+
{self} [--path=<path>] [-v...] <target> request <namespace>/<repo> (list|ls)
24+
{self} [--path=<path>] [-v...] <target> request <namespace>/<repo> fetch <request> [-f]
25+
{self} [--path=<path>] [-v...] <target> request <namespace>/<repo> create [--title=<title>] [--branch=<remote>] [--message=<message>]
26+
{self} [--path=<path>] [-v...] <target> request <namespace>/<repo> create <local_branch> [--title=<title>] [--branch=<remote>] [--message=<message>]
27+
{self} [--path=<path>] [-v...] <target> request <namespace>/<repo> create <remote_branch> <local_branch> [--title=<title>] [--branch=<remote>] [--message=<message>]
2828
{self} [--path=<path>] [-v...] <target> (gist|snippet) (list|ls) [<gist>]
2929
{self} [--path=<path>] [-v...] <target> (gist|snippet) clone <gist>
3030
{self} [--path=<path>] [-v...] <target> (gist|snippet) fetch <gist> [<gist_file>]
@@ -49,13 +49,13 @@
4949
config Run authentication process and configure the tool
5050
5151
Options:
52-
<user>/<repo> Repository to work with
52+
<namespace>/<repo> Repository to work with
5353
-p,--path=<path> Path to work on [default: .]
5454
-v,--verbose Makes it more chatty (repeat twice to see git commands)
5555
-h,--help Shows this message
5656
5757
Options for list:
58-
<user> Name of the user whose repositories will be listed
58+
<namespace> Name of the user whose repositories will be listed
5959
-l,--long Show one repository per line, when set show the results
6060
with the following columns:
6161
STATUS, COMMITS, REQUESTS, ISSUES, FORKS, CONTRIBUTORS, WATCHERS, LIKES, LANGUAGE, MODIF, NAME
@@ -116,6 +116,7 @@
116116
'''
117117

118118
from docopt import docopt
119+
from getpass import getpass
119120

120121
import os
121122
import sys
@@ -131,27 +132,22 @@
131132
log_root = logging.getLogger()
132133
log = logging.getLogger('git_repo')
133134

134-
if sys.version_info.major < 3: # pragma: no cover
135+
if sys.version_info.major < 3: # pragma: no cover
135136
print('Please use with python version 3')
136137
sys.exit(1)
137138

138139
from .exceptions import ArgumentError, ResourceNotFoundError
139-
from .services.service import RepositoryService
140+
from .services.service import RepositoryService, EXTRACT_URL_RE
140141

141142
from .tools import print_tty, print_iter, loop_input, confirm
142143
from .kwargparse import KeywordArgumentParser, store_parameter, register_action
143144

144145
from git import Repo, Git
145146
from git.exc import InvalidGitRepositoryError, NoSuchPathError, BadName
146147

147-
import re
148-
149-
EXTRACT_URL_RE = re.compile('[^:]*(://|@)[^/]*/')
150-
151-
152148
class GitRepoRunner(KeywordArgumentParser):
153149

154-
def init(self): # pragma: no cover
150+
def init(self): # pragma: no cover
155151
if 'GIT_WORK_TREE' in os.environ.keys() or 'GIT_DIR' in os.environ.keys():
156152
del os.environ['GIT_WORK_TREE']
157153

@@ -180,7 +176,7 @@ def get_service(self, lookup_repository=True, resolve_targets=None):
180176
'''Argument storage'''
181177

182178
@store_parameter('--verbose')
183-
def set_verbosity(self, verbose): # pragma: no cover
179+
def set_verbosity(self, verbose): # pragma: no cover
184180
if verbose >= 5:
185181
print(self.args)
186182
if verbose >= 4:
@@ -205,21 +201,25 @@ def set_verbosity(self, verbose): # pragma: no cover
205201

206202
log.addHandler(logging.StreamHandler())
207203

208-
@store_parameter('<user>/<repo>')
204+
@store_parameter('<namespace>/<repo>')
209205
def set_repo_slug(self, repo_slug, auto=False):
210206
self.repo_slug = EXTRACT_URL_RE.sub('', repo_slug) if repo_slug else repo_slug
211207
self._auto_slug = auto
212208
if not self.repo_slug:
213-
self.user_name = None
209+
self.namespace = None
214210
self.repo_name = None
215211
elif '/' in self.repo_slug:
216212
# in case a full URL is given as parameter, just extract the slug part.
217-
self.user_name, self.repo_name, *overflow = self.repo_slug.split('/')
218-
if len(overflow) != 0:
213+
*namespace, self.repo_name = self.repo_slug.split('/')
214+
self.namespace = '/'.join(namespace)
215+
216+
# This needs to be manually plucked because otherwise it'll be unset for some commands.
217+
service = RepositoryService.get_service(None, self.target)
218+
if len(namespace) > service._max_nested_namespaces:
219219
raise ArgumentError('Too many slashes.'
220-
'Format of the parameter is <user>/<repo> or <repo>.')
220+
'The maximum depth of namespaces is: {}'.format(service._max_nested_namespaces))
221221
else:
222-
self.user_name = None
222+
self.namespace = None
223223
self.repo_name = self.repo_slug
224224

225225
@store_parameter('<branch>')
@@ -258,7 +258,7 @@ def do_list(self):
258258
@register_action('add')
259259
def do_remote_add(self):
260260
service = self.get_service()
261-
remote, user, repo = service.add(self.repo_name, self.user_name,
261+
remote, user, repo = service.add(self.repo_name, self.namespace,
262262
name=self.remote_name,
263263
tracking=self.tracking,
264264
alone=self.alone,
@@ -280,15 +280,15 @@ def do_fork(self):
280280
raise ArgumentError('Path {} is not a git repository'.format(self.path))
281281

282282
else:
283-
# git <target> fork <user>/<repo>
283+
# git <target> fork <namespace>/<repo>
284284
if not self.target_repo:
285-
if not self.user_name:
285+
if not self.namespace:
286286
raise ArgumentError('Cannot clone repository, '
287-
'you shall provide either a <user>/<repo> parameter '
287+
'you shall provide either a <namespace>/<repo> parameter '
288288
'or no parameters to fork current repository!')
289289
service = self.get_service(None)
290290

291-
# git <target> fork <user>/<repo> <path>
291+
# git <target> fork <namespace>/<repo> <path>
292292
else:
293293
repo_path = os.path.join(self.path, self.target_repo)
294294
try:
@@ -298,7 +298,7 @@ def do_fork(self):
298298
# if the repository does not exists at given path, clone upstream into that path
299299
self.do_clone(service, repo_path)
300300

301-
service.run_fork(self.user_name, self.repo_name, branch=self.branch)
301+
service.run_fork(self.namespace, self.repo_name, branch=self.branch)
302302

303303
if not self.repo_slug or self.target_repo:
304304
log.info('Successfully forked {} as {} within {}.'.format(
@@ -317,7 +317,7 @@ def do_clone(self, service=None, repo_path=None):
317317
try:
318318
repository = Repo.init(repo_path)
319319
service = RepositoryService.get_service(repository, self.target)
320-
service.clone(self.user_name, self.repo_name, self.branch)
320+
service.clone(self.namespace, self.repo_name, self.branch)
321321
log.info('Successfully cloned `{}` into `{}`!'.format(
322322
service.format_path(self.repo_slug),
323323
repo_path)
@@ -332,28 +332,28 @@ def do_clone(self, service=None, repo_path=None):
332332
def do_create(self):
333333
service = self.get_service(lookup_repository=self.repo_slug == None or self.add)
334334
# if no repo_slug has been given, use the directory name as current project name
335-
if not self.user_name and not self.repo_name:
335+
if not self.namespace and not self.repo_name:
336336
self.set_repo_slug('/'.join([service.user,
337337
os.path.basename(os.path.abspath(self.path))]))
338-
if not self.user_name:
339-
self.user_name = service.user
340-
service.create(self.user_name, self.repo_name, add=self.add)
338+
if not self.namespace:
339+
self.namespace = service.user
340+
service.create(self.namespace, self.repo_name, add=self.add)
341341
log.info('Successfully created remote repository `{}`, '
342342
'with local remote `{}`'.format(
343-
service.format_path(self.repo_name, namespace=self.user_name),
343+
service.format_path(self.repo_name, namespace=self.namespace),
344344
service.name)
345345
)
346346
return 0
347347

348348
@register_action('delete')
349349
def do_delete(self):
350350
service = self.get_service(lookup_repository=self.repo_slug == None)
351-
if not self.force: # pragma: no cover
351+
if not self.force: # pragma: no cover
352352
if not confirm('repository', self.repo_slug):
353353
return 0
354354

355-
if self.user_name:
356-
service.delete(self.repo_name, self.user_name)
355+
if self.namespace:
356+
service.delete(self.repo_name, self.namespace)
357357
else:
358358
service.delete(self.repo_name)
359359
log.info('Successfully deleted remote `{}` from {}'.format(
@@ -365,15 +365,15 @@ def do_delete(self):
365365

366366
@register_action('open')
367367
def do_open(self):
368-
self.get_service(lookup_repository=self.repo_slug is None).open(self.user_name, self.repo_name)
368+
self.get_service(lookup_repository=self.repo_slug is None).open(self.namespace, self.repo_name)
369369
return 0
370370

371371
@register_action('request', 'ls')
372372
@register_action('request', 'list')
373373
def do_request_list(self):
374374
service = self.get_service(lookup_repository=self.repo_slug == None)
375375
print_tty('List of open requests to merge:')
376-
print_iter(service.request_list(self.user_name, self.repo_name))
376+
print_iter(service.request_list(self.namespace, self.repo_name))
377377
return 0
378378

379379
@register_action('request', 'create')
@@ -426,7 +426,7 @@ def request_edition(repository, from_branch, onto_target):
426426

427427
service = self.get_service(resolve_targets=('upstream', '{service}', 'origin'))
428428

429-
new_request = service.request_create(self.user_name,
429+
new_request = service.request_create(self.namespace,
430430
self.repo_name,
431431
self.local_branch,
432432
self.remote_branch,
@@ -442,7 +442,7 @@ def request_edition(repository, from_branch, onto_target):
442442
@register_action('request', 'fetch')
443443
def do_request_fetch(self):
444444
service = self.get_service()
445-
new_branch = service.request_fetch(self.user_name, self.repo_name, self.request, force=self.force)
445+
new_branch = service.request_fetch(self.namespace, self.repo_name, self.request, force=self.force)
446446
log.info('Successfully fetched request id `{}` of `{}` into `{}`!'.format(
447447
self.request,
448448
self.repo_slug,
@@ -489,7 +489,7 @@ def do_gist_create(self):
489489
@register_action('snippet', 'delete')
490490
def do_gist_delete(self):
491491
service = self.get_service(lookup_repository=False)
492-
if not self.force: # pragma: no cover
492+
if not self.force: # pragma: no cover
493493
if not confirm('snippet', self.gist_ref):
494494
return 0
495495

@@ -499,8 +499,6 @@ def do_gist_delete(self):
499499

500500
@register_action('config')
501501
def do_config(self):
502-
from getpass import getpass
503-
504502
def setup_service(service):
505503
new_conf = dict(
506504
fqdn=None,
@@ -587,7 +585,8 @@ def main(args):
587585
log.exception('------------------------------------')
588586
return 2
589587

590-
def cli(): #pragma: no cover
588+
589+
def cli(): # pragma: no cover
591590
try:
592591
sys.exit(main(docopt(__doc__.format(self=sys.argv[0].split(os.path.sep)[-1], version=__version__))))
593592
finally:

git_repo/services/ext/gitlab.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,24 @@
2121
class GitlabService(RepositoryService):
2222
fqdn = 'gitlab.com'
2323

24+
_max_nested_namespaces = 21
25+
2426
def __init__(self, *args, **kwarg):
25-
self.gl = gitlab.Gitlab(self.url_ro)
27+
self.session = gitlab.requests.Session()
2628
super().__init__(*args, **kwarg)
2729

2830
def connect(self):
29-
self.gl.ssl_verify = self.session_certificate or not self.session_insecure
3031
if self.session_proxy:
3132
self.gl.session.proxies.update(self.session_proxy)
3233

33-
self.gl.set_url(self.url_ro)
34-
self.gl.set_token(self._privatekey)
35-
self.gl.token_auth()
34+
self.gl = gitlab.Gitlab(self.url_ro,
35+
session=self.session,
36+
private_token=self._privatekey
37+
)
38+
39+
self.gl.ssl_verify = self.session_certificate or not self.session_insecure
40+
41+
self.gl.auth()
3642
self.username = self.gl.user.username
3743

3844
def create(self, user, repo, add=False):
@@ -80,7 +86,7 @@ def list(self, user, _long=False):
8086
if not self.gl.users.search(user):
8187
raise ResourceNotFoundError("User {} does not exists.".format(user))
8288

83-
repositories = self.gl.projects.list(author=user)
89+
repositories = self.gl.projects.list(author=user, safe_all=True)
8490
if not _long:
8591
repositories = list([repo.path_with_namespace for repo in repositories])
8692
yield "{}"
@@ -105,11 +111,11 @@ def list(self, user, _long=False):
105111
# status
106112
status,
107113
# stats
108-
str(len(list(repo.commits.list()))), # number of commits
109-
str(len(list(repo.mergerequests.list()))), # number of pulls
110-
str(len(list(repo.issues.list()))), # number of issues
114+
str(len(repo.commits.list(all=True))), # number of commits
115+
str(len(repo.mergerequests.list(all=True))), # number of pulls
116+
str(len(repo.issues.list(all=True))), # number of issues
111117
str(repo.forks_count), # number of forks
112-
str(len(list(repo.members.list()))), # number of contributors
118+
str(len(repo.members.list(all=True))), # number of contributors
113119
'N.A.', # number of subscribers
114120
str(repo.star_count), # number of ♥
115121
# info
@@ -259,7 +265,8 @@ def request_create(self, onto_user, onto_repo, from_branch, onto_branch, title=N
259265

260266
from_reposlug = self.guess_repo_slug(self.repository, self, resolve_targets=['{service}'])
261267
if from_reposlug:
262-
from_user, from_repo = from_reposlug.split('/')
268+
*namespaces, from_repo = from_reposlug.split('/')
269+
from_user = '/'.join(namespaces)
263270
if (onto_user, onto_repo) == (from_user, from_repo):
264271
from_project = onto_project
265272
else:
@@ -268,7 +275,7 @@ def request_create(self, onto_user, onto_repo, from_branch, onto_branch, title=N
268275
from_project = None
269276

270277
if not from_project:
271-
raise ResourceNotFoundError('Could not find project `{}/{}`!'.format(from_user, from_repo))
278+
raise ResourceNotFoundError('Could not find project `{}`!'.format(from_reposlug))
272279

273280
# when no repo slug has been given to `git-repo X request create`
274281
# then chances are current project is a fork of the target

0 commit comments

Comments
 (0)