Skip to content

Commit

Permalink
clone: Get smarter at interpretting URLs
Browse files Browse the repository at this point in the history
Some very half baked support to use full GitHub clone URLs was added to
clone at some point, but this support is insuficient. It completely
ignores the URL itself so it will always clone using the current
`hub.urltype` scheme (which defaults to `ssh_url` and is probably too
obscure to ask the user to manually change it).

This commit makes the clone command to parse the URLs more intelligently
and set the `hub.urltype` config option as appropriate (it seems more
reasonable to think the user will like to use that instead, not only for
the cloning but also for other future operations, like pushing).

Fixes sociomantic-tsunami#149.
  • Loading branch information
Leandro Lucarella committed Nov 9, 2015
1 parent 340ef56 commit 0e7eeab
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 37 deletions.
91 changes: 55 additions & 36 deletions git-hub
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,7 @@ class SetupCmd (object):
# `git hub clone` command implementation
class CloneCmd (object):

cmd_required_config = ['username', 'urltype', 'oauthtoken']
cmd_required_config = ['username', 'oauthtoken']
cmd_help = 'clone a GitHub repository (and fork as needed)'
cmd_usage = '%(prog)s [OPTIONS] [GIT CLONE OPTIONS] REPO [DEST]'

Expand All @@ -757,25 +757,15 @@ class CloneCmd (object):

@classmethod
def run(cls, parser, args):
upstream = urlparse.urlsplit(args.repository).path
if upstream.endswith('.git'): upstream = upstream[:-4]
if '/' in upstream:
(owner, proj) = upstream.split('/')[-2:]
if owner == config.username:
(repo, upstream) = cls.set_own_repo_params(proj)
else: # not own repo
(repo, upstream) = cls.set_not_own_repo_params(
'{}/{}'.format(owner, proj))
else: # no '/' in upstream, so it's an own repo
(repo, upstream) = cls.set_own_repo_params(upstream)
(urltype, proj) = cls.parse_repo(args.repository)
(repo, upstream) = cls.setup_repo(proj)
dest = args.dest or repo['name']
triangular = cls.check_triangular(config.triangular or
args.triangular)
if triangular and not upstream:
parser.error("Can't use triangular workflow without "
"an upstream repo")
url = repo['parent'][config.urltype] if triangular \
else repo[config.urltype]
url = repo['parent'][urltype] if triangular else repo[urltype]
infof('Cloning {} to {}', url, dest)
git_quiet(1, 'clone', *(args.unknown_args + [url, dest]))
if not upstream:
Expand All @@ -784,39 +774,68 @@ class CloneCmd (object):
# Complete the repository setup
os.chdir(dest)
remote = args.remote or ('fork' if triangular else 'upstream')
remote_url = repo['parent'][config.urltype]
remote_url = repo['parent'][urltype]
if triangular:
remote_url = repo[config.urltype]
remote_url = repo[urltype]
git_config('remote.pushdefault', prefix='', value=remote)
git_config('forkremote', value=remote)
git_config('urltype', value=urltype)
git_config('upstream', value=upstream)
git('remote', 'add', remote, remote_url)
infof('Fetching from {} ({})', remote, remote_url)
git_quiet(1, 'fetch', remote)

@classmethod
def set_own_repo_params(cls, upstream):
repo = req.get('/repos/%s/%s' % (config.username,
upstream))
if not repo['fork']:
warnf('Repository {} is not a fork, just '
'cloning, upstream will not be set',
repo['full_name'])
upstream = None
else:
upstream = repo['parent']['full_name']
return (repo, upstream)
def parse_repo(cls, repo):
# None means the URL was specified as just 'owner/repo' or
# plain 'repo'
urltype = config.urltype
if repo.endswith('.git'):
repo = repo[:-4] # remove suffix
if repo.startswith('https://'):
urltype = 'clone_url' # how GitHub calls HTTP
elif repo.startswith('git:'):
urltype = 'git_url'
elif ':' in repo:
urltype = 'ssh_url'
elif repo.startswith('https://'): # and it doesn't end with .git
urltype = 'svn_url'
# At this point we need to have an urltype
if urltype is None:
die("Can't infer a urltype and can't find the config "
"key '{}{}' config key in git config. "
"Read the man page for details.")
# If no / was found, then we assume the owner is the user
if '/' not in repo:
repo = config.username + '/' + repo
# Get just the owner/repo form from the full URL
url = urlparse.urlsplit(repo)
proj = '/'.join(url.path.split(':')[-1:][0].split('/')[-2:])
return (urltype, proj)

@classmethod
def set_not_own_repo_params(cls, upstream):
for repo in req.get('/user/repos'):
if repo['name'] == proj and repo['fork']:
break
else: # Fork not found
infof('Forking {} to {}/{}', upstream,
config.username, proj)
repo = req.post('/repos/' + upstream + '/forks')
repo = req.get('/repos/' + repo['full_name'])
def setup_repo(cls, proj):
# Own repo
if proj.split('/')[0] == config.username:
repo = req.get('/repos/%s/%s' % (config.username, proj))
if repo['fork']:
upstream = repo['parent']['full_name']
else:
upstream = None
warnf('Repository {} is not a fork, just '
'cloning, upstream will not be set',
repo['full_name'])
else:
upstream = proj
repo_name = proj.split('/')[1]
for repo in req.get('/user/repos'):
if repo['name'] == repo_name and repo['fork']:
break
else: # Fork not found
infof('Forking {} to {}/{}', upstream,
config.username, repo_name)
repo = req.post('/repos/' + upstream + '/forks')
repo = req.get('/repos/' + repo['full_name'])
return (repo, upstream)

@classmethod
Expand Down
4 changes: 3 additions & 1 deletion man.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ COMMANDS
repository is specified in *<owner>/<project>* form, the **REPO** will be
used as upstream and a personal fork will be looked up. If none is found,
a new fork will be created. In both cases, the fork will be cloned instead of
the upstream repository.
the upstream repository. The **REPO** can be specified as a regular *clone*
URL too (svn, http, ssh, git), in that case the URL will be inspected and the
`hub.urltype` will be set as appropriate.

If only *<project>* is specified as **REPO**, then the configuration
`hub.username` is used as *<owner>*, and the parent repository is looked up
Expand Down

0 comments on commit 0e7eeab

Please sign in to comment.