From c38eb0cf592d619e56c766ad31ab1eecd5daac77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 17 Sep 2021 00:33:22 +0200 Subject: [PATCH] Ensure the full path to git is used on Windows # Conflicts: # poetry/core/vcs/git.py # tests/vcs/test_vcs.py --- poetry/core/vcs/git.py | 43 ++++++++++++++++++++++++++++++++++++++++-- tests/vcs/test_vcs.py | 30 +++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/poetry/core/vcs/git.py b/poetry/core/vcs/git.py index dda509236..ee646e6a0 100644 --- a/poetry/core/vcs/git.py +++ b/poetry/core/vcs/git.py @@ -6,6 +6,8 @@ from typing import Any from typing import Optional +from poetry.core.utils._compat import PY36 +from poetry.core.utils._compat import WINDOWS from poetry.core.utils._compat import Path from poetry.core.utils._compat import decode @@ -154,6 +156,41 @@ def __str__(self): # type: () -> str GitUrl = namedtuple("GitUrl", ["url", "revision"]) +_executable: Optional[str] = None + + +def executable(): + global _executable + + if _executable is not None: + return _executable + + if WINDOWS and PY36: + # Finding git via where.exe + where = "%WINDIR%\\System32\\where.exe" + paths = decode( + subprocess.check_output([where, "git"], shell=True, encoding="oem") + ).split("\n") + for path in paths: + if not path: + continue + + path = Path(path.strip()) + try: + path.relative_to(Path.cwd()) + except ValueError: + _executable = str(path) + + break + else: + _executable = "git" + + if _executable is None: + raise RuntimeError("Unable to find a valid git executable") + + return _executable + + class GitConfig: def __init__(self, requires_git_presence=False): # type: (bool) -> None self._config = {} @@ -161,7 +198,7 @@ def __init__(self, requires_git_presence=False): # type: (bool) -> None try: config_list = decode( subprocess.check_output( - ["git", "config", "-l"], stderr=subprocess.STDOUT + [executable(), "config", "-l"], stderr=subprocess.STDOUT ) ) @@ -310,7 +347,9 @@ def run(self, *args, **kwargs): # type: (*Any, **Any) -> str ) + args return decode( - subprocess.check_output(["git"] + list(args), stderr=subprocess.STDOUT) + subprocess.check_output( + [executable()] + list(args), stderr=subprocess.STDOUT + ) ).strip() def _check_parameter(self, parameter): # type: (str) -> None diff --git a/tests/vcs/test_vcs.py b/tests/vcs/test_vcs.py index 1a5e39624..84a9f9d77 100644 --- a/tests/vcs/test_vcs.py +++ b/tests/vcs/test_vcs.py @@ -1,5 +1,9 @@ +import subprocess + import pytest +from poetry.core.utils._compat import PY36 +from poetry.core.utils._compat import WINDOWS from poetry.core.utils._compat import Path from poetry.core.vcs.git import Git from poetry.core.vcs.git import GitError @@ -276,3 +280,29 @@ def test_git_checkout_raises_error_on_invalid_repository(): def test_git_rev_parse_raises_error_on_invalid_repository(): with pytest.raises(GitError): Git().rev_parse("-u./payload") + + +@pytest.mark.skipif( + not WINDOWS or not PY36, + reason="Retrieving the complete path to git is only necessary on Windows, for security reasons", +) +def test_ensure_absolute_path_to_git(mocker): + def checkout_output(cmd, *args, **kwargs): + if Path(cmd[0]).name == "where.exe": + return "\n".join( + [ + str(Path.cwd().joinpath("git.exe")), + "C:\\Program Files\\Git\\cmd\\git.exe", + ] + ) + + return b"" + + mock = mocker.patch.object(subprocess, "check_output", side_effect=checkout_output) + + Git().run("config") + + assert mock.call_args_list[-1][0][0] == [ + "C:\\Program Files\\Git\\cmd\\git.exe", + "config", + ]