Skip to content

🚧 list command for bitbucket #80

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions git_repo/repo.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env pytho

'''
Usage:
Expand Down Expand Up @@ -134,7 +134,7 @@
print('Please use with python version 3')
sys.exit(1)

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

from .kwargparse import KeywordArgumentParser, store_parameter, register_action
Expand Down
225 changes: 111 additions & 114 deletions git_repo/services/ext/bitbucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,144 +6,141 @@
from ..service import register_target, RepositoryService
from ...exceptions import ResourceError, ResourceExistsError, ResourceNotFoundError

import bitbucket.bitbucket as bitbucket
from pybitbucket.bitbucket import Client
from pybitbucket.auth import BasicAuthenticator
from pybitbucket.user import User
from pybitbucket.repository import Repository, RepositoryForkPolicy

from requests import Request, Session
from requests.exceptions import HTTPError
import json

'''
Extension of the bitbucket module implementation to add support for the extra
features the original implementation lacked. This is a temporary measure, up
until a PR is crafted for the original code.
'''

bitbucket.URLS.update({
'GET_REPO' : 'repositories/%(username)s/%(repo_slug)s/',
'DELETE_REPO' : 'repositories/%(accountname)s/%(repo_slug)s',
'FORK_REPO' : 'repositories/%(username)s/%(repo_slug)s/fork',
})

class Bitbucket(bitbucket.Bitbucket):
def __init__(self, *args, **kwarg):
super(Bitbucket, self).__init__(self)
self.session = Session()
# XXX monkey patching of requests within bitbucket module
self.requests = self.session

def get(self, user=None, repo_slug=None):
""" Get a single repository on Bitbucket and return it."""
username = user or self.username
repo_slug = repo_slug or self.repo_slug or ''
url = self.url('GET_REPO', username=username, repo_slug=repo_slug)
return self.dispatch('GET', url, auth=self.auth)

def delete(self, user, repo_slug):
url = self.url('DELETE_REPO', username=user, accountname=user, repo_slug=repo_slug)
return self.dispatch('DELETE', url, auth=self.auth)

def fork(self, user, repo_slug, new_name=None):
url = self.url('FORK_REPO', username=user, repo_slug=repo_slug)
new_repo = new_name or repo_slug
return self.dispatch('POST', url, name=new_repo, auth=self.auth)

def dispatch(self, method, url, auth=None, params=None, **kwargs):
""" Send HTTP request, with given method,
credentials and data to the given URL,
and return the success and the result on success.
"""
r = Request(
method=method,
url=url,
auth=auth,
params=params,
data=kwargs)
resp = self.session.send(r.prepare())
status = resp.status_code
text = resp.text
error = resp.reason
if status >= 200 and status < 300:
if text:
try:
return (True, json.loads(text))
except TypeError:
pass
except ValueError:
pass
return (True, text)
elif status >= 300 and status < 400:
return (
False,
'Unauthorized access, '
'please check your credentials.')
elif status == 404:
return (False, dict(message='Service not found', reason=error, code=status))
elif status == 400:
return (False, dict(message='Bad request sent to server.', reason=error, code=status))
elif status == 401:
return (False, dict(message='Not enough privileges.', reason=error, code=status))
elif status == 403:
return (False, dict(message='Not authorized.', reason=error, code=status))
elif status == 402 or status >= 405:
return (False, dict(message='Request error.', reason=error, code=status))
elif status >= 500 and status < 600:
return (False, dict(message='Server error.', reason=error, code=status))
else:
return (False, dict(message='Unidentified error.', reason=error, code=status))
# bitbucket.URLS.update({
# 'GET_REPO' : 'repositories/%(username)s/%(repo_slug)s/',
# 'DELETE_REPO' : 'repositories/%(accountname)s/%(repo_slug)s',
# 'FORK_REPO' : 'repositories/%(username)s/%(repo_slug)s/fork',
# })


@register_target('bb', 'bitbucket')
class BitbucketService(RepositoryService):
fqdn = 'bitbucket.org'

def __init__(self, *args, **kwarg):
self.bb = Bitbucket()
self.bb = Client()
super(BitbucketService, self).__init__(*args, **kwarg)

def connect(self):
if not self._privatekey:
raise ConnectionError('Could not connect to BitBucket. Please configure .gitconfig with your bitbucket credentials.')
if not ':' in self._privatekey:
raise ConnectionError('Could not connect to BitBucket. Please setup your private key with login:password')
self.bb.username, self.bb.password = self._privatekey.split(':')
self.username = self.bb.username
result, _ = self.bb.get_user()
if not result:
raise ConnectionError('Could not connect to BitBucket. Not authorized, wrong credentials.')
self.bb.config = BasicAuthenticator(*self._privatekey.split(':')+['[email protected]'])
self.bb.session = self.bb.config.session
try:
User.find_current_user(client=self.bb)
except HTTPError as err:
raise ConnectionError('Could not connect to BitBucket. Not authorized, wrong credentials.') from err

def create(self, user, repo, add=False):
success, result = self.bb.repository.create(repo, scm='git', public=True)
if not success and result['code'] == 400:
raise ResourceExistsError('Project {} already exists on this account.'.format(repo))
elif not success:
raise ResourceError("Couldn't complete creation: {message} (error #{code}: {reason})".format(**result))
if add:
self.add(user=user, repo=repo, tracking=self.name)
try:
repo = Repository.create(
repo,
fork_policy=RepositoryForkPolicy.ALLOW_FORKS,
is_private=False,
client=self.bb
)
if add:
self.add(user=user, repo=repo, tracking=self.name)
except HTTPError as err:
if err.status_code == 400:
raise ResourceExistsError('Project {} already exists on this account.'.format(repo)) from err
raise ResourceError("Couldn't complete creation: {}".format(err)) from err

def fork(self, user, repo):
success, result = self.bb.fork(user, repo)
if not success:
raise ResourceError("Couldn't complete fork: {message} (error #{code}: {reason})".format(**result))
raise NotImplementedError('No support yet by the underlying library.')
try:
Repository.find_repository_by_name_and_owner(repo, owner=user, client=self.bb).fork()
except HTTPError as err:
raise ResourceError("Couldn't complete creation: {}".format(err)) from err
return '/'.join([result['owner'], result['slug']])

def delete(self, repo, user=None):
if not user:
user = self.user
success, result = self.bb.delete(user, repo)
if not success and result['code'] == 404:
raise ResourceNotFoundError("Cannot delete: repository {}/{} does not exists.".format(user, repo))
elif not success:
raise ResourceError("Couldn't complete deletion: {message} (error #{code}: {reason})".format(**result))
try:
Repository.find_repository_by_name_and_owner(repo, owner=user, client=self.bb).delete()
except HTTPError as err:
if err.status_code == 404:
raise ResourceNotFoundError("Cannot delete: repository {}/{} does not exists.".format(user, repo)) from err
raise ResourceError("Couldn't complete creation: {}".format(err)) from err

def list(self, user, _long=False):
import shutil, sys
from datetime import datetime
term_width = shutil.get_terminal_size((80, 20)).columns
def col_print(lines, indent=0, pad=2):
# prints a list of items in a fashion similar to the dir command
# borrowed from https://gist.github.com/critiqjo/2ca84db26daaeb1715e1
n_lines = len(lines)
if n_lines == 0:
return
col_width = max(len(line) for line in lines)
n_cols = int((term_width + pad - indent)/(col_width + pad))
n_cols = min(n_lines, max(1, n_cols))
col_len = int(n_lines/n_cols) + (0 if n_lines % n_cols == 0 else 1)
if (n_cols - 1) * col_len >= n_lines:
n_cols -= 1
cols = [lines[i*col_len : i*col_len + col_len] for i in range(n_cols)]
rows = list(zip(*cols))
rows_missed = zip(*[col[len(rows):] for col in cols[:-1]])
rows.extend(rows_missed)
for row in rows:
print(" "*indent + (" "*pad).join(line.ljust(col_width) for line in row))

try:
user = User.find_user_by_username(user)
except HTTPError as err:
raise ResourceNotFoundError("User {} does not exists.".format(user)) from err

repositories = user.repositories()
if not _long:
repositories = list(repositories)
col_print(["/".join([user.username, repo.name]) for repo in repositories])
else:
print('Status\tCommits\tReqs\tIssues\tForks\tCoders\tWatch\tLikes\tLang\tModif\t\tName', file=sys.stderr)
for repo in repositories:
# if repo.updated_at.year < datetime.now().year:
# date_fmt = "%b %d %Y"
# else:
# date_fmt = "%b %d %H:%M"

status = ''.join([
'F' if getattr(repo, 'parent', None) else ' ', # is a fork?
'P' if repo.is_private else ' ', # is private?
])
print('\t'.join([
# status
status,
# stats
str(len(list(repo.commits()))), # number of commits
str(len(list(repo.pullrequests()))), # number of pulls
str('N.A.'), # number of issues
str(len(list(repo.forks()))), # number of forks
str('N.A.'), # number of contributors
str(len(list(repo.watchers()))), # number of subscribers
str('N.A.'), # number of ♥
# info
repo.language or '?', # language
repo.updated_on, # date
'/'.join([user.username, repo.name]), # name
]))

def get_repository(self, user, repo):
if user != self.user:
result, repo_list = self.bb.repository.public(user)
else:
result, repo_list = self.bb.repository.all()
if not result:
raise ResourceError("Couldn't list repositories: {message} (error #{code}: {reason})".format(**repo_list))
for r in repo_list:
if r['name'] == repo:
return r
#raise ResourceNotFoundError('Cannot retrieve repository: {}/{} does not exists.'.format(user, repo))
try:
return Repository.find_repository_by_name_and_owner(repo, owner=user, client=self.bb)
except HTTPError as err:
raise ResourceNotFoundError('Cannot retrieve repository: {}/{} does not exists.'.format(user, repo))

@classmethod
def get_auth_token(cls, login, password, prompt=None):
Expand All @@ -152,9 +149,9 @@ def get_auth_token(cls, login, password, prompt=None):

@property
def user(self):
ret, user = self.bb.get_user()
if ret:
return user['username']
raise ResourceError("Could not retrieve username: {message} (error #{code}: {reason}".format(**result))
try:
return User.find_current_user(client=self.bb).username
except HTTPError as err:
raise ResourceError("Couldn't complete creation: {}".format(err)) from err


2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ GitPython>=2.1.0
uritemplate.py==2.0.0
github3.py==0.9.5
python-gitlab>=0.13
bitbucket-api
pybitbucket>=0.11.1
Loading