| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -198,19 +198,28 @@ def run_commit_hook(name: str, index: "IndexFile", *args: str) -> None: | |||
| 198 | 198 | if sys.platform == "win32" and not _has_file_extension(hp): | |
| 199 | 199 | # Windows only uses extensions to determine how to open files | |
| 200 | 200 | # (doesn't understand shebangs). Try using bash to run the hook. | |
| 201 | + # Prefer Bash associated with GitPython's selected Git installation. If | ||
| 202 | + # that layout is not recognized, use an explicitly configured non-system | ||
| 203 | + # PATH entry. Preserve the bare fallback for installations that previously | ||
| 204 | + # relied on WSL or another CreateProcess-resolved Bash. | ||
| 205 | + found_bash = _git_for_windows_bash() or _which_from_path("bash.exe") | ||
| 201 | 206 | try: | |
| 202 | 207 | bash_hp = osp.relpath(hp, index.repo.working_dir) | |
| 203 | 208 | except ValueError: | |
| 204 | 209 | # Different drives have no relative path on Windows. Git Bash accepts | |
| 205 | 210 | # an absolute path in this form, although a relative path is preferable | |
| 206 | 211 | # because it also works with the Windows Subsystem for Linux wrapper. | |
| 207 | 212 | bash_hp = hp | |
| 208 | - # Prefer Bash associated with GitPython's selected Git installation. If | ||
| 209 | - # that layout is not recognized, use an explicitly configured non-system | ||
| 210 | - # PATH entry. Preserve the bare fallback for installations that previously | ||
| 211 | - # relied on WSL or another CreateProcess-resolved Bash. | ||
| 212 | - bash_executable = _git_for_windows_bash() or _which_from_path("bash.exe") or "bash.exe" | ||
| 213 | - cmd = [bash_executable, Path(bash_hp).as_posix()] | ||
| 213 | + else: | ||
| 214 | + # Bash resolves a relative path in its own view of the filesystem, | ||
| 215 | + # which differs from Windows' across a mount point: Git Bash mounts | ||
| 216 | + # the user's temporary directory as /tmp, so from a working tree | ||
| 217 | + # there, ".." leads elsewhere. A path leaving the working tree is | ||
| 218 | + # thus passed whole to a Bash found above, which accepts it; the | ||
| 219 | + # bare fallback may be WSL's, which does not, and keeps it relative. | ||
| 220 | + if found_bash and bash_hp.startswith(os.pardir + os.sep): | ||
| 221 | + bash_hp = hp | ||
| 222 | + cmd = [found_bash or "bash.exe", Path(bash_hp).as_posix()] | ||
| 214 | 223 | ||
| 215 | 224 | process = safer_popen( | |
| 216 | 225 | cmd + list(args), | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1513,6 +1513,7 @@ def test_run_commit_hook_outside_worktree_on_windows(self, rw_dir): | |||
| 1513 | 1513 | repo = Repo.init(root / "repo") | |
| 1514 | 1514 | hooks_dir = root / "hooks" | |
| 1515 | 1515 | _make_hook(root, "fake-hook", "exit 0") | |
| 1516 | + _make_hook(repo.working_dir, "fake-hook", "exit 0") | ||
| 1516 | 1517 | system_root = root / "Windows" | |
| 1517 | 1518 | system_bash = system_root / "System32" / "bash.exe" | |
| 1518 | 1519 | git_executable = root / "Git" / "cmd" / "git.exe" | |
@@ -1521,24 +1522,37 @@ def test_run_commit_hook_outside_worktree_on_windows(self, rw_dir): | |||
| 1521 | 1522 | executable.parent.mkdir(parents=True) | |
| 1522 | 1523 | executable.touch() | |
| 1523 | 1524 | executable.chmod(0o755) | |
| 1524 | - with repo.config_writer() as writer: | ||
| 1525 | - writer.set_value("core", "hooksPath", str(hooks_dir)) | ||
| 1525 | + | ||
| 1526 | + def command(hooks_path, exec_path): | ||
| 1527 | + with repo.config_writer() as writer: | ||
| 1528 | + writer.set_value("core", "hooksPath", hooks_path) | ||
| 1529 | + with mock.patch("git.index.fun.sys.platform", "win32"), mock.patch.object( | ||
| 1530 | + Git, "GIT_PYTHON_GIT_EXECUTABLE", "git" | ||
| 1531 | + ), mock.patch.dict(os.environ, {"SystemRoot": str(system_root)}), mock.patch( | ||
| 1532 | + "git.index.fun.os.get_exec_path", return_value=exec_path | ||
| 1533 | + ): | ||
| 1534 | + with mock.patch("git.index.fun.safer_popen") as popen, mock.patch( | ||
| 1535 | + "git.index.fun.handle_process_output" | ||
| 1536 | + ): | ||
| 1537 | + popen.return_value.returncode = 0 | ||
| 1538 | + run_commit_hook("fake-hook", repo.index) | ||
| 1539 | + return popen.call_args[0][0] | ||
| 1526 | 1540 | ||
| 1527 | 1541 | # Model a normal Windows PATH: System32 (containing the WSL launcher) comes | |
| 1528 | 1542 | # before Git's cmd directory, while Git's Bash is not itself on PATH. This | |
| 1529 | 1543 | # exercises both Git-installation discovery and shell selection without | |
| 1530 | 1544 | # mocking either resolver's answer. | |
| 1531 | - with mock.patch("git.index.fun.sys.platform", "win32"), mock.patch.object( | ||
| 1532 | - Git, "GIT_PYTHON_GIT_EXECUTABLE", "git" | ||
| 1533 | - ), mock.patch.dict(os.environ, {"SystemRoot": str(system_root)}), mock.patch( | ||
| 1534 | - "git.index.fun.os.get_exec_path", return_value=["", str(system_bash.parent), str(git_executable.parent)] | ||
| 1535 | - ): | ||
| 1536 | - with mock.patch("git.index.fun.safer_popen") as popen, mock.patch("git.index.fun.handle_process_output"): | ||
| 1537 | - popen.return_value.returncode = 0 | ||
| 1538 | - run_commit_hook("fake-hook", repo.index) | ||
| 1539 | - | ||
| 1540 | - command = popen.call_args[0][0] | ||
| 1541 | - self.assertEqual(command, [str(git_bash), "../hooks/fake-hook"]) | ||
| 1545 | + with_git = ["", str(system_bash.parent), str(git_executable.parent)] | ||
| 1546 | + # A hook outside the working tree is passed by its absolute path: a relative | ||
| 1547 | + # one is resolved in Bash's own view of the filesystem, where a mount point | ||
| 1548 | + # (Git Bash's /tmp) can make ".." lead elsewhere. | ||
| 1549 | + self.assertEqual(command(str(hooks_dir), with_git), [str(git_bash), (hooks_dir / "fake-hook").as_posix()]) | ||
| 1550 | + # A hook inside the working tree keeps its relative path. | ||
| 1551 | + self.assertEqual(command("hooks", with_git), [str(git_bash), "hooks/fake-hook"]) | ||
| 1552 | + # So does one outside it for the bare fallback, which may be WSL's bash.exe, | ||
| 1553 | + # unable to open a Windows absolute path. | ||
| 1554 | + without_git = ["", str(system_bash.parent)] | ||
| 1555 | + self.assertEqual(command(str(hooks_dir), without_git), ["bash.exe", "../hooks/fake-hook"]) | ||
| 1542 | 1556 | ||
| 1543 | 1557 | @with_rw_directory | |
| 1544 | 1558 | def test_windows_bash_lookup_respects_explicit_current_directory_in_path(self, rw_dir): | |
@@ -1671,6 +1685,28 @@ def test_pre_commit_hook_respects_core_hooks_path(self, rw_repo): | |||
| 1671 | 1685 | output = Path(rw_repo.working_dir, "custom-hook-output.txt").read_text(encoding="utf-8") | |
| 1672 | 1686 | self.assertEqual(output, "ran custom hook\n") | |
| 1673 | 1687 | ||
| 1688 | + @pytest.mark.xfail( | ||
| 1689 | + type(_win_bash_status) is WinBashStatus.Absent, | ||
| 1690 | + reason="Can't run a hook on Windows without bash.exe.", | ||
| 1691 | + raises=HookExecutionError, | ||
| 1692 | + ) | ||
| 1693 | + @pytest.mark.xfail( | ||
| 1694 | + type(_win_bash_status) is WinBashStatus.WslNoDistro, | ||
| 1695 | + reason="Currently uses the bash.exe of WSL, even with no WSL distro installed", | ||
| 1696 | + raises=HookExecutionError, | ||
| 1697 | + ) | ||
| 1698 | + @with_rw_directory | ||
| 1699 | + def test_pre_commit_hook_outside_worktree(self, rw_dir): | ||
| 1700 | + repo = Repo.init(osp.join(rw_dir, "repo")) | ||
| 1701 | + hp = _make_hook(rw_dir, "pre-commit", "echo 'ran outside hook' >outside-hook-output.txt") | ||
| 1702 | + | ||
| 1703 | + with repo.config_writer() as writer: | ||
| 1704 | + writer.set_value("core", "hooksPath", osp.dirname(hp)) | ||
| 1705 | + | ||
| 1706 | + repo.index.commit("This should run the hook outside the working tree") | ||
| 1707 | + output = Path(repo.working_dir, "outside-hook-output.txt").read_text(encoding="utf-8") | ||
| 1708 | + self.assertEqual(output, "ran outside hook\n") | ||
| 1709 | + | ||
| 1674 | 1710 | @pytest.mark.xfail( | |
| 1675 | 1711 | type(_win_bash_status) is WinBashStatus.WslNoDistro, | |
| 1676 | 1712 | reason="Currently uses the bash.exe of WSL, even with no WSL distro installed", | |
| Back | FazBrowse Home | New Git URL |
0 commit comments