1
1
"""PyPI and direct package downloading."""
2
2
3
3
import sys
4
+ import subprocess
4
5
import os
5
6
import re
6
7
import io
@@ -587,7 +588,7 @@ def download(self, spec, tmpdir):
587
588
scheme = URL_SCHEME (spec )
588
589
if scheme :
589
590
# It's a url, download it to tmpdir
590
- found = self ._download_url (scheme . group ( 1 ), spec , tmpdir )
591
+ found = self ._download_url (spec , tmpdir )
591
592
base , fragment = egg_info_for_url (spec )
592
593
if base .endswith ('.py' ):
593
594
found = self .gen_setup (found , fragment , tmpdir )
@@ -816,7 +817,7 @@ def open_url(self, url, warning=None): # noqa: C901 # is too complex (12)
816
817
else :
817
818
raise DistutilsError ("Download error for %s: %s" % (url , v )) from v
818
819
819
- def _download_url (self , scheme , url , tmpdir ):
820
+ def _download_url (self , url , tmpdir ):
820
821
# Determine download filename
821
822
#
822
823
name , fragment = egg_info_for_url (url )
@@ -831,19 +832,59 @@ def _download_url(self, scheme, url, tmpdir):
831
832
832
833
filename = os .path .join (tmpdir , name )
833
834
834
- # Download the file
835
- #
836
- if scheme == 'svn' or scheme .startswith ('svn+' ):
837
- return self ._download_svn (url , filename )
838
- elif scheme == 'git' or scheme .startswith ('git+' ):
839
- return self ._download_git (url , filename )
840
- elif scheme .startswith ('hg+' ):
841
- return self ._download_hg (url , filename )
842
- elif scheme == 'file' :
843
- return urllib .request .url2pathname (urllib .parse .urlparse (url )[2 ])
844
- else :
845
- self .url_ok (url , True ) # raises error if not allowed
846
- return self ._attempt_download (url , filename )
835
+ return self ._download_vcs (url , filename ) or self ._download_other (url , filename )
836
+
837
+ @staticmethod
838
+ def _resolve_vcs (url ):
839
+ """
840
+ >>> rvcs = PackageIndex._resolve_vcs
841
+ >>> rvcs('git+http://foo/bar')
842
+ 'git'
843
+ >>> rvcs('hg+https://foo/bar')
844
+ 'hg'
845
+ >>> rvcs('git:myhost')
846
+ 'git'
847
+ >>> rvcs('hg:myhost')
848
+ >>> rvcs('http://foo/bar')
849
+ """
850
+ scheme = urllib .parse .urlsplit (url ).scheme
851
+ pre , sep , post = scheme .partition ('+' )
852
+ # svn and git have their own protocol; hg does not
853
+ allowed = set (['svn' , 'git' ] + ['hg' ] * bool (sep ))
854
+ return next (iter ({pre } & allowed ), None )
855
+
856
+ def _download_vcs (self , url , spec_filename ):
857
+ vcs = self ._resolve_vcs (url )
858
+ if not vcs :
859
+ return
860
+ if vcs == 'svn' :
861
+ raise DistutilsError (
862
+ f"Invalid config, SVN download is not supported: { url } "
863
+ )
864
+
865
+ filename , _ , _ = spec_filename .partition ('#' )
866
+ url , rev = self ._vcs_split_rev_from_url (url )
867
+
868
+ self .info (f"Doing { vcs } clone from { url } to { filename } " )
869
+ subprocess .check_call ([vcs , 'clone' , '--quiet' , url , filename ])
870
+
871
+ co_commands = dict (
872
+ git = [vcs , '-C' , filename , 'checkout' , '--quiet' , rev ],
873
+ hg = [vcs , '--cwd' , filename , 'up' , '-C' , '-r' , rev , '-q' ],
874
+ )
875
+ if rev is not None :
876
+ self .info (f"Checking out { rev } " )
877
+ subprocess .check_call (co_commands [vcs ])
878
+
879
+ return filename
880
+
881
+ def _download_other (self , url , filename ):
882
+ scheme = urllib .parse .urlsplit (url ).scheme
883
+ if scheme == 'file' : # pragma: no cover
884
+ return urllib .request .url2pathname (urllib .parse .urlparse (url ).path )
885
+ # raise error if not allowed
886
+ self .url_ok (url , True )
887
+ return self ._attempt_download (url , filename )
847
888
848
889
def scan_url (self , url ):
849
890
self .process_url (url , True )
@@ -859,64 +900,37 @@ def _invalid_download_html(self, url, headers, filename):
859
900
os .unlink (filename )
860
901
raise DistutilsError (f"Unexpected HTML page found at { url } " )
861
902
862
- def _download_svn (self , url , _filename ):
863
- raise DistutilsError (f"Invalid config, SVN download is not supported: { url } " )
864
-
865
903
@staticmethod
866
- def _vcs_split_rev_from_url (url , pop_prefix = False ):
867
- scheme , netloc , path , query , frag = urllib .parse .urlsplit (url )
904
+ def _vcs_split_rev_from_url (url ):
905
+ """
906
+ Given a possible VCS URL, return a clean URL and resolved revision if any.
907
+
908
+ >>> vsrfu = PackageIndex._vcs_split_rev_from_url
909
+ >>> vsrfu('git+https://github.com/pypa/[email protected] #egg-info=setuptools')
910
+ ('https://github.com/pypa/setuptools', 'v69.0.0')
911
+ >>> vsrfu('git+https://github.com/pypa/setuptools#egg-info=setuptools')
912
+ ('https://github.com/pypa/setuptools', None)
913
+ >>> vsrfu('http://foo/bar')
914
+ ('http://foo/bar', None)
915
+ """
916
+ parts = urllib .parse .urlsplit (url )
868
917
869
- scheme = scheme .split ('+' , 1 )[- 1 ]
918
+ clean_scheme = parts . scheme .split ('+' , 1 )[- 1 ]
870
919
871
920
# Some fragment identification fails
872
- path = path .split ('#' , 1 )[0 ]
873
-
874
- rev = None
875
- if '@' in path :
876
- path , rev = path .rsplit ('@' , 1 )
877
-
878
- # Also, discard fragment
879
- url = urllib .parse .urlunsplit ((scheme , netloc , path , query , '' ))
880
-
881
- return url , rev
882
-
883
- def _download_git (self , url , filename ):
884
- filename = filename .split ('#' , 1 )[0 ]
885
- url , rev = self ._vcs_split_rev_from_url (url , pop_prefix = True )
886
-
887
- self .info ("Doing git clone from %s to %s" , url , filename )
888
- os .system ("git clone --quiet %s %s" % (url , filename ))
889
-
890
- if rev is not None :
891
- self .info ("Checking out %s" , rev )
892
- os .system (
893
- "git -C %s checkout --quiet %s"
894
- % (
895
- filename ,
896
- rev ,
897
- )
898
- )
921
+ no_fragment_path , _ , _ = parts .path .partition ('#' )
899
922
900
- return filename
923
+ pre , sep , post = no_fragment_path .rpartition ('@' )
924
+ clean_path , rev = (pre , post ) if sep else (post , None )
901
925
902
- def _download_hg (self , url , filename ):
903
- filename = filename .split ('#' , 1 )[0 ]
904
- url , rev = self ._vcs_split_rev_from_url (url , pop_prefix = True )
926
+ resolved = parts ._replace (
927
+ scheme = clean_scheme ,
928
+ path = clean_path ,
929
+ # discard the fragment
930
+ fragment = '' ,
931
+ ).geturl ()
905
932
906
- self .info ("Doing hg clone from %s to %s" , url , filename )
907
- os .system ("hg clone --quiet %s %s" % (url , filename ))
908
-
909
- if rev is not None :
910
- self .info ("Updating to %s" , rev )
911
- os .system (
912
- "hg --cwd %s up -C -r %s -q"
913
- % (
914
- filename ,
915
- rev ,
916
- )
917
- )
918
-
919
- return filename
933
+ return resolved , rev
920
934
921
935
def debug (self , msg , * args ):
922
936
log .debug (msg , * args )
0 commit comments