Skip to content

Commit

Permalink
Merge branch 'devel' into fix/issues/167
Browse files Browse the repository at this point in the history
  • Loading branch information
guyzmo committed Nov 18, 2017
2 parents 4302d1a + 9897954 commit 18086ca
Show file tree
Hide file tree
Showing 13 changed files with 353 additions and 76 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@
* [![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)
* [![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)

## Looking for help

For the past few months I've been really busy coding on stuff that puts food on the table…
And sadly, I cannot give this project all the love it deserves. Which is why it's taken me months
to spend a few hours merge and release the PRs featured in this repository.

I'm still using this project daily, but I'm not having enough time to keep on putting all the
effort needed to make it shine (SSH keys, issues support…)

So I'd like to share the maintenance responsibility with someone or more people. If you're
interested, please ping me on IRC or by mail (which is in all my commits). I'm always happy
to guide through the code's design!

### Usage

#### main commands
Expand Down Expand Up @@ -380,6 +393,7 @@ With code contributions coming from:
* [@rnestler](https://github.com/rnestler)[commits](https://github.com/guyzmo/git-repo/commits/devel?author=rnestler)
* [@jayvdb](https://github.com/jayvdb)[commits](https://github.com/guyzmo/git-repo/commits/devel?author=jayvdb)
* [@kounoike](https://github.com/kounoike)[commits](https://github.com/guyzmo/git-repo/commits/devel?author=kounoike)
* [@AmandaCameron](https://github.com/AmandaCameron)[commits](https://github.com/guyzmo/git-repo/commits/devel?author=AmandaCameron)

### License

Expand Down
103 changes: 51 additions & 52 deletions git_repo/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,24 @@
{self} [--path=<path>] [-v...] <target> delete [-f]
{self} [--path=<path>] [-v...] <target> open
{self} [--path=<path>] [-v...] <target> (list|ls) [-l] <user>
{self} [--path=<path>] [-v...] <target> fork <user>/<repo> [--branch=<branch>]
{self} [--path=<path>] [-v...] <target> fork <user>/<repo> <repo> [--branch=<branch>]
{self} [--path=<path>] [-v...] <target> create <user>/<repo> [--add]
{self} [--path=<path>] [-v...] <target> delete <user>/<repo> [-f]
{self} [--path=<path>] [-v...] <target> open <user>/<repo>
{self} [--path=<path>] [-v...] <target> clone <user>/<repo> [<repo> [<branch>]]
{self} [--path=<path>] [-v...] <target> fork <namespace>/<repo> [--branch=<branch>]
{self} [--path=<path>] [-v...] <target> fork <namespace>/<repo> <repo> [--branch=<branch>]
{self} [--path=<path>] [-v...] <target> create <namespace>/<repo> [--add]
{self} [--path=<path>] [-v...] <target> delete <namespace>/<repo> [-f]
{self} [--path=<path>] [-v...] <target> open <namespace>/<repo>
{self} [--path=<path>] [-v...] <target> clone <namespace>/<repo> [<repo> [<branch>]]
{self} [--path=<path>] [-v...] <target> add
{self} [--path=<path>] [-v...] <target> add <user>/<repo> [<name>] [--tracking=<branch>] [-a]
{self} [--path=<path>] [-v...] <target> add <namespace>/<repo> [<name>] [--tracking=<branch>] [-a]
{self} [--path=<path>] [-v...] <target> request (list|ls)
{self} [--path=<path>] [-v...] <target> request fetch <request> [-f]
{self} [--path=<path>] [-v...] <target> request create [--title=<title>] [--message=<message>]
{self} [--path=<path>] [-v...] <target> request create <local_branch> [--title=<title>] [--message=<message>]
{self} [--path=<path>] [-v...] <target> request create <remote_branch> <local_branch> [--title=<title>] [--message=<message>]
{self} [--path=<path>] [-v...] <target> request <user>/<repo> (list|ls)
{self} [--path=<path>] [-v...] <target> request <user>/<repo> fetch <request> [-f]
{self} [--path=<path>] [-v...] <target> request <user>/<repo> create [--title=<title>] [--branch=<remote>] [--message=<message>]
{self} [--path=<path>] [-v...] <target> request <user>/<repo> create <local_branch> [--title=<title>] [--branch=<remote>] [--message=<message>]
{self} [--path=<path>] [-v...] <target> request <user>/<repo> create <remote_branch> <local_branch> [--title=<title>] [--branch=<remote>] [--message=<message>]
{self} [--path=<path>] [-v...] <target> request <namespace>/<repo> (list|ls)
{self} [--path=<path>] [-v...] <target> request <namespace>/<repo> fetch <request> [-f]
{self} [--path=<path>] [-v...] <target> request <namespace>/<repo> create [--title=<title>] [--branch=<remote>] [--message=<message>]
{self} [--path=<path>] [-v...] <target> request <namespace>/<repo> create <local_branch> [--title=<title>] [--branch=<remote>] [--message=<message>]
{self} [--path=<path>] [-v...] <target> request <namespace>/<repo> create <remote_branch> <local_branch> [--title=<title>] [--branch=<remote>] [--message=<message>]
{self} [--path=<path>] [-v...] <target> (gist|snippet) (list|ls) [<gist>]
{self} [--path=<path>] [-v...] <target> (gist|snippet) clone <gist>
{self} [--path=<path>] [-v...] <target> (gist|snippet) fetch <gist> [<gist_file>]
Expand All @@ -49,13 +49,13 @@
config Run authentication process and configure the tool
Options:
<user>/<repo> Repository to work with
<namespace>/<repo> Repository to work with
-p,--path=<path> Path to work on [default: .]
-v,--verbose Makes it more chatty (repeat twice to see git commands)
-h,--help Shows this message
Options for list:
<user> Name of the user whose repositories will be listed
<namespace> Name of the user whose repositories will be listed
-l,--long Show one repository per line, when set show the results
with the following columns:
STATUS, COMMITS, REQUESTS, ISSUES, FORKS, CONTRIBUTORS, WATCHERS, LIKES, LANGUAGE, MODIF, NAME
Expand Down Expand Up @@ -116,6 +116,7 @@
'''

from docopt import docopt
from getpass import getpass

import os
import sys
Expand All @@ -131,27 +132,22 @@
log_root = logging.getLogger()
log = logging.getLogger('git_repo')

if sys.version_info.major < 3: # pragma: no cover
if sys.version_info.major < 3: # pragma: no cover
print('Please use with python version 3')
sys.exit(1)

from .exceptions import ArgumentError, ResourceNotFoundError
from .services.service import RepositoryService
from .services.service import RepositoryService, EXTRACT_URL_RE

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

from git import Repo, Git
from git.exc import InvalidGitRepositoryError, NoSuchPathError, BadName

import re

EXTRACT_URL_RE = re.compile('[^:]*(:https://|@)[^/]*/')


class GitRepoRunner(KeywordArgumentParser):

def init(self): # pragma: no cover
def init(self): # pragma: no cover
if 'GIT_WORK_TREE' in os.environ.keys() or 'GIT_DIR' in os.environ.keys():
del os.environ['GIT_WORK_TREE']

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

@store_parameter('--verbose')
def set_verbosity(self, verbose): # pragma: no cover
def set_verbosity(self, verbose): # pragma: no cover
if verbose >= 5:
print(self.args)
if verbose >= 4:
Expand All @@ -205,21 +201,25 @@ def set_verbosity(self, verbose): # pragma: no cover

log.addHandler(logging.StreamHandler())

@store_parameter('<user>/<repo>')
@store_parameter('<namespace>/<repo>')
def set_repo_slug(self, repo_slug, auto=False):
self.repo_slug = EXTRACT_URL_RE.sub('', repo_slug) if repo_slug else repo_slug
self._auto_slug = auto
if not self.repo_slug:
self.user_name = None
self.namespace = None
self.repo_name = None
elif '/' in self.repo_slug:
# in case a full URL is given as parameter, just extract the slug part.
self.user_name, self.repo_name, *overflow = self.repo_slug.split('/')
if len(overflow) != 0:
*namespace, self.repo_name = self.repo_slug.split('/')
self.namespace = '/'.join(namespace)

# This needs to be manually plucked because otherwise it'll be unset for some commands.
service = RepositoryService.get_service(None, self.target)
if len(namespace) > service._max_nested_namespaces:
raise ArgumentError('Too many slashes.'
'Format of the parameter is <user>/<repo> or <repo>.')
'The maximum depth of namespaces is: {}'.format(service._max_nested_namespaces))
else:
self.user_name = None
self.namespace = None
self.repo_name = self.repo_slug

@store_parameter('<branch>')
Expand Down Expand Up @@ -258,7 +258,7 @@ def do_list(self):
@register_action('add')
def do_remote_add(self):
service = self.get_service()
remote, user, repo = service.add(self.repo_name, self.user_name,
remote, user, repo = service.add(self.repo_name, self.namespace,
name=self.remote_name,
tracking=self.tracking,
alone=self.alone,
Expand All @@ -280,15 +280,15 @@ def do_fork(self):
raise ArgumentError('Path {} is not a git repository'.format(self.path))

else:
# git <target> fork <user>/<repo>
# git <target> fork <namespace>/<repo>
if not self.target_repo:
if not self.user_name:
if not self.namespace:
raise ArgumentError('Cannot clone repository, '
'you shall provide either a <user>/<repo> parameter '
'you shall provide either a <namespace>/<repo> parameter '
'or no parameters to fork current repository!')
service = self.get_service(None)

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

service.run_fork(self.user_name, self.repo_name, branch=self.branch)
service.run_fork(self.namespace, self.repo_name, branch=self.branch)

if not self.repo_slug or self.target_repo:
log.info('Successfully forked {} as {} within {}.'.format(
Expand All @@ -317,7 +317,7 @@ def do_clone(self, service=None, repo_path=None):
try:
repository = Repo.init(repo_path)
service = RepositoryService.get_service(repository, self.target)
service.clone(self.user_name, self.repo_name, self.branch)
service.clone(self.namespace, self.repo_name, self.branch)
log.info('Successfully cloned `{}` into `{}`!'.format(
service.format_path(self.repo_slug),
repo_path)
Expand All @@ -332,28 +332,28 @@ def do_clone(self, service=None, repo_path=None):
def do_create(self):
service = self.get_service(lookup_repository=self.repo_slug == None or self.add)
# if no repo_slug has been given, use the directory name as current project name
if not self.user_name and not self.repo_name:
if not self.namespace and not self.repo_name:
self.set_repo_slug('/'.join([service.user,
os.path.basename(os.path.abspath(self.path))]))
if not self.user_name:
self.user_name = service.user
service.create(self.user_name, self.repo_name, add=self.add)
if not self.namespace:
self.namespace = service.user
service.create(self.namespace, self.repo_name, add=self.add)
log.info('Successfully created remote repository `{}`, '
'with local remote `{}`'.format(
service.format_path(self.repo_name, namespace=self.user_name),
service.format_path(self.repo_name, namespace=self.namespace),
service.name)
)
return 0

@register_action('delete')
def do_delete(self):
service = self.get_service(lookup_repository=self.repo_slug == None)
if not self.force: # pragma: no cover
if not self.force: # pragma: no cover
if not confirm('repository', self.repo_slug):
return 0

if self.user_name:
service.delete(self.repo_name, self.user_name)
if self.namespace:
service.delete(self.repo_name, self.namespace)
else:
service.delete(self.repo_name)
log.info('Successfully deleted remote `{}` from {}'.format(
Expand All @@ -365,15 +365,15 @@ def do_delete(self):

@register_action('open')
def do_open(self):
self.get_service(lookup_repository=self.repo_slug is None).open(self.user_name, self.repo_name)
self.get_service(lookup_repository=self.repo_slug is None).open(self.namespace, self.repo_name)
return 0

@register_action('request', 'ls')
@register_action('request', 'list')
def do_request_list(self):
service = self.get_service(lookup_repository=self.repo_slug == None)
print_tty('List of open requests to merge:')
print_iter(service.request_list(self.user_name, self.repo_name))
print_iter(service.request_list(self.namespace, self.repo_name))
return 0

@register_action('request', 'create')
Expand Down Expand Up @@ -426,7 +426,7 @@ def request_edition(repository, from_branch, onto_target):

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

new_request = service.request_create(self.user_name,
new_request = service.request_create(self.namespace,
self.repo_name,
self.local_branch,
self.remote_branch,
Expand All @@ -442,7 +442,7 @@ def request_edition(repository, from_branch, onto_target):
@register_action('request', 'fetch')
def do_request_fetch(self):
service = self.get_service()
new_branch = service.request_fetch(self.user_name, self.repo_name, self.request, force=self.force)
new_branch = service.request_fetch(self.namespace, self.repo_name, self.request, force=self.force)
log.info('Successfully fetched request id `{}` of `{}` into `{}`!'.format(
self.request,
self.repo_slug,
Expand Down Expand Up @@ -489,7 +489,7 @@ def do_gist_create(self):
@register_action('snippet', 'delete')
def do_gist_delete(self):
service = self.get_service(lookup_repository=False)
if not self.force: # pragma: no cover
if not self.force: # pragma: no cover
if not confirm('snippet', self.gist_ref):
return 0

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

@register_action('config')
def do_config(self):
from getpass import getpass

def setup_service(service):
new_conf = dict(
fqdn=None,
Expand Down Expand Up @@ -587,7 +585,8 @@ def main(args):
log.exception('------------------------------------')
return 2

def cli(): #pragma: no cover

def cli(): # pragma: no cover
try:
sys.exit(main(docopt(__doc__.format(self=sys.argv[0].split(os.path.sep)[-1], version=__version__))))
finally:
Expand Down
31 changes: 19 additions & 12 deletions git_repo/services/ext/gitlab.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,24 @@
class GitlabService(RepositoryService):
fqdn = 'gitlab.com'

_max_nested_namespaces = 21

def __init__(self, *args, **kwarg):
self.gl = gitlab.Gitlab(self.url_ro)
self.session = gitlab.requests.Session()
super().__init__(*args, **kwarg)

def connect(self):
self.gl.ssl_verify = self.session_certificate or not self.session_insecure
if self.session_proxy:
self.gl.session.proxies.update(self.session_proxy)

self.gl.set_url(self.url_ro)
self.gl.set_token(self._privatekey)
self.gl.token_auth()
self.gl = gitlab.Gitlab(self.url_ro,
session=self.session,
private_token=self._privatekey
)

self.gl.ssl_verify = self.session_certificate or not self.session_insecure

self.gl.auth()
self.username = self.gl.user.username

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

repositories = self.gl.projects.list(author=user)
repositories = self.gl.projects.list(author=user, safe_all=True)
if not _long:
repositories = list([repo.path_with_namespace for repo in repositories])
yield "{}"
Expand All @@ -105,11 +111,11 @@ def list(self, user, _long=False):
# status
status,
# stats
str(len(list(repo.commits.list()))), # number of commits
str(len(list(repo.mergerequests.list()))), # number of pulls
str(len(list(repo.issues.list()))), # number of issues
str(len(repo.commits.list(all=True))), # number of commits
str(len(repo.mergerequests.list(all=True))), # number of pulls
str(len(repo.issues.list(all=True))), # number of issues
str(repo.forks_count), # number of forks
str(len(list(repo.members.list()))), # number of contributors
str(len(repo.members.list(all=True))), # number of contributors
'N.A.', # number of subscribers
str(repo.star_count), # number of ♥
# info
Expand Down Expand Up @@ -259,7 +265,8 @@ def request_create(self, onto_user, onto_repo, from_branch, onto_branch, title=N

from_reposlug = self.guess_repo_slug(self.repository, self, resolve_targets=['{service}'])
if from_reposlug:
from_user, from_repo = from_reposlug.split('/')
*namespaces, from_repo = from_reposlug.split('/')
from_user = '/'.join(namespaces)
if (onto_user, onto_repo) == (from_user, from_repo):
from_project = onto_project
else:
Expand All @@ -268,7 +275,7 @@ def request_create(self, onto_user, onto_repo, from_branch, onto_branch, title=N
from_project = None

if not from_project:
raise ResourceNotFoundError('Could not find project `{}/{}`!'.format(from_user, from_repo))
raise ResourceNotFoundError('Could not find project `{}`!'.format(from_reposlug))

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

0 comments on commit 18086ca

Please sign in to comment.