Skip to content

Commit 587e230

Browse files
committed
refactor(base,git,hg,svn): Decouple away pip url stuff
1 parent d9cf725 commit 587e230

File tree

6 files changed

+77
-65
lines changed

6 files changed

+77
-65
lines changed

libvcs/base.py

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,36 @@
44

55
import logging
66
import os
7+
from typing import NamedTuple
78

89
from ._compat import implements_to_string, urlparse
910
from .util import RepoLoggingAdapter, mkdir_p, run
1011

1112
logger = logging.getLogger(__name__)
1213

1314

15+
class VCSLocation(NamedTuple):
16+
url: str
17+
rev: str
18+
19+
20+
def convert_pip_url(pip_url: str) -> VCSLocation:
21+
"""Return repo URL and revision by parsing `libvcs.base.BaseRepo.url`."""
22+
error_message = (
23+
"Sorry, '%s' is a malformed VCS url. "
24+
"The format is <vcs>+<protocol>://<url>, "
25+
"e.g. svn+http://myrepo/svn/MyApp#egg=MyApp"
26+
)
27+
assert '+' in pip_url, error_message % pip_url
28+
url = pip_url.split('+', 1)[1]
29+
scheme, netloc, path, query, frag = urlparse.urlsplit(url)
30+
rev = None
31+
if '@' in path:
32+
path, rev = path.rsplit('@', 1)
33+
url = urlparse.urlunsplit((scheme, netloc, path, query, ''))
34+
return VCSLocation(url=url, rev=rev)
35+
36+
1437
@implements_to_string
1538
class BaseRepo(RepoLoggingAdapter, object):
1639

@@ -56,7 +79,7 @@ def __init__(self, url, repo_dir, progress_callback=None, *args, **kwargs):
5679

5780
@classmethod
5881
def from_pip_url(cls, pip_url, *args, **kwargs):
59-
url, rev = cls.get_url_and_revision_from_pip_url(pip_url)
82+
url, rev = convert_pip_url(pip_url)
6083
self = cls(url=url, rev=rev, *args, **kwargs)
6184

6285
return self
@@ -122,22 +145,5 @@ def check_destination(self, *args, **kwargs):
122145

123146
return True
124147

125-
@classmethod
126-
def get_url_and_revision_from_pip_url(cls, pip_url):
127-
"""Return repo URL and revision by parsing `libvcs.base.BaseRepo.url`."""
128-
error_message = (
129-
"Sorry, '%s' is a malformed VCS url. "
130-
"The format is <vcs>+<protocol>://<url>, "
131-
"e.g. svn+http://myrepo/svn/MyApp#egg=MyApp"
132-
)
133-
assert '+' in pip_url, error_message % pip_url
134-
url = pip_url.split('+', 1)[1]
135-
scheme, netloc, path, query, frag = urlparse.urlsplit(url)
136-
rev = None
137-
if '@' in path:
138-
path, rev = path.rsplit('@', 1)
139-
url = urlparse.urlunsplit((scheme, netloc, path, query, ''))
140-
return url, rev
141-
142148
def __repr__(self):
143149
return "<{} {}>".format(self.__class__.__name__, self.repo_name)

libvcs/git.py

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
From pip (MIT Licnese):
1010
1111
- [`GitRepo.remote`](libvcs.git.GitRepo.remote_set) (renamed to ``set_remote``)
12-
- [`GitRepo.get_url_and_revision_from_pip_url`](libvcs.git.GitRepo.get_url_and_revision_from_pip_url`) (``get_url_rev``)
12+
- [`GitRepo.convert_pip_url`](libvcs.git.GitRepo.convert_pip_url`) (``get_url_rev``)
1313
- [`GitRepo.get_revision`](libvcs.git.GitRepo.get_revision)
1414
- [`GitRepo.get_git_version`](libvcs.git.GitRepo.get_git_version)
1515
""" # NOQA: E501
@@ -22,7 +22,7 @@
2222

2323
from . import exc
2424
from ._compat import urlparse
25-
from .base import BaseRepo
25+
from .base import BaseRepo, VCSLocation, convert_pip_url as base_convert_pip_url
2626

2727
logger = logging.getLogger(__name__)
2828

@@ -102,6 +102,31 @@ def extract_status(value) -> GitStatus:
102102
return GitStatus(**matches.groupdict())
103103

104104

105+
def convert_pip_url(pip_url: str) -> VCSLocation:
106+
"""
107+
Prefixes stub URLs like 'user@hostname:user/repo.git' with 'ssh://'.
108+
That's required because although they use SSH they sometimes doesn't
109+
work with a ssh:// scheme (e.g. Github). But we need a scheme for
110+
parsing. Hence we remove it again afterwards and return it as a stub.
111+
The manpage for git-clone(1) refers to this as the "scp-like styntax".
112+
"""
113+
if '://' not in pip_url:
114+
assert 'file:' not in pip_url
115+
pip_url = pip_url.replace('git+', 'git+ssh://')
116+
url, rev = base_convert_pip_url(pip_url)
117+
url = url.replace('ssh://', '')
118+
elif 'github.com:' in pip_url:
119+
raise exc.LibVCSException(
120+
"Repo %s is malformatted, please use the convention %s for"
121+
"ssh / private GitHub repositories."
122+
% (pip_url, "git+https://github.com/username/repo.git")
123+
)
124+
else:
125+
url, rev = base_convert_pip_url(pip_url)
126+
127+
return VCSLocation(url=url, rev=rev)
128+
129+
105130
class GitRepo(BaseRepo):
106131
bin_name = 'git'
107132
schemes = ('git', 'git+http', 'git+https', 'git+ssh', 'git+git', 'git+file')
@@ -126,38 +151,20 @@ def __init__(self, url, **kwargs):
126151

127152
BaseRepo.__init__(self, url, **kwargs)
128153

154+
@classmethod
155+
def from_pip_url(cls, pip_url, *args, **kwargs):
156+
url, rev = convert_pip_url(pip_url)
157+
self = cls(url=url, rev=rev, *args, **kwargs)
158+
159+
return self
160+
129161
def get_revision(self):
130162
"""Return current revision. Initial repositories return 'initial'."""
131163
try:
132164
return self.run(['rev-parse', '--verify', 'HEAD'])
133165
except exc.CommandError:
134166
return 'initial'
135167

136-
@classmethod
137-
def get_url_and_revision_from_pip_url(cls, pip_url):
138-
"""
139-
Prefixes stub URLs like 'user@hostname:user/repo.git' with 'ssh://'.
140-
That's required because although they use SSH they sometimes doesn't
141-
work with a ssh:// scheme (e.g. Github). But we need a scheme for
142-
parsing. Hence we remove it again afterwards and return it as a stub.
143-
The manpage for git-clone(1) refers to this as the "scp-like styntax".
144-
"""
145-
if '://' not in pip_url:
146-
assert 'file:' not in pip_url
147-
pip_url = pip_url.replace('git+', 'git+ssh://')
148-
url, rev = super(GitRepo, cls).get_url_and_revision_from_pip_url(pip_url)
149-
url = url.replace('ssh://', '')
150-
elif 'github.com:' in pip_url:
151-
raise exc.LibVCSException(
152-
"Repo %s is malformatted, please use the convention %s for"
153-
"ssh / private GitHub repositories."
154-
% (pip_url, "git+https://github.com/username/repo.git")
155-
)
156-
else:
157-
url, rev = super(GitRepo, cls).get_url_and_revision_from_pip_url(pip_url)
158-
159-
return url, rev
160-
161168
def obtain(self):
162169
"""Retrieve the repository, clone if doesn't exist."""
163170
self.check_destination()

libvcs/hg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
44
The following is from pypa/pip (MIT license):
55
6-
- [`MercurialRepo.get_url_and_revision_from_pip_url`](libvcs.hg.get_url_and_revision_from_pip_url)
6+
- [`MercurialRepo.convert_pip_url`](libvcs.hg.convert_pip_url)
77
- [`MercurialRepo.get_url`](libvcs.hg.MercurialRepo.get_url)
88
- [`MercurialRepo.get_revision`](libvcs.hg.MercurialRepo.get_revision)
99
""" # NOQA E5

libvcs/svn.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
99
The following are pypa/pip (MIT license):
1010
11-
- [`SubversionRepo.get_url_and_revision_from_pip_url`](libvcs.svn.SubversionRepo.get_url_and_revision_from_pip_url)
11+
- [`SubversionRepo.convert_pip_url`](libvcs.svn.SubversionRepo.convert_pip_url)
1212
- [`SubversionRepo.get_url`](libvcs.svn.SubversionRepo.get_url)
1313
- [`SubversionRepo.get_revision`](libvcs.svn.SubversionRepo.get_revision)
1414
- [`get_rev_options`](libvcs.svn.get_rev_options)
@@ -20,11 +20,19 @@
2020
import re
2121

2222
from ._compat import urlparse
23-
from .base import BaseRepo
23+
from .base import BaseRepo, VCSLocation, convert_pip_url as base_convert_pip_url
2424

2525
logger = logging.getLogger(__name__)
2626

2727

28+
def convert_pip_url(pip_url: str) -> VCSLocation:
29+
# hotfix the URL scheme after removing svn+ from svn+ssh:// re-add it
30+
url, rev = base_convert_pip_url(pip_url)
31+
if url.startswith('ssh://'):
32+
url = 'svn+' + url
33+
return VCSLocation(url=url, rev=rev)
34+
35+
2836
class SubversionRepo(BaseRepo):
2937
bin_name = 'svn'
3038
schemes = ('svn', 'svn+ssh', 'svn+http', 'svn+https', 'svn+svn')
@@ -115,14 +123,6 @@ def get_revision(self, location=None):
115123
revision = max(revision, localrev)
116124
return revision
117125

118-
@classmethod
119-
def get_url_and_revision_from_pip_url(cls, pip_url):
120-
# hotfix the URL scheme after removing svn+ from svn+ssh:// re-add it
121-
url, rev = super(SubversionRepo, cls).get_url_and_revision_from_pip_url(pip_url)
122-
if url.startswith('ssh://'):
123-
url = 'svn+' + url
124-
return url, rev
125-
126126
def update_repo(self, dest=None):
127127
self.check_destination()
128128
if os.path.isdir(os.path.join(self.path, '.svn')):

tests/test_base.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import absolute_import, print_function, unicode_literals
44

55
from libvcs._compat import text_type
6-
from libvcs.base import BaseRepo
6+
from libvcs.base import BaseRepo, convert_pip_url
77
from libvcs.shortcuts import create_repo
88

99

@@ -34,10 +34,8 @@ def test_check_destination_creates_parent_if_not_exist(tmpdir):
3434
assert parentdir.check()
3535

3636

37-
def test_get_url_and_revision_from_pip_url():
38-
url, rev = BaseRepo.get_url_and_revision_from_pip_url(
39-
pip_url='git+file://path/to/myrepo@therev'
40-
)
37+
def test_convert_pip_url():
38+
url, rev = convert_pip_url(pip_url='git+file://path/to/myrepo@therev')
4139

4240
assert url, rev == 'therev'
4341
assert url, rev == 'file://path/to/myrepo'

tests/test_git.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from libvcs import exc
1212
from libvcs._compat import PY2, string_types
13-
from libvcs.git import GitRemote, GitRepo, extract_status
13+
from libvcs.git import GitRemote, convert_pip_url as git_convert_pip_url, extract_status
1414
from libvcs.shortcuts import create_repo_from_pip_url
1515
from libvcs.util import run, which
1616

@@ -120,22 +120,23 @@ def test_remotes(parentdir, git_remote):
120120

121121
def test_git_get_url_and_rev_from_pip_url():
122122
pip_url = 'git+ssh://git@bitbucket.example.com:7999/PROJ/repo.git'
123-
url, rev = GitRepo.get_url_and_revision_from_pip_url(pip_url)
123+
124+
url, rev = git_convert_pip_url(pip_url)
124125
assert 'ssh://git@bitbucket.example.com:7999/PROJ/repo.git' == url
125126
assert rev is None
126127

127128
pip_url = '%s@%s' % (
128129
'git+ssh://git@bitbucket.example.com:7999/PROJ/repo.git',
129130
'eucalyptus',
130131
)
131-
url, rev = GitRepo.get_url_and_revision_from_pip_url(pip_url)
132+
url, rev = git_convert_pip_url(pip_url)
132133
assert 'ssh://git@bitbucket.example.com:7999/PROJ/repo.git' == url
133134
assert rev == 'eucalyptus'
134135

135136
# the git manual refers to this as "scp-like syntax"
136137
# https://git-scm.com/docs/git-clone
137138
pip_url = '%s@%s' % ('git+user@hostname:user/repo.git', 'eucalyptus')
138-
url, rev = GitRepo.get_url_and_revision_from_pip_url(pip_url)
139+
url, rev = git_convert_pip_url(pip_url)
139140
assert 'user@hostname:user/repo.git' == url
140141
assert rev == 'eucalyptus'
141142

0 commit comments

Comments
 (0)