Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
4302d1a
🚒 Project name creation mishandling
guyzmo Jul 8, 2017
ad9a4d9
Removed stale too many slashes tests
guyzmo Nov 18, 2017
693fb75
Fixes minor issue when catching too many nested namespaces
guyzmo Nov 18, 2017
e25cd55
FIXUP too many slashes
guyzmo Nov 18, 2017
e07aac0
Updates the gitlab test with proper data
guyzmo Nov 18, 2017
9844722
Forces usage of gitlab ≥ v1
guyzmo Nov 18, 2017
9897954
📚 Updated README
guyzmo Nov 18, 2017
18086ca
Merge branch 'devel' into fix/issues/167
guyzmo Nov 18, 2017
3ae548f
Prevent extract_config running on Python 2
jayvdb Nov 20, 2017
28c924c
Fix incorrect hash bang
jayvdb Nov 20, 2017
736f929
Use consistent python3 hashbang
jayvdb Nov 20, 2017
b594f65
Merge branch 'devel' into requests/github/169
guyzmo Nov 21, 2017
e80d990
Updated travis configuration to avoid py3.4 failures
guyzmo Nov 21, 2017
2bbaeb3
Add basic gerrit support (repo cloning)
nikitavbv Dec 10, 2017
398ed2b
Update README related to Bitbucket setup
AllanLRH Dec 19, 2017
00d42eb
Add gerrit review support
nikitavbv Dec 13, 2017
334ae82
Add gerrit patchset fetch support
nikitavbv Dec 16, 2017
ba43ef6
Add gerrit active changes list support
nikitavbv Dec 16, 2017
f88c565
Return generators instead of dicts in services request_create
nikitavbv Dec 16, 2017
0654425
Add Gerrit configuration section to README
nikitavbv Dec 20, 2017
d5d00c3
Add Gerrit test case for add action
nikitavbv Dec 20, 2017
2c784b2
Allow running git repo commands from sub directories in the repository
Crazybus Jan 12, 2018
f14a343
Merge branch 'devel' into yellow_submarine
Crazybus Jan 12, 2018
c40db4f
Merge branch 'requests/github/187' into devel
guyzmo Feb 3, 2018
9249201
Merge branch 'requests/github/179' into devel
guyzmo Feb 3, 2018
fe790c2
Merge branch 'requests/github/178' into devel
guyzmo Feb 3, 2018
9307526
Fix test typos
guyzmo Feb 4, 2018
9b20189
Handle cases when the remote is not yet setup.
guyzmo Feb 4, 2018
2974c3f
Merge branch 'requests/github/183' into devel
guyzmo Feb 4, 2018
92a7c0f
feat: Suggest adding SSH key when none is present
gemini-25-pro-collab Mar 30, 2026
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
8 changes: 3 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,25 @@ sudo: required
dist: trusty
matrix:
include:
- os: linux
python: "3.4"
- os: linux
python: "3.5"
- os: linux
python: "3.6"
- os: linux
python: "3.6-dev"
- os: linux
python: "nightly"
python: "3.7-dev"
- os: linux
python: "pypy3"
python: "nightly"

- os: osx
sudo: required
language: generic

allow_failures:
- python: "3.6-dev"
- python: "3.7-dev"
- python: "nightly"
- python: "pypy3"
- os: "osx"
addons:
apt:
Expand Down
21 changes: 15 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,23 +237,20 @@ section in the gitconfig:

[gitrepo "bitbucket"]
username = ford.prefect
token = YourOtherSecretKey
token = YourSecretAppKey

[gitrepo "gogs"]
fqdn = UrlOfYourGogs
token = YourVerySecretKey

Here, we're setting the basics: just the private token. You'll notice that for bitbucket
the private token is your username and password seperated by a column. That's because
bitbucket does not offer throw away private tokens for tools (I might implement BB's OAuth
at some point).
Here, we're setting the basics: just the private token. Notice that the token needed for Bitbucket are an App-token, not to be confused with an OAuth-token, which are also avaiable from the Butbucket settings.

You also have the ability to set up an alias:

[gitrepo "bitbucket"]
alias = bit
username = ford.prefect
token = YourOtherSecretKey
token = YourSecretAppKey

that will change the command you use for a name you'll prefer to handle actions
for the service you use:
Expand Down Expand Up @@ -295,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
2 changes: 1 addition & 1 deletion git_repo/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3

class ArgumentError(ValueError):
pass
Expand Down
2 changes: 2 additions & 0 deletions git_repo/extract_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ def extract_gitrepo_conf(gconf_old, gconf_new):
print("🍻 git-repo configuration extracted to new file: {}".format(gconf_new))

if __name__ == '__main__':
if sys.version_info < (3, ):
sys.exit('Please use with python version 3')
if '-h' in sys.argv or '--help' in sys.argv:
sys.exit('Usage: {} [.gitconfig-repos] [.gitconfig]'.format(sys.argv[0]))
sys.exit(extract_gitrepo_conf(
Expand Down
2 changes: 1 addition & 1 deletion git_repo/kwargparse.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3

import logging
log = logging.getLogger('git_repo.kwargparse')
Expand Down
22 changes: 13 additions & 9 deletions git_repo/repo.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env pytho
#!/usr/bin/env python3

'''
Usage:
Expand Down Expand Up @@ -159,9 +159,9 @@ def get_service(self, lookup_repository=True, resolve_targets=None):
# Try to resolve existing repository path
try:
try:
repository = Repo(os.path.join(self.path, self.repo_name or ''))
repository = Repo(os.path.join(self.path, self.repo_name or ''), search_parent_directories=True)
except NoSuchPathError:
repository = Repo(self.path)
repository = Repo(self.path, search_parent_directories=True)
except InvalidGitRepositoryError:
raise FileNotFoundError('Cannot find path to the repository.')
service = RepositoryService.get_service(repository, self.target)
Expand Down Expand Up @@ -326,7 +326,10 @@ def do_clone(self, service=None, repo_path=None):
except Exception as err:
if os.path.exists(repo_path):
shutil.rmtree(repo_path)
raise ResourceNotFoundError(err.args[2].decode('utf-8')) from err
if 'Permission denied' in str(err):
raise ResourceNotFoundError('Permission denied. Have you added your SSH key to the remote service?') from err
else:
raise ResourceNotFoundError(err.args[2].decode('utf-8')) from err

@register_action('create')
def do_create(self):
Expand Down Expand Up @@ -426,17 +429,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
2 changes: 1 addition & 1 deletion git_repo/services/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#!/usr/bin/env python
#!/usr/bin/env python3

17 changes: 9 additions & 8 deletions git_repo/services/ext/bitbucket.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3

import logging
log = logging.getLogger('git_repo.bitbucket')
Expand Down 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://{}@{}:{}'.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'])]
2 changes: 1 addition & 1 deletion git_repo/services/ext/gitbucket.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3

import logging
log = logging.getLogger('git_repo.gitbucket')
Expand Down
17 changes: 9 additions & 8 deletions git_repo/services/ext/github.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3

import logging
log = logging.getLogger('git_repo.github')
Expand Down 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
Loading