diff options
Diffstat (limited to 'venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/vcs/git.py')
-rw-r--r-- | venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/vcs/git.py | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/vcs/git.py b/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/vcs/git.py new file mode 100644 index 0000000..7a63dfa --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/vcs/git.py | |||
@@ -0,0 +1,311 @@ | |||
1 | from __future__ import absolute_import | ||
2 | |||
3 | import logging | ||
4 | import os.path | ||
5 | import re | ||
6 | |||
7 | from pip._vendor.packaging.version import parse as parse_version | ||
8 | from pip._vendor.six.moves.urllib import parse as urllib_parse | ||
9 | from pip._vendor.six.moves.urllib import request as urllib_request | ||
10 | |||
11 | from pip._internal.compat import samefile | ||
12 | from pip._internal.exceptions import BadCommand | ||
13 | from pip._internal.utils.misc import display_path | ||
14 | from pip._internal.utils.temp_dir import TempDirectory | ||
15 | from pip._internal.vcs import VersionControl, vcs | ||
16 | |||
17 | urlsplit = urllib_parse.urlsplit | ||
18 | urlunsplit = urllib_parse.urlunsplit | ||
19 | |||
20 | |||
21 | logger = logging.getLogger(__name__) | ||
22 | |||
23 | |||
24 | HASH_REGEX = re.compile('[a-fA-F0-9]{40}') | ||
25 | |||
26 | |||
27 | def looks_like_hash(sha): | ||
28 | return bool(HASH_REGEX.match(sha)) | ||
29 | |||
30 | |||
31 | class Git(VersionControl): | ||
32 | name = 'git' | ||
33 | dirname = '.git' | ||
34 | repo_name = 'clone' | ||
35 | schemes = ( | ||
36 | 'git', 'git+http', 'git+https', 'git+ssh', 'git+git', 'git+file', | ||
37 | ) | ||
38 | # Prevent the user's environment variables from interfering with pip: | ||
39 | # https://github.com/pypa/pip/issues/1130 | ||
40 | unset_environ = ('GIT_DIR', 'GIT_WORK_TREE') | ||
41 | default_arg_rev = 'HEAD' | ||
42 | |||
43 | def __init__(self, url=None, *args, **kwargs): | ||
44 | |||
45 | # Works around an apparent Git bug | ||
46 | # (see http://article.gmane.org/gmane.comp.version-control.git/146500) | ||
47 | if url: | ||
48 | scheme, netloc, path, query, fragment = urlsplit(url) | ||
49 | if scheme.endswith('file'): | ||
50 | initial_slashes = path[:-len(path.lstrip('/'))] | ||
51 | newpath = ( | ||
52 | initial_slashes + | ||
53 | urllib_request.url2pathname(path) | ||
54 | .replace('\\', '/').lstrip('/') | ||
55 | ) | ||
56 | url = urlunsplit((scheme, netloc, newpath, query, fragment)) | ||
57 | after_plus = scheme.find('+') + 1 | ||
58 | url = scheme[:after_plus] + urlunsplit( | ||
59 | (scheme[after_plus:], netloc, newpath, query, fragment), | ||
60 | ) | ||
61 | |||
62 | super(Git, self).__init__(url, *args, **kwargs) | ||
63 | |||
64 | def get_base_rev_args(self, rev): | ||
65 | return [rev] | ||
66 | |||
67 | def get_git_version(self): | ||
68 | VERSION_PFX = 'git version ' | ||
69 | version = self.run_command(['version'], show_stdout=False) | ||
70 | if version.startswith(VERSION_PFX): | ||
71 | version = version[len(VERSION_PFX):].split()[0] | ||
72 | else: | ||
73 | version = '' | ||
74 | # get first 3 positions of the git version becasue | ||
75 | # on windows it is x.y.z.windows.t, and this parses as | ||
76 | # LegacyVersion which always smaller than a Version. | ||
77 | version = '.'.join(version.split('.')[:3]) | ||
78 | return parse_version(version) | ||
79 | |||
80 | def export(self, location): | ||
81 | """Export the Git repository at the url to the destination location""" | ||
82 | if not location.endswith('/'): | ||
83 | location = location + '/' | ||
84 | |||
85 | with TempDirectory(kind="export") as temp_dir: | ||
86 | self.unpack(temp_dir.path) | ||
87 | self.run_command( | ||
88 | ['checkout-index', '-a', '-f', '--prefix', location], | ||
89 | show_stdout=False, cwd=temp_dir.path | ||
90 | ) | ||
91 | |||
92 | def get_revision_sha(self, dest, rev): | ||
93 | """ | ||
94 | Return a commit hash for the given revision if it names a remote | ||
95 | branch or tag. Otherwise, return None. | ||
96 | |||
97 | Args: | ||
98 | dest: the repository directory. | ||
99 | rev: the revision name. | ||
100 | """ | ||
101 | # Pass rev to pre-filter the list. | ||
102 | output = self.run_command(['show-ref', rev], cwd=dest, | ||
103 | show_stdout=False, on_returncode='ignore') | ||
104 | refs = {} | ||
105 | for line in output.strip().splitlines(): | ||
106 | try: | ||
107 | sha, ref = line.split() | ||
108 | except ValueError: | ||
109 | # Include the offending line to simplify troubleshooting if | ||
110 | # this error ever occurs. | ||
111 | raise ValueError('unexpected show-ref line: {!r}'.format(line)) | ||
112 | |||
113 | refs[ref] = sha | ||
114 | |||
115 | branch_ref = 'refs/remotes/origin/{}'.format(rev) | ||
116 | tag_ref = 'refs/tags/{}'.format(rev) | ||
117 | |||
118 | return refs.get(branch_ref) or refs.get(tag_ref) | ||
119 | |||
120 | def check_rev_options(self, dest, rev_options): | ||
121 | """Check the revision options before checkout. | ||
122 | |||
123 | Returns a new RevOptions object for the SHA1 of the branch or tag | ||
124 | if found. | ||
125 | |||
126 | Args: | ||
127 | rev_options: a RevOptions object. | ||
128 | """ | ||
129 | rev = rev_options.arg_rev | ||
130 | sha = self.get_revision_sha(dest, rev) | ||
131 | |||
132 | if sha is not None: | ||
133 | return rev_options.make_new(sha) | ||
134 | |||
135 | # Do not show a warning for the common case of something that has | ||
136 | # the form of a Git commit hash. | ||
137 | if not looks_like_hash(rev): | ||
138 | logger.warning( | ||
139 | "Did not find branch or tag '%s', assuming revision or ref.", | ||
140 | rev, | ||
141 | ) | ||
142 | return rev_options | ||
143 | |||
144 | def is_commit_id_equal(self, dest, name): | ||
145 | """ | ||
146 | Return whether the current commit hash equals the given name. | ||
147 | |||
148 | Args: | ||
149 | dest: the repository directory. | ||
150 | name: a string name. | ||
151 | """ | ||
152 | if not name: | ||
153 | # Then avoid an unnecessary subprocess call. | ||
154 | return False | ||
155 | |||
156 | return self.get_revision(dest) == name | ||
157 | |||
158 | def switch(self, dest, url, rev_options): | ||
159 | self.run_command(['config', 'remote.origin.url', url], cwd=dest) | ||
160 | cmd_args = ['checkout', '-q'] + rev_options.to_args() | ||
161 | self.run_command(cmd_args, cwd=dest) | ||
162 | |||
163 | self.update_submodules(dest) | ||
164 | |||
165 | def update(self, dest, rev_options): | ||
166 | # First fetch changes from the default remote | ||
167 | if self.get_git_version() >= parse_version('1.9.0'): | ||
168 | # fetch tags in addition to everything else | ||
169 | self.run_command(['fetch', '-q', '--tags'], cwd=dest) | ||
170 | else: | ||
171 | self.run_command(['fetch', '-q'], cwd=dest) | ||
172 | # Then reset to wanted revision (maybe even origin/master) | ||
173 | rev_options = self.check_rev_options(dest, rev_options) | ||
174 | cmd_args = ['reset', '--hard', '-q'] + rev_options.to_args() | ||
175 | self.run_command(cmd_args, cwd=dest) | ||
176 | #: update submodules | ||
177 | self.update_submodules(dest) | ||
178 | |||
179 | def obtain(self, dest): | ||
180 | url, rev = self.get_url_rev() | ||
181 | rev_options = self.make_rev_options(rev) | ||
182 | if self.check_destination(dest, url, rev_options): | ||
183 | rev_display = rev_options.to_display() | ||
184 | logger.info( | ||
185 | 'Cloning %s%s to %s', url, rev_display, display_path(dest), | ||
186 | ) | ||
187 | self.run_command(['clone', '-q', url, dest]) | ||
188 | |||
189 | if rev: | ||
190 | rev_options = self.check_rev_options(dest, rev_options) | ||
191 | # Only do a checkout if the current commit id doesn't match | ||
192 | # the requested revision. | ||
193 | if not self.is_commit_id_equal(dest, rev_options.rev): | ||
194 | rev = rev_options.rev | ||
195 | # Only fetch the revision if it's a ref | ||
196 | if rev.startswith('refs/'): | ||
197 | self.run_command( | ||
198 | ['fetch', '-q', url] + rev_options.to_args(), | ||
199 | cwd=dest, | ||
200 | ) | ||
201 | # Change the revision to the SHA of the ref we fetched | ||
202 | rev = 'FETCH_HEAD' | ||
203 | self.run_command(['checkout', '-q', rev], cwd=dest) | ||
204 | |||
205 | #: repo may contain submodules | ||
206 | self.update_submodules(dest) | ||
207 | |||
208 | def get_url(self, location): | ||
209 | """Return URL of the first remote encountered.""" | ||
210 | remotes = self.run_command( | ||
211 | ['config', '--get-regexp', r'remote\..*\.url'], | ||
212 | show_stdout=False, cwd=location, | ||
213 | ) | ||
214 | remotes = remotes.splitlines() | ||
215 | found_remote = remotes[0] | ||
216 | for remote in remotes: | ||
217 | if remote.startswith('remote.origin.url '): | ||
218 | found_remote = remote | ||
219 | break | ||
220 | url = found_remote.split(' ')[1] | ||
221 | return url.strip() | ||
222 | |||
223 | def get_revision(self, location): | ||
224 | current_rev = self.run_command( | ||
225 | ['rev-parse', 'HEAD'], show_stdout=False, cwd=location, | ||
226 | ) | ||
227 | return current_rev.strip() | ||
228 | |||
229 | def _get_subdirectory(self, location): | ||
230 | """Return the relative path of setup.py to the git repo root.""" | ||
231 | # find the repo root | ||
232 | git_dir = self.run_command(['rev-parse', '--git-dir'], | ||
233 | show_stdout=False, cwd=location).strip() | ||
234 | if not os.path.isabs(git_dir): | ||
235 | git_dir = os.path.join(location, git_dir) | ||
236 | root_dir = os.path.join(git_dir, '..') | ||
237 | # find setup.py | ||
238 | orig_location = location | ||
239 | while not os.path.exists(os.path.join(location, 'setup.py')): | ||
240 | last_location = location | ||
241 | location = os.path.dirname(location) | ||
242 | if location == last_location: | ||
243 | # We've traversed up to the root of the filesystem without | ||
244 | # finding setup.py | ||
245 | logger.warning( | ||
246 | "Could not find setup.py for directory %s (tried all " | ||
247 | "parent directories)", | ||
248 | orig_location, | ||
249 | ) | ||
250 | return None | ||
251 | # relative path of setup.py to repo root | ||
252 | if samefile(root_dir, location): | ||
253 | return None | ||
254 | return os.path.relpath(location, root_dir) | ||
255 | |||
256 | def get_src_requirement(self, dist, location): | ||
257 | repo = self.get_url(location) | ||
258 | if not repo.lower().startswith('git:'): | ||
259 | repo = 'git+' + repo | ||
260 | egg_project_name = dist.egg_name().split('-', 1)[0] | ||
261 | if not repo: | ||
262 | return None | ||
263 | current_rev = self.get_revision(location) | ||
264 | req = '%s@%s#egg=%s' % (repo, current_rev, egg_project_name) | ||
265 | subdirectory = self._get_subdirectory(location) | ||
266 | if subdirectory: | ||
267 | req += '&subdirectory=' + subdirectory | ||
268 | return req | ||
269 | |||
270 | def get_url_rev(self): | ||
271 | """ | ||
272 | Prefixes stub URLs like 'user@hostname:user/repo.git' with 'ssh://'. | ||
273 | That's required because although they use SSH they sometimes doesn't | ||
274 | work with a ssh:// scheme (e.g. Github). But we need a scheme for | ||
275 | parsing. Hence we remove it again afterwards and return it as a stub. | ||
276 | """ | ||
277 | if '://' not in self.url: | ||
278 | assert 'file:' not in self.url | ||
279 | self.url = self.url.replace('git+', 'git+ssh://') | ||
280 | url, rev = super(Git, self).get_url_rev() | ||
281 | url = url.replace('ssh://', '') | ||
282 | else: | ||
283 | url, rev = super(Git, self).get_url_rev() | ||
284 | |||
285 | return url, rev | ||
286 | |||
287 | def update_submodules(self, location): | ||
288 | if not os.path.exists(os.path.join(location, '.gitmodules')): | ||
289 | return | ||
290 | self.run_command( | ||
291 | ['submodule', 'update', '--init', '--recursive', '-q'], | ||
292 | cwd=location, | ||
293 | ) | ||
294 | |||
295 | @classmethod | ||
296 | def controls_location(cls, location): | ||
297 | if super(Git, cls).controls_location(location): | ||
298 | return True | ||
299 | try: | ||
300 | r = cls().run_command(['rev-parse'], | ||
301 | cwd=location, | ||
302 | show_stdout=False, | ||
303 | on_returncode='ignore') | ||
304 | return not r | ||
305 | except BadCommand: | ||
306 | logger.debug("could not determine if %s is under git control " | ||
307 | "because git is not available", location) | ||
308 | return False | ||
309 | |||
310 | |||
311 | vcs.register(Git) | ||