1"""Handles all VCS (version control) support"""
46 from typing
import Literal
54AuthInfo = Tuple[Optional[str], Optional[str]]
57def is_url(name: str) -> bool:
59 Return true if the name looks like a URL.
61 scheme = get_url_scheme(name)
68 repo_url: str, rev: str, project_name: str, subdir: Optional[str] =
None
71 Return the URL for a VCS requirement.
74 repo_url: the remote VCS url, with any needed VCS prefix (e.g. "git+").
75 project_name: the (unescaped) project name.
78 req = f
"{repo_url}@{rev}#egg={egg_project_name}"
80 req += f
"&subdirectory={subdir}"
85def find_path_to_project_root_from_repo_root(
86 location: str, repo_root: str
89 Find the the Python project's root by searching up the filesystem from
90 `location`. Return the path to project root relative to `repo_root`.
91 Return None if the project root is `repo_root`, or cannot be found.
94 orig_location = location
95 while not is_installable_dir(location):
96 last_location = location
98 if location == last_location:
102 "Could not find a Python project for directory %s (tried all "
103 "parent directories)",
127 Encapsulates a VCS-specific revision to install, along with any VCS
130 Instances of this class should be treated as if immutable.
135 vc_class: Type[
"VersionControl"],
136 rev: Optional[str] =
None,
137 extra_args: Optional[CommandArgs] =
None,
141 vc_class: a VersionControl subclass.
142 rev: the name of the revision to install.
143 extra_args: a list of extra options.
145 if extra_args
is None:
151 self.branch_name: Optional[str] =
None
154 return f
"<RevOptions {self.vc_class.name}: rev={self.rev!r}>"
159 return self.
vc_class.default_arg_rev
165 Return the VCS-specific command arguments.
167 args: CommandArgs = []
170 args += self.
vc_class.get_base_rev_args(rev)
179 return f
" (to revision {self.rev})"
183 Make a copy of the current instance, but with a new rev.
186 rev: the name of the revision for the new object.
192 _registry: Dict[str,
"VersionControl"] = {}
193 schemes = [
"ssh",
"git",
"hg",
"bzr",
"sftp",
"svn"]
214 schemes: List[str] = []
219 def register(self, cls: Type[
"VersionControl"]) ->
None:
233 Return a VersionControl object if a repository of that type is found
234 at the given directory.
237 for vcs_backend
in self.
_registry.values():
242 vcs_backends[repo_path] = vcs_backend
251 inner_most_repo_path = max(vcs_backends, key=len)
252 return vcs_backends[inner_most_repo_path]
256 Return a VersionControl object or None.
258 for vcs_backend
in self.
_registry.values():
265 Return a VersionControl object or None.
279 schemes: Tuple[str, ...] = ()
281 unset_environ: Tuple[str, ...] = ()
282 default_arg_rev: Optional[str] =
None
287 Return whether the vcs prefix (e.g. "git+") should be added to a
288 repository's remote url when used in a requirement.
295 Return the path to Python project root, relative to the repo root.
296 Return None if the project root is in the repo root.
303 Return the revision string that should be used in a requirement.
310 Return the requirement string to use to redownload the files
311 currently at the given repository directory.
314 project_name: the (unescaped) project name.
316 The return value has a form similar to the following:
318 {repository_url}@{revision}#egg={project_name}
323 repo_url = f
"{cls.name}+{repo_url}"
334 Return the base revision arguments for a vcs command.
337 rev: the name of a revision to install. Cannot be None.
339 raise NotImplementedError
343 Return true if the commit hash checked out at dest matches
346 Always return False, if the VCS does not support immutable commit
349 This method does not check if there are local uncommitted changes
350 in dest after checkout, as pip currently has no use case for that.
356 cls, rev: Optional[str] =
None, extra_args: Optional[CommandArgs] =
None
359 Return a RevOptions object.
362 rev: the name of a revision to install.
363 extra_args: a list of extra options.
365 return RevOptions(cls, rev, extra_args=extra_args)
370 posix absolute paths start with os.path.sep,
371 win32 ones start with drive (like c:\\folder)
378 cls, netloc: str, scheme: str
379 ) -> Tuple[str, Tuple[Optional[str], Optional[str]]]:
381 Parse the repository URL's netloc, and return the new netloc to use
382 along with auth information.
385 netloc: the original repository URL netloc.
386 scheme: the repository URL's scheme without the vcs prefix.
388 This is mainly for the Subversion class to override, so that auth
389 information can be provided via the --username and --password options
390 instead of through the URL. For other subclasses like Git without
391 such an option, auth information must stay in the URL.
393 Returns: (netloc, (username, password)).
395 return netloc, (
None,
None)
400 Parse the repository URL to use, and return the URL, revision,
401 and auth info to use.
403 Returns: (url, rev, (username, password)).
406 if "+" not in scheme:
408 "Sorry, {!r} is a malformed VCS url. "
409 "The format is <vcs>+<protocol>://<url>, "
410 "e.g. svn+http://myrepo/svn/MyApp#egg=MyApp".format(url)
420 "The URL {!r} has an empty revision (after @) "
421 "which is not supported. Include a revision after @ "
422 "or remove @ from the URL.".format(url)
425 return url, rev, user_pass
429 username: Optional[str], password: Optional[HiddenText]
432 Return the RevOptions "extra arguments" to use in obtain().
438 Return the URL and RevOptions object to use in obtain(),
439 as a tuple (url, rev_options).
442 username, secret_password = user_pass
443 password: Optional[HiddenText] =
None
444 if secret_password
is not None:
445 password = hide_value(secret_password)
449 return hide_url(secret_url), rev_options
454 Normalize a URL for comparison by unquoting it and removing any
462 Compare two repo URLs for identity, ignoring incidental differences.
467 self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int
470 Fetch a revision from a repository, in the case that this is the
471 first fetch from the repository.
474 dest: the directory to fetch the repository to.
475 rev_options: a RevOptions object.
476 verbosity: verbosity level.
478 raise NotImplementedError
480 def switch(self, dest: str, url: HiddenText, rev_options: RevOptions) ->
None:
482 Switch the repo at ``dest`` to point to ``URL``.
485 rev_options: a RevOptions object.
487 raise NotImplementedError
489 def update(self, dest: str, url: HiddenText, rev_options: RevOptions) ->
None:
491 Update an already-existing repo to the given ``rev_options``.
494 rev_options: a RevOptions object.
496 raise NotImplementedError
501 Return whether the id of the current commit equals the given name.
504 dest: the repository directory.
507 raise NotImplementedError
509 def obtain(self, dest: str, url: HiddenText, verbosity: int) ->
None:
511 Install or update in editable mode the package represented by this
512 VersionControl object.
514 :param dest: the repository directory in which to install or update.
515 :param url: the repository URL starting with a vcs prefix.
516 :param verbosity: verbosity level.
521 self.
fetch_new(dest, url, rev_options, verbosity=verbosity)
529 "%s in %s exists, and has correct URL (%s)",
541 self.
update(dest, url, rev_options)
543 logger.info(
"Skipping because already up-to-date.")
547 "%s %s in %s exists with URL %s",
553 prompt = (
"(s)witch, (i)gnore, (w)ipe, (b)ackup ", (
"s",
"i",
"w",
"b"))
556 "Directory %s already exists, and is not a %s %s.",
562 prompt = (
"(i)gnore, (w)ipe, (b)ackup ", (
"i",
"w",
"b"))
565 "The plan is to install the %s repository %s",
569 response = ask_path_exists(
"What to do? {}".format(prompt[0]), prompt[1])
577 self.
fetch_new(dest, url, rev_options, verbosity=verbosity)
581 dest_dir = backup_dir(dest)
582 logger.warning(
"Backing up %s to %s", display_path(dest), dest_dir)
584 self.
fetch_new(dest, url, rev_options, verbosity=verbosity)
590 "Switching %s %s to %s%s",
596 self.
switch(dest, url, rev_options)
598 def unpack(self, location: str, url: HiddenText, verbosity: int) ->
None:
600 Clean up current location and download the url repository
601 (and vcs infos) into location
603 :param url: the repository URL starting with a vcs prefix.
604 :param verbosity: verbosity level.
608 self.
obtain(location, url=url, verbosity=verbosity)
613 Return the url used at location
615 Raises RemoteNotFoundError if the repository does not have a remote
618 raise NotImplementedError
623 Return the current commit id of the files at the given location.
625 raise NotImplementedError
630 cmd: Union[List[str], CommandArgs],
631 show_stdout: bool =
True,
632 cwd: Optional[str] =
None,
633 on_returncode:
'Literal["raise", "warn", "ignore"]' =
"raise",
634 extra_ok_returncodes: Optional[Iterable[int]] =
None,
635 command_desc: Optional[str] =
None,
636 extra_environ: Optional[Mapping[str, Any]] =
None,
637 spinner: Optional[SpinnerInterface] =
None,
638 log_failed_cmd: bool =
True,
639 stdout_only: bool =
False,
643 This is simply a wrapper around call_subprocess that adds the VCS
644 command name, and checks that the VCS is available
646 cmd = make_command(cls.
namename, *cmd)
647 if command_desc
is None:
648 command_desc = format_command_args(cmd)
650 return call_subprocess(
654 on_returncode=on_returncode,
655 extra_ok_returncodes=extra_ok_returncodes,
656 command_desc=command_desc,
657 extra_environ=extra_environ,
660 log_failed_cmd=log_failed_cmd,
661 stdout_only=stdout_only,
663 except FileNotFoundError:
667 f
"Cannot find command {cls.name!r} - do you have "
668 f
"{cls.name!r} installed and in your PATH?"
670 except PermissionError:
676 f
"No permission to execute {cls.name!r} - install it "
677 f
"locally, globally (ask admin), or check your PATH. "
678 f
"See possible solutions at "
679 f
"https://pip.pypa.io/en/latest/reference/pip_freeze/"
680 f
"#fixing-permission-denied."
686 Return whether a directory path is a repository directory.
694 Return the "root" (top-level) directory controlled by the vcs,
695 or `None` if the directory is not in any.
697 It is meant to be overridden to implement smarter detection
698 mechanisms for specific vcs.
700 This can do more than is_repository_directory() alone. For
701 example, the Git override checks that Git is actually available.
"RevOptions" make_new(self, str rev)
CommandArgs to_args(self)
None __init__(self, Type["VersionControl"] vc_class, Optional[str] rev=None, Optional[CommandArgs] extra_args=None)
Optional[str] arg_rev(self)
Iterator[str] __iter__(self)
List["VersionControl"] backends(self)
None unregister(self, str name)
Optional["VersionControl"] get_backend_for_dir(self, str location)
Optional["VersionControl"] get_backend_for_scheme(self, str scheme)
None register(self, Type["VersionControl"] cls)
Optional["VersionControl"] get_backend(self, str name)
List[str] all_schemes(self)
List[str] get_base_rev_args(str rev)
Tuple[str, Tuple[Optional[str], Optional[str]]] get_netloc_and_auth(cls, str netloc, str scheme)
Optional[str] get_subdirectory(cls, str location)
str get_src_requirement(cls, str repo_dir, str project_name)
bool is_immutable_rev_checkout(self, str url, str dest)
bool should_add_vcs_url_prefix(cls, str remote_url)
Tuple[str, Optional[str], AuthInfo] get_url_rev_and_auth(cls, str url)
None obtain(self, str dest, HiddenText url, int verbosity)
None unpack(self, str location, HiddenText url, int verbosity)
Tuple[HiddenText, RevOptions] get_url_rev_options(self, HiddenText url)
bool is_repository_directory(cls, str path)
RevOptions make_rev_options(cls, Optional[str] rev=None, Optional[CommandArgs] extra_args=None)
CommandArgs make_rev_args(Optional[str] username, Optional[HiddenText] password)
str get_remote_url(cls, str location)
str get_revision(cls, str location)
None fetch_new(self, str dest, HiddenText url, RevOptions rev_options, int verbosity)
str get_requirement_revision(cls, str repo_dir)
bool is_commit_id_equal(cls, str dest, Optional[str] name)
bool _is_local_repository(cls, str repo)
Optional[str] get_repository_root(cls, str location)
None switch(self, str dest, HiddenText url, RevOptions rev_options)
None update(self, str dest, HiddenText url, RevOptions rev_options)
str normalize_url(str url)
str run_command(cls, Union[List[str], CommandArgs] cmd, bool show_stdout=True, Optional[str] cwd=None, 'Literal["raise", "warn", "ignore"]' on_returncode="raise", Optional[Iterable[int]] extra_ok_returncodes=None, Optional[str] command_desc=None, Optional[Mapping[str, Any]] extra_environ=None, Optional[SpinnerInterface] spinner=None, bool log_failed_cmd=True, bool stdout_only=False)
bool compare_urls(cls, str url1, str url2)
str make_vcs_requirement_url(str repo_url, str rev, str project_name, Optional[str] subdir=None)