@@ -1513,6 +1513,7 @@ def test_run_commit_hook_outside_worktree_on_windows(self, rw_dir):
15131513 repo = Repo .init (root / "repo" )
15141514 hooks_dir = root / "hooks"
15151515 _make_hook (root , "fake-hook" , "exit 0" )
1516+ _make_hook (repo .working_dir , "fake-hook" , "exit 0" )
15161517 system_root = root / "Windows"
15171518 system_bash = system_root / "System32" / "bash.exe"
15181519 git_executable = root / "Git" / "cmd" / "git.exe"
@@ -1521,24 +1522,37 @@ def test_run_commit_hook_outside_worktree_on_windows(self, rw_dir):
15211522 executable .parent .mkdir (parents = True )
15221523 executable .touch ()
15231524 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 ]
15261540
15271541 # Model a normal Windows PATH: System32 (containing the WSL launcher) comes
15281542 # before Git's cmd directory, while Git's Bash is not itself on PATH. This
15291543 # exercises both Git-installation discovery and shell selection without
15301544 # 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" ])
15421556
15431557 @with_rw_directory
15441558 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):
16711685 output = Path (rw_repo .working_dir , "custom-hook-output.txt" ).read_text (encoding = "utf-8" )
16721686 self .assertEqual (output , "ran custom hook\n " )
16731687
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+
16741710 @pytest .mark .xfail (
16751711 type (_win_bash_status ) is WinBashStatus .WslNoDistro ,
16761712 reason = "Currently uses the bash.exe of WSL, even with no WSL distro installed" ,
0 commit comments