Skip to content

Commit

Permalink
Merge branch 'requests/github/183' into devel
Browse files Browse the repository at this point in the history
  • Loading branch information
guyzmo committed Feb 4, 2018
2 parents fe790c2 + 9b20189 commit 2974c3f
Show file tree
Hide file tree
Showing 21 changed files with 1,386 additions and 74 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,18 @@ if you want to use another path, you can change the defaults:

python -m git_repo.extract_config ~/.gitconfig-repos ~/.gitconfig

### Configuring Gerrit

Please note: when configuration wizard will ask you for password, do not provide
your Gerrit account password, but enter `HTTP password` instead. You can setup
it on [Settings > HTTP Password page](https://review.gerrithub.io/#/settings/http-password)

You may also need to tweak your `~/.gitconfig`:
* set `ro-suffix` if your Gerrit isn't served at server root. For example, set
`ro-suffix` to `/r` if your Gerrit is hosted at `https://review.host.com/r`
* set `ssh-port` parameter to set custom port for ssh connection to Gerrit (default: 29418)
* set `auth-type`: basic (default) or digest

### Development

For development, start a virtualenv and from within install the devel requirements:
Expand Down
11 changes: 6 additions & 5 deletions git_repo/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,17 +426,18 @@ def request_edition(repository, from_branch, onto_target):

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

new_request = service.request_create(self.namespace,
print_iter(service.request_create(
self.namespace,
self.repo_name,
self.local_branch,
self.remote_branch,
self.title,
self.message,
self._auto_slug,
request_edition)
log.info('Successfully created request of `{local}` onto `{project}:{remote}`, with id `{ref}`!'.format(**new_request))
if 'url' in new_request:
log.info('available at: {url}'.format(**new_request))
request_edition
)
)

return 0

@register_action('request', 'fetch')
Expand Down
15 changes: 8 additions & 7 deletions git_repo/services/ext/bitbucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,13 +294,14 @@ def request_create(self, onto_user, onto_repo, from_branch, onto_branch, title=N
client=self.bb.client
)

return {
'local': from_branch,
'remote': onto_branch,
'ref': request.id,
'project': '/'.join([onto_user, onto_repo]),
'url': request.links['html']['href']
}
yield '{}'
yield ['Successfully created request of `{local}` onto `{project}:{remote}, with id `{ref}'.format(
local=from_branch,
project='/'.join([onto_user, onto_repo]),
remote=onto_branch,
ref=request.id
)]
yield ['available at {}'.format(request.links['html']['href'])]

except HTTPError as err:
status_code = hasattr(err, 'code') and err.code or err.response.status_code
Expand Down
166 changes: 166 additions & 0 deletions git_repo/services/ext/gerrit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#!/usr/bin/env python

from ...exceptions import ResourceNotFoundError
from ..service import register_target, RepositoryService

from gerritclient import client
from gerritclient.error import HTTPError

@register_target('gerrit', 'gerrit')
class GerritService(RepositoryService):
fqdn = 'review.gerrithub.io'
auth_type = 'basic'
ssh_port = 29418
_max_nested_namespaces = 99
_min_nested_namespaces = 0
ro_suffix = ''

def create_connection(self):
self.connection = client.connect(self.url_ro, auth_type=self.auth_type,
username=self._username, password=self._privatekey)
self._session = self.connection.session

def connect(self):
if not hasattr(self, 'connection'):
self.create_connection()
self.server_client = client.get_client('server', connection=self.connection)
self.project_client = client.get_client('project', connection=self.connection)
self.change_client = client.get_client('change', connection=self.connection)

try:
self.server_client.get_version()
except HTTPError as err:
if not self._username or not self._privatekey:
raise ConnectionError('Could not connect to Gerrit. '
'Please configure .gitconfig '
'with your gerrit username and HTTP password.') from err
else:
raise ConnectionError('Could not connect to Gerrit. '
'Please check your configuration and try again.') from err

@classmethod
def get_auth_token(self, login, password, prompt=None):
# HTTP password is used as auth token
return password

def load_configuration(self, c, hc=[]):
super(GerritService, self).load_configuration(c, hc)
self.ssh_port = c.get('ssh-port', self.ssh_port)
self.auth_type = c.get('auth-type', self.auth_type)
self.ro_suffix = c.get('ro-suffix', self.ro_suffix)

@property
def session(self):
if not hasattr(self, '_session'):
self.create_connection()
return self._session

@property
def git_user(self):
return self._username

@property
def url_ro(self):
'''Property that returns the HTTP URL of the service'''
return self.build_url(self) + self.ro_suffix

@property
def url_rw(self):
return 'ssh:https://{}@{}:{}'.format(self.git_user, self.ssh_url, self.ssh_port)

def repo_name(self, namespace, repo):
if namespace:
return '{}/{}'.format(namespace, repo)
else:
return repo

def get_repository(self, namespace, repo):
if namespace is not None:
return self.project_client.get_by_name(self.repo_name(namespace, repo))
else:
return self.project_client.get_by_name(repo)

def get_project_default_branch(self, project):
branches = self.project_client.get_branches(project['name'])
for branch in branches:
if branch['ref'] == 'HEAD':
return branch['revision']

def is_repository_empty(self, project):
# There is no way to find out if repository is empty, so always return False
return False

def get_parent_project_url(self, namespace, repo, rw=True):
# Gerrit parent project concept is quite different from other services,
# so it is better to always return None here
return None

def request_create(self, onto_user, onto_repo, from_branch, onto_branch=None, title=None, description=None, auto_slug=False, edit=None):
from_branch = from_branch or self.repository.active_branch.name
onto_branch = onto_branch or 'HEAD:refs/for/' + from_branch
remote = self.repository.remote(self.name)
info, lines = self.push(remote, onto_branch)
new_changes = []
new_changes_lines = False
for line in lines:
if line.startswith('remote:'):
line = line[len('remote:'):].strip()

if 'New Changes' in line:
new_changes_lines = True

if new_changes_lines and self.fqdn in line:
url = line.split(' ')[0]
new_changes.append(url)

if len(new_changes) > 0:
yield '{}'
yield ['Created new review request of `{local}` onto `{project}:{remote}`'.format(
local = from_branch,
project = '/'.join([onto_user, onto_repo]),
remote = onto_branch
)]
for url in new_changes:
yield ['with changeset {} available at {}'.format(url, url.split('/')[-1])]
else:
yield '{}'
yield ['Review request of `{local}` was not created'.format(
local = from_branch
)]
for element in info:
yield ['{} -> {}: {}'.format(element.local_ref, element.remote_ref_string, element.summary)]

def request_fetch(self, user, repo, request, pull=False, force=False):
if 'refs/changes/' not in request:
if '/' in request:
change_id, patch_set = request.split('/')
else:
change_id = request
change = self.change_client.get_all(['change: {}'.format(change_id)], ['CURRENT_REVISION'])[0]
current_patchset = change['revisions'][change['current_revision']]
patch_set = current_patchset['_number']

if change_id[0] == 'I':
change_id = str(self.change_client.get_by_id(request)['_number'])

request = 'refs/changes/{}/{}/{}'.format(change_id[-2:], change_id, patch_set)
else:
change_id = request.split('/')[3]

try:
remote = self.repository.remote(self.name)
except ValueError as err:
raise Exception('Remote "{remote}" is not setup. Please run `git {remote} add`'.format(remote=self.name))
local_branch_name = 'requests/{}/{}'.format(self.name, change_id)
self.fetch(remote, request, local_branch_name, force=force)

return local_branch_name

def request_list(self, user, repo):
project = self.repo_name(user, repo)
changes = self.change_client.get_all(['project:{} status:open'.format(project)])

yield "{}\t{}\t{:<60}\t{}"
yield ['id', 'branch', 'subject', 'url']
for change in changes:
yield [change['_number'], change['branch'], change['subject'], '{}/{}'.format(self.url_ro, change['_number'])]
15 changes: 8 additions & 7 deletions git_repo/services/ext/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,13 +277,14 @@ def request_create(self, onto_user, onto_repo, from_branch, onto_branch, title=N
base=onto_branch,
body=description)

return {
'local': from_branch,
'project': '/'.join([onto_user, onto_repo]),
'remote': onto_branch,
'ref': request.number,
'url': request.html_url
}
yield '{}'
yield ['Successfully created request of `{local}` onto `{project}:{remote}, with id `{ref}'.format(
local=from_branch,
project='/'.join([onto_user, onto_repo]),
remote=onto_branch,
ref=request.number
)]
yield ['available at {}'.format(request.html_url)]

except github3.models.GitHubError as err:
if err.code == 422:
Expand Down
15 changes: 8 additions & 7 deletions git_repo/services/ext/gitlab.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,13 +313,14 @@ def request_create(self, onto_user, onto_repo, from_branch, onto_branch, title=N
}
)

return {
'local': from_branch,
'project': '/'.join([onto_user, onto_repo]),
'remote': onto_branch,
'url': request.web_url,
'ref': request.iid
}
yield '{}'
yield ['Successfully created request of `{local}` onto `{project}:{remote}, with id `{ref}'.format(
local=from_branch,
project='/'.join([onto_user, onto_repo]),
remote=onto_branch,
ref=request.iid
)]
yield ['available at {}'.format(request.web_url)]

except GitlabGetError as err:
raise ResourceNotFoundError(err) from err
Expand Down
44 changes: 37 additions & 7 deletions git_repo/services/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from git import RemoteProgress, config as git_config
from progress.bar import IncrementalBar as Bar

from enum import Enum
from urllib.parse import ParseResult
from subprocess import call

Expand All @@ -32,8 +33,18 @@

class ProgressBar(RemoteProgress): # pragma: no cover
'''Nice looking progress bar for long running commands'''
def setup(self, repo_name):
self.bar = Bar(message='Pulling from {}'.format(repo_name), suffix='')

class Action(Enum):
PULL = 1
PUSH = 2

def setup(self, repo_name, action=Action.PULL):
if action == ProgressBar.Action.PULL:
message = 'Pulling from {}'.format(repo_name)
elif action == ProgressBar.Action.PUSH:
message = 'Pushing to {}'.format(repo_name)

self.bar = Bar(message=message, suffix='')

def update(self, op_code, cur_count, max_count=100, message=''):
#log.info("{}, {}, {}, {}".format(op_code, cur_count, max_count, message))
Expand Down Expand Up @@ -70,6 +81,7 @@ class RepositoryService:
]

_max_nested_namespaces = 1
_min_nested_namespaces = 1

@staticmethod
def get_config_path():
Expand Down Expand Up @@ -287,9 +299,9 @@ def format_path(self, repository, namespace=None, rw=False):
if namespace:
repo = '{}/{}'.format(namespace, repository)

if not rw and '/' in repo:
if not rw and repo.count('/') >= self._min_nested_namespaces:
return '{}/{}'.format(self.url_ro, repo)
elif rw and '/' in repo:
elif rw and repo.count('/') >= self._min_nested_namespaces:
if self.url_rw.startswith('ssh:https://'):
return '{}/{}'.format(self.url_rw, repo)
else:
Expand All @@ -310,15 +322,33 @@ def pull(self, remote, branch=None):
remote.pull(progress=pb)
print()

def fetch(self, remote, remote_branch, local_branch, force=False):
def push(self, remote, branch=None):
'''Push a repository
:param remote: git-remote instance
:param branch: name of the branch to push
:return: PushInfo, git push output lines
'''
pb = ProgressBar()
pb.setup(self.name, ProgressBar.Action.PUSH)
if branch:
result = remote.push(branch, progress=pb)
else: #pragma: no cover
result = remote.push(progress=pb)
print()
return result, pb.other_lines

def fetch(self, remote, branch, local_branch = None, force=False):
'''Pull a repository
:param remote: git-remote instance
:param branch: name of the branch to pull
'''
pb = ProgressBar()
pb.setup(self.name)
remote.fetch(':'.join([remote_branch, local_branch]),
update_head_ok=True, force=force, progress=pb)

if local_branch:
branch = ':'.join([branch, local_branch])

remote.fetch(branch, update_head_ok=True, force=force, progress=pb)
print()

def clone(self, user, repo, branch=None, rw=True):
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ github3.py<1.0.0
python-gitlab>=1.0.0
gogs-client>=1.0.3
pybitbucket_fork>=0.12.2
python-gerritclient>=0.0.1dev137
Loading

0 comments on commit 2974c3f

Please sign in to comment.