Repository: pypa/pip

Issue:
Checkout from svn repository with authentication failing.
It looks like the command generated to check out the subversion project is incorrect.  The failing command reported below (`/usr/bin/svn checkout -q --username <svnuser> --password <svnpass> https://<svnuser>:<svnpass>@mysubversionserver.org/svn/dir1/dir2/svn-project/tags/v0.2 /tmp/pip_build_jivan/svn-project`) works if "`<svnuser>:<svnpass>@`" is removed from the url.

> pip install svn+https://`<svnuser>:<svnpass>@`mysubversionserver.org/svn/dir1/dir2/svn-project/tags/v0.2#egg=svn-project==0.2

Results in:

> Downloading/unpacking svn-project==0.2 from svn+https://`<svnuser>:<svnpass>@`mysubversionserver.org/svn/dir1/dir2/svn-project/tags/v0.2
>  Checking out https://`<svnuser>:<svnpass>@`mysubversionserver.org/svn/dir1/dir2/svn-project/tags/v0.2 to /tmp/pip_build_jivan/svn-project
> svn: E170000: 'https://`<svnuser>:<svnpass>@`mysubversionserver.org/svn/dir1/dir2/svn-project/tags/v0.2' isn't in the same repository as 'https://`<svnuser>:XXXXXXXX@`mysubversionserver.org/svn/dir1'
>   Complete output from command :
> 
> `----------------------------------------`
> Cleaning up...
> Command /usr/bin/svn checkout -q --username `<svnuser>` --password `<svnpass>` https://`<svnuser>:<svnpass>@`mysubversionserver.org/svn/dir1/dir2/svn-project/tags/v0.2 /tmp/pip_build_jivan/svn-project failed with error code 1 in None
> Storing debug log for failure in /home/jivan/.pip/pip.log



Discussion:

Resolving patch:
diff --git a/pip/vcs/subversion.py b/pip/vcs/subversion.py
--- a/pip/vcs/subversion.py
+++ b/pip/vcs/subversion.py
@@ -60,6 +60,7 @@ def export(self, location):
         """Export the svn repository at the url to the destination location"""
         url, rev = self.get_url_rev()
         rev_options = get_rev_options(url, rev)
+        url = self.remove_auth_from_url(url)
         logger.info('Exporting svn repository %s to %s', url, location)
         with indent_log():
             if os.path.exists(location):
@@ -79,6 +80,7 @@ def update(self, dest, rev_options):
     def obtain(self, dest):
         url, rev = self.get_url_rev()
         rev_options = get_rev_options(url, rev)
+        url = self.remove_auth_from_url(url)
         if rev:
             rev_display = ' (to revision %s)' % rev
         else:
@@ -217,6 +219,24 @@ def check_version(self, dest, rev_options):
         """Always assume the versions don't match"""
         return False
 
+    @staticmethod
+    def remove_auth_from_url(url):
+        # Return a copy of url with 'username:password@' removed.
+        # username/pass params are passed to subversion through flags
+        # and are not recognized in the url.
+
+        # parsed url
+        purl = urllib_parse.urlsplit(url)
+        stripped_netloc = \
+            purl.netloc.split('@')[-1]
+
+        # stripped url
+        url_pieces = (
+            purl.scheme, stripped_netloc, purl.path, purl.query, purl.fragment
+        )
+        surl = urllib_parse.urlunsplit(url_pieces)
+        return surl
+
 
 def get_rev_options(url, rev):
     if rev:


Test patch:
diff --git a/tests/functional/test_install_vcs_svn.py b/tests/functional/test_install_vcs_svn.py
--- a/tests/functional/test_install_vcs_svn.py
+++ b/tests/functional/test_install_vcs_svn.py
@@ -10,7 +10,7 @@ def test_obtain_should_recognize_auth_info_url(call_subprocess_mock, script):
     svn.obtain(script.scratch_path / 'test')
     assert call_subprocess_mock.call_args[0][0] == [
         svn.name, 'checkout', '-q', '--username', 'username', '--password',
-        'password', 'http://username:password@svn.example.com/',
+        'password', 'http://svn.example.com/',
         script.scratch_path / 'test',
     ]
 
@@ -22,6 +22,6 @@ def test_export_should_recognize_auth_info_url(call_subprocess_mock, script):
     svn.export(script.scratch_path / 'test')
     assert call_subprocess_mock.call_args[0][0] == [
         svn.name, 'export', '--username', 'username', '--password',
-        'password', 'http://username:password@svn.example.com/',
+        'password', 'http://svn.example.com/',
         script.scratch_path / 'test',
     ]
diff --git a/tests/unit/test_vcs.py b/tests/unit/test_vcs.py
--- a/tests/unit/test_vcs.py
+++ b/tests/unit/test_vcs.py
@@ -3,6 +3,7 @@
 from pip.vcs import VersionControl
 from pip.vcs.bazaar import Bazaar
 from pip.vcs.git import Git
+from pip.vcs.subversion import Subversion
 from mock import Mock
 
 if pyversion >= '3':
@@ -110,3 +111,29 @@ def test_bazaar_simple_urls():
     assert launchpad_bzr_repo.get_url_rev() == (
         'lp:MyLaunchpadProject', None,
     )
+
+
+def test_subversion_remove_auth_from_url():
+    # Check that the url is doctored appropriately to remove auth elements
+    #    from the url
+    svn_auth_url = 'https://user:pass@svnrepo.org/svn/project/tags/v0.2'
+    expected_url = 'https://svnrepo.org/svn/project/tags/v0.2'
+    url = Subversion.remove_auth_from_url(svn_auth_url)
+    assert url == expected_url
+
+    # Check that this doesn't impact urls without authentication'
+    svn_noauth_url = 'https://svnrepo.org/svn/project/tags/v0.2'
+    expected_url = svn_noauth_url
+    url = Subversion.remove_auth_from_url(svn_noauth_url)
+    assert url == expected_url
+
+    # Check that links to specific revisions are handled properly
+    svn_rev_url = 'https://user:pass@svnrepo.org/svn/project/trunk@8181'
+    expected_url = 'https://svnrepo.org/svn/project/trunk@8181'
+    url = Subversion.remove_auth_from_url(svn_rev_url)
+    assert url == expected_url
+
+    svn_rev_url = 'https://svnrepo.org/svn/project/trunk@8181'
+    expected_url = 'https://svnrepo.org/svn/project/trunk@8181'
+    url = Subversion.remove_auth_from_url(svn_rev_url)
+    assert url == expected_url