diff --git a/docs/html/reference/pip_install.rst b/docs/html/reference/pip_install.rst index 1418e0f58..5cbb13906 100644 --- a/docs/html/reference/pip_install.rst +++ b/docs/html/reference/pip_install.rst @@ -573,6 +573,9 @@ and use any packages found there. This is disabled via the same of that is not part of the pip API. As of 7.0, pip makes a subdirectory for each sdist that wheels are built from and places the resulting wheels inside. +As of version 20.0, pip also caches wheels when building from an immutable Git +reference (i.e. a commit hash). + Pip attempts to choose the best wheels from those built in preference to building a new wheel. Note that this means when a package has both optional C extensions and builds ``py`` tagged wheels when the C extension can't be built diff --git a/news/6640.feature b/news/6640.feature new file mode 100644 index 000000000..cb7e939da --- /dev/null +++ b/news/6640.feature @@ -0,0 +1,2 @@ +Cache wheels built from Git requirements that are considered immutable, +because they point to a commit hash. diff --git a/src/pip/_internal/vcs/git.py b/src/pip/_internal/vcs/git.py index 92b845714..28a4fbae5 100644 --- a/src/pip/_internal/vcs/git.py +++ b/src/pip/_internal/vcs/git.py @@ -12,7 +12,7 @@ from pip._vendor.six.moves.urllib import parse as urllib_parse from pip._vendor.six.moves.urllib import request as urllib_request from pip._internal.exceptions import BadCommand -from pip._internal.utils.misc import display_path +from pip._internal.utils.misc import display_path, hide_url from pip._internal.utils.subprocess import make_command from pip._internal.utils.temp_dir import TempDirectory from pip._internal.utils.typing import MYPY_CHECK_RUNNING @@ -59,6 +59,13 @@ class Git(VersionControl): def get_base_rev_args(rev): return [rev] + def is_immutable_rev_checkout(self, url, dest): + # type: (str, str) -> bool + _, rev_options = self.get_url_rev_options(hide_url(url)) + if not rev_options.rev: + return False + return self.is_commit_id_equal(dest, rev_options.rev) + def get_git_version(self): VERSION_PFX = 'git version ' version = self.run_command(['version'], show_stdout=False) diff --git a/src/pip/_internal/vcs/versioncontrol.py b/src/pip/_internal/vcs/versioncontrol.py index 0eccf436a..a1742cfc8 100644 --- a/src/pip/_internal/vcs/versioncontrol.py +++ b/src/pip/_internal/vcs/versioncontrol.py @@ -329,6 +329,20 @@ class VersionControl(object): """ raise NotImplementedError + def is_immutable_rev_checkout(self, url, dest): + # type: (str, str) -> bool + """ + Return true if the commit hash checked out at dest matches + the revision in url. + + Always return False, if the VCS does not support immutable commit + hashes. + + This method does not check if there are local uncommitted changes + in dest after checkout, as pip currently has no use case for that. + """ + return False + @classmethod def make_rev_options(cls, rev=None, extra_args=None): # type: (Optional[str], Optional[CommandArgs]) -> RevOptions diff --git a/src/pip/_internal/wheel.py b/src/pip/_internal/wheel.py index ac4bf029f..0af58056d 100644 --- a/src/pip/_internal/wheel.py +++ b/src/pip/_internal/wheel.py @@ -52,6 +52,7 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING from pip._internal.utils.ui import open_spinner from pip._internal.utils.unpacking import unpack_file from pip._internal.utils.urls import path_to_url +from pip._internal.vcs import vcs if MYPY_CHECK_RUNNING: from typing import ( @@ -838,7 +839,15 @@ def should_cache( return False if req.link and req.link.is_vcs: - # VCS checkout. Build wheel just for this run. + # VCS checkout. Build wheel just for this run + # unless it points to an immutable commit hash in which + # case it can be cached. + assert not req.editable + assert req.source_dir + vcs_backend = vcs.get_backend_for_scheme(req.link.scheme) + assert vcs_backend + if vcs_backend.is_immutable_rev_checkout(req.link.url, req.source_dir): + return True return False link = req.link diff --git a/tests/functional/test_vcs_git.py b/tests/functional/test_vcs_git.py index 606cbe1ef..c3b23afa0 100644 --- a/tests/functional/test_vcs_git.py +++ b/tests/functional/test_vcs_git.py @@ -221,3 +221,20 @@ def test_is_commit_id_equal(script): assert not Git.is_commit_id_equal(version_pkg_path, 'abc123') # Also check passing a None value. assert not Git.is_commit_id_equal(version_pkg_path, None) + + +def test_is_immutable_rev_checkout(script): + version_pkg_path = _create_test_package(script) + commit = script.run( + 'git', 'rev-parse', 'HEAD', + cwd=version_pkg_path + ).stdout.strip() + assert Git().is_immutable_rev_checkout( + "git+https://g.c/o/r@" + commit, version_pkg_path + ) + assert not Git().is_immutable_rev_checkout( + "git+https://g.c/o/r", version_pkg_path + ) + assert not Git().is_immutable_rev_checkout( + "git+https://g.c/o/r@master", version_pkg_path + )