Skip to content

Commit bd7927c

Browse files
committed
👍 Merge branch 'devel' ; 🔖 Version bump to v1.7.2
* 🚧 fixed #34: refactor `fork` command * 🚧 fixed #32: added support of full URL as repo_slug * 🚧 fixed #33: add target directory name for clone and fork Signed-off-by: Guyzmo <[email protected]>
2 parents e516f84 + 8493155 commit bd7927c

File tree

11 files changed

+228
-249
lines changed

11 files changed

+228
-249
lines changed

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.7.1
1+
1.7.2

git_repo/repo.py

Lines changed: 90 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22

33
'''
44
Usage:
5-
{self} [--path=<path>] [-v...] <target> fork [<branch>] [--clone]
5+
{self} [--path=<path>] [-v...] <target> fork [--branch=<branch>]
66
{self} [--path=<path>] [-v...] <target> create [--add]
77
{self} [--path=<path>] [-v...] <target> delete [-f]
88
{self} [--path=<path>] [-v...] <target> open
9-
{self} [--path=<path>] [-v...] <target> fork <user>/<repo> [<branch>] [--clone]
9+
{self} [--path=<path>] [-v...] <target> fork <user>/<repo> [--branch=<branch>]
10+
{self} [--path=<path>] [-v...] <target> fork <user>/<repo> <repo> [--branch=<branch>]
1011
{self} [--path=<path>] [-v...] <target> create <user>/<repo> [--add]
1112
{self} [--path=<path>] [-v...] <target> delete <user>/<repo> [-f]
1213
{self} [--path=<path>] [-v...] <target> open <user>/<repo>
13-
{self} [--path=<path>] [-v...] <target> clone <user>/<repo> [<branch>]
14+
{self} [--path=<path>] [-v...] <target> clone <user>/<repo> [<repo> [<branch>]]
1415
{self} [--path=<path>] [-v...] <target> add <user>/<repo> [<name>] [--tracking=<branch>] [-a]
1516
{self} [--path=<path>] [-v...] <target> request (list|ls)
1617
{self} [--path=<path>] [-v...] <target> request fetch <request>
@@ -55,9 +56,14 @@
5556
-t,--tracking=<branch> Makes this remote tracking for the current branch
5657
-a,--alone Does not add the remote to the 'all' remote
5758
58-
Options for fork and clone:
59-
<branch> Branch to pull (when cloning) [default: master]
60-
--clone Clone locally after fork
59+
Option for both clone and fork:
60+
<repo> Name of the local workspace directory
61+
62+
Options for clone:
63+
<branch> Branch to pull [default: master]
64+
65+
Options for fork:
66+
--branch=<branch> Branch to pull [default: master]
6167
6268
Options for create:
6369
--add Add to local repository after creation
@@ -128,6 +134,10 @@
128134
from git import Repo, Git
129135
from git.exc import InvalidGitRepositoryError, NoSuchPathError
130136

137+
import re
138+
139+
EXTRACT_URL_RE = re.compile('[^:]*(://|@)[^/]*/')
140+
131141
def confirm(what, where):
132142
'''
133143
Method to show a CLI based confirmation message, waiting for a yes/no answer.
@@ -155,19 +165,20 @@ def _guess_repo_slug(self, repository, service):
155165
config = repository.config_reader()
156166
target = service.name
157167
for remote in repository.remotes:
158-
for url in remote.urls:
159-
if url.startswith('https'):
160-
if '.git' in url:
161-
url = url[:-4]
162-
*_, user, name = url.split('/')
163-
self.set_repo_slug('/'.join([user, name]))
164-
break
165-
elif url.startswith('git@'):
166-
if '.git' in url:
167-
url = url[:-4]
168-
_, repo_slug = url.split(':')
169-
self.set_repo_slug(repo_slug)
170-
break
168+
if remote.name in (target, 'upstream', 'origin'):
169+
for url in remote.urls:
170+
if url.startswith('https'):
171+
if '.git' in url:
172+
url = url[:-4]
173+
*_, user, name = url.split('/')
174+
self.set_repo_slug('/'.join([user, name]))
175+
break
176+
elif url.startswith('git@'):
177+
if '.git' in url:
178+
url = url[:-4]
179+
_, repo_slug = url.split(':')
180+
self.set_repo_slug(repo_slug)
181+
break
171182

172183
def get_service(self, lookup_repository=True):
173184
if not lookup_repository:
@@ -217,18 +228,19 @@ def set_verbosity(self, verbose): # pragma: no cover
217228

218229
@store_parameter('<user>/<repo>')
219230
def set_repo_slug(self, repo_slug):
220-
self.repo_slug = repo_slug
221-
if not repo_slug:
231+
self.repo_slug = EXTRACT_URL_RE.sub('', repo_slug) if repo_slug else repo_slug
232+
if not self.repo_slug:
222233
self.user_name = None
223234
self.repo_name = None
224-
elif '/' in repo_slug:
225-
self.user_name, self.repo_name, *overflow = repo_slug.split('/')
235+
elif '/' in self.repo_slug:
236+
# in case a full URL is given as parameter, just extract the slug part.
237+
self.user_name, self.repo_name, *overflow = self.repo_slug.split('/')
226238
if len(overflow) != 0:
227239
raise ArgumentError('Too many slashes.'
228240
'Format of the parameter is <user>/<repo> or <repo>.')
229241
else:
230242
self.user_name = None
231-
self.repo_name = repo_slug
243+
self.repo_name = self.repo_slug
232244

233245
@store_parameter('<branch>')
234246
def set_branch(self, branch):
@@ -238,6 +250,19 @@ def set_branch(self, branch):
238250

239251
self.branch = branch
240252

253+
@store_parameter('<branch>')
254+
@store_parameter('--branch')
255+
def set_branch(self, branch):
256+
# FIXME workaround for default value that is not correctly parsed in docopt
257+
if branch == None:
258+
branch = 'master'
259+
260+
self.branch = branch
261+
262+
@store_parameter('<repo>')
263+
def set_target_repo(self, repo):
264+
self.target_repo = repo
265+
241266
@store_parameter('<name>')
242267
def set_name(self, name):
243268
self.remote_name = name
@@ -267,69 +292,61 @@ def do_remote_add(self):
267292

268293
@register_action('fork')
269294
def do_fork(self):
270-
def clone_repo():
271-
try:
272-
repo_path = os.path.join(self.path, self.repo_name)
273-
repository = Repo(repo_path)
274-
except (InvalidGitRepositoryError, NoSuchPathError):
275-
repo_path = os.path.join(self.path, self.repo_name)
276-
repository = Repo.init(repo_path)
277-
return repository, repo_path
278-
279-
def lookup_repo():
295+
if not self.repo_slug:
296+
service = self.get_service(lookup_repository=True)
280297
try:
281298
repo_path = self.path
282299
repository = Repo(repo_path)
283300
except (InvalidGitRepositoryError, NoSuchPathError):
301+
raise ArgumentError('Path {} is not a git repository'.format(self.path))
302+
303+
else:
304+
# git <target> fork <user>/<repo>
305+
if not self.target_repo:
306+
if not self.user_name:
307+
raise ArgumentError('Cannot clone repository, '
308+
'you shall provide either a <user>/<repo> parameter '
309+
'or no parameters to fork current repository!')
310+
service = self.get_service(None)
311+
312+
# git <target> fork <user>/<repo> <path>
313+
else:
314+
repo_path = os.path.join(self.path, self.target_repo)
284315
try:
285-
repo_path = os.path.join(self.path, self.repo_name)
286-
repository = Repo(repo_path)
316+
service = RepositoryService.get_service(Repo(repo_path), self.target)
287317
except (InvalidGitRepositoryError, NoSuchPathError):
288-
return None, None
289-
return repository, repo_path
318+
service = self.get_service(lookup_repository=False)
319+
# if the repository does not exists at given path, clone upstream into that path
320+
self.do_clone(service, repo_path)
290321

291-
service = self.get_service(lookup_repository=self.repo_slug == None)
292-
if not self.repo_name and not self.user_name:
293-
raise ArgumentError('Cannot clone repository, '
294-
'you shall provide the <user>/<repo> parameter '
295-
'or run the command from a git repository!')
296-
if not self.clone:
297-
repository, repo_path = lookup_repo()
298-
if repository == None:
299-
repository, repo_path = clone_repo()
300-
self.clone = True
301-
else:
302-
repository, repo_path = clone_repo()
303-
service = RepositoryService.get_service(repository, self.target)
304-
service.fork(self.user_name, self.repo_name, branch=self.branch, clone=self.clone)
305-
if self.clone:
306-
log.info('Successfully cloned repository {} in {}'.format(
307-
self.repo_slug,
308-
repo_path)
309-
)
310-
else:
311-
log.info('Successfully added repository {} as a remote to {}'.format(
312-
self.repo_slug,
313-
repo_path)
314-
)
322+
service.run_fork(self.user_name, self.repo_name, branch=self.branch)
323+
324+
if not self.repo_slug or self.target_repo:
325+
log.info('Successfully forked {} as {} within {}.'.format(
326+
self.repo_slug, '/'.join([service.username, self.repo_name]), repo_path))
315327

316328
return 0
317329

318330
@register_action('clone')
319-
def do_clone(self):
320-
service = self.get_service(lookup_repository=False)
321-
repo_path = os.path.join(self.path, self.repo_name)
331+
def do_clone(self, service=None, repo_path=None):
332+
service = service or self.get_service(lookup_repository=False)
333+
repo_path = repo_path or os.path.join(self.path, self.target_repo or self.repo_name)
322334
if os.path.exists(repo_path):
323335
raise FileExistsError('Cannot clone repository, '
324336
'a folder named {} already exists!'.format(repo_path))
325-
repository = Repo.init(repo_path)
326-
service = RepositoryService.get_service(repository, self.target)
327-
service.clone(self.user_name, self.repo_name, self.branch)
328-
log.info('Successfully cloned `{}` into `{}`!'.format(
329-
service.format_path(self.repo_slug),
330-
repo_path)
331-
)
332-
return 0
337+
try:
338+
repository = Repo.init(repo_path)
339+
service = RepositoryService.get_service(repository, self.target)
340+
service.clone(self.user_name, self.repo_name, self.branch)
341+
log.info('Successfully cloned `{}` into `{}`!'.format(
342+
service.format_path(self.repo_slug),
343+
repo_path)
344+
)
345+
return 0
346+
except Exception as err:
347+
if os.path.exists(repo_path):
348+
os.removedirs(repo_path)
349+
raise err from err
333350

334351
@register_action('create')
335352
def do_create(self):

git_repo/services/ext/bitbucket.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ def connect(self):
104104
if not ':' in self._privatekey:
105105
raise ConnectionError('Could not connect to BitBucket. Please setup your private key with login:password')
106106
self.bb.username, self.bb.password = self._privatekey.split(':')
107+
self.username = self.bb.username
107108
result, _ = self.bb.get_user()
108109
if not result:
109110
raise ConnectionError('Could not connect to BitBucket. Not authorized, wrong credentials.')
@@ -117,21 +118,11 @@ def create(self, user, repo, add=False):
117118
if add:
118119
self.add(user=user, repo=repo, tracking=self.name)
119120

120-
def fork(self, user, repo, branch='master', clone=False):
121-
log.info("Forking repository {}/{}…".format(user, repo))
122-
repositories = self.get_repository(user, repo)
123-
if repo in repositories:
124-
raise ResourceExistsError('Cannot fork repository as it already exists')
121+
def fork(self, user, repo):
125122
success, result = self.bb.fork(user, repo)
126123
if not success:
127124
raise ResourceError("Couldn't complete fork: {message} (error #{code}: {reason})".format(**result))
128-
fork = result
129-
self.add(repo=repo, user=user, name='upstream', alone=True)
130-
remote = self.add(repo=fork['slug'], user=fork['owner'], tracking=self.name)
131-
if clone:
132-
self.pull(remote, branch)
133-
log.info("New forked repository available at {}".format(self.format_path(repository=fork['slug'],
134-
namespace=fork['owner'])))
125+
return '/'.join([result['owner'], result['slug']])
135126

136127
def delete(self, repo, user=None):
137128
if not user:

git_repo/services/ext/github.py

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def connect(self):
2222
try:
2323
self.gh.login(token=self._privatekey)
2424
self.username = self.gh.user().name
25+
self.username = self.gh.user().name
2526
except github3.models.GitHubError as err:
2627
if err.code is 401:
2728
if not self._privatekey:
@@ -45,35 +46,14 @@ def create(self, user, repo, add=False):
4546
if add:
4647
self.add(user=self.username, repo=repo, tracking=self.name)
4748

48-
def fork(self, user, repo, branch='master', clone=False):
49-
log.info("Forking repository {}/{}…".format(user, repo))
50-
# checking for an 'upstream' remote.
51-
upstream_remotes = list(filter(lambda x: x.name == 'upstream', self.repository.remotes))
52-
if len(upstream_remotes) != 0:
53-
raise ResourceExistsError('A remote named `upstream` already exists. Has this repo already been forked?')
54-
# forking the repository on the service
49+
def fork(self, user, repo):
5550
try:
56-
fork = self.gh.repository(user, repo).create_fork()
51+
return self.gh.repository(user, repo).create_fork().full_name
5752
except github3.models.GitHubError as err:
5853
if err.message == 'name already exists on this account':
5954
raise ResourceExistsError("Project already exists.") from err
6055
else: # pragma: no cover
6156
raise ResourceError("Unhandled error: {}".format(err)) from err
62-
# checking if a remote with the service's name already exists
63-
service_remotes = list(filter(lambda x: x.name == self.name, self.repository.remotes))
64-
if len(service_remotes) != 0:
65-
# if it does, rename it to upstream
66-
repo.delete(service_remotes[0])
67-
repo.create_remote('upstream', service_remotes[0].url)
68-
else:
69-
# otherwise create an upstream remote with the source repository
70-
self.add(user=user, repo=repo, name='upstream', alone=True)
71-
# add the service named repository
72-
remote = self.add(repo=repo, user=self.username, tracking=self.name)
73-
if clone:
74-
self.pull(remote, branch)
75-
log.info("New forked repository available at {}/{}".format(self.url_ro,
76-
fork.full_name))
7757

7858
def delete(self, repo, user=None):
7959
if not user:

git_repo/services/ext/gitlab.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def __init__(self, *args, **kwarg):
2222
def connect(self):
2323
self.gl.set_token(self._privatekey)
2424
self.gl.token_auth()
25+
self.username = self.gl.user.username
2526

2627
def create(self, user, repo, add=False):
2728
try:
@@ -38,20 +39,14 @@ def create(self, user, repo, add=False):
3839
if add:
3940
self.add(user=user, repo=repo, tracking=self.name)
4041

41-
def fork(self, user, repo, branch='master', clone=False):
42+
def fork(self, user, repo):
4243
try:
43-
fork = self.gl.projects.get('{}/{}'.format(user, repo)).forks.create({})
44+
return self.gl.projects.get('{}/{}'.format(user, repo)).forks.create({}).path_with_namespace
4445
except GitlabCreateError as err:
4546
if json.loads(err.response_body.decode('utf-8'))['message']['name'][0] == 'has already been taken':
4647
raise ResourceExistsError("Project already exists.") from err
4748
else:
4849
raise ResourceError("Unhandled error: {}".format(err)) from err
49-
self.add(user=user, repo=repo, name='upstream', alone=True)
50-
remote = self.add(repo=fork.path, user=fork.namespace['path'], tracking=self.name)
51-
if clone:
52-
self.pull(remote, branch)
53-
log.info("New forked repository available at {}/{}".format(self.url_ro,
54-
fork.path_with_namespace))
5550

5651
def delete(self, repo, user=None):
5752
if not user:

0 commit comments

Comments
 (0)