77from pathlib import Path
88from typing import TYPE_CHECKING , ClassVar
99
10+ from sync_pre_commit_lock .config import HookRunner
11+
1012if TYPE_CHECKING :
1113 from collections .abc import Sequence
1214
1315 from sync_pre_commit_lock import Printer
1416
1517
16- class SetupPreCommitHooks :
17- install_pre_commit_hooks_command : ClassVar [Sequence [str | bytes ]] = ["pre-commit" , "install" ]
18- check_pre_commit_version_command : ClassVar [Sequence [str | bytes ]] = ["pre-commit" , "--version" ]
18+ class ResolvedHookRunner :
19+ """A resolved hook runner, bound to a concrete runner (never AUTO) and a command prefix."""
20+
21+ # Probe order for auto-detection: try prek first, then pre-commit
22+ _AUTO_ORDER : ClassVar [tuple [HookRunner , ...]] = (HookRunner .PREK , HookRunner .PRE_COMMIT )
23+
24+ def __init__ (self , runner : HookRunner , command_prefix : Sequence [str ] = ()) -> None :
25+ self .runner = runner
26+ self .command_prefix = command_prefix
27+
28+ @property
29+ def name (self ) -> str :
30+ return self .runner .value
31+
32+ def execute (self , * args : str ) -> Sequence [str | bytes ]:
33+ return [* self .command_prefix , self .runner .value , * args ]
34+
35+ def is_installed (self ) -> bool :
36+ """Check if this runner is installed by running its --version command."""
37+ try :
38+ output = subprocess .check_output (self .execute ("--version" )).decode () # noqa: S603
39+ except (subprocess .CalledProcessError , FileNotFoundError ):
40+ return False
41+ else :
42+ return self .runner .value in output
43+
44+ @classmethod
45+ def resolve (
46+ cls ,
47+ hook_runner : HookRunner ,
48+ command_prefix : Sequence [str ] = (),
49+ printer : Printer | None = None ,
50+ ) -> ResolvedHookRunner | None :
51+ """Resolve a HookRunner config to a concrete ResolvedHookRunner, or None if not found."""
52+ candidates = cls ._AUTO_ORDER if hook_runner is HookRunner .AUTO else [hook_runner ]
53+ for candidate in candidates :
54+ runner = cls (candidate , command_prefix )
55+ if runner .is_installed ():
56+ if hook_runner is HookRunner .AUTO and printer :
57+ printer .debug (f"Auto-detected hook runner: { candidate .value } " )
58+ return runner
59+ return None
60+
1961
20- def __init__ (self , printer : Printer , dry_run : bool = False ) -> None :
62+ class SetupPreCommitHooks :
63+ command_prefix : ClassVar [Sequence [str ]] = ()
64+
65+ def __init__ (
66+ self ,
67+ printer : Printer ,
68+ dry_run : bool = False ,
69+ hook_runner : HookRunner = HookRunner .PRE_COMMIT ,
70+ ) -> None :
2171 self .printer = printer
2272 self .dry_run = dry_run
73+ self .hook_runner = hook_runner
2374
2475 def execute (self ) -> None :
25- if not self ._is_pre_commit_package_installed ():
26- self .printer .debug ("pre-commit package is not installed (or detected). Skipping." )
76+ runner = ResolvedHookRunner .resolve (self .hook_runner , self .command_prefix , self .printer )
77+ if runner is None :
78+ self .printer .debug ("No hook runner (pre-commit or prek) is installed (or detected). Skipping." )
2779 return
2880
2981 git_root = self ._get_git_directory_path ()
@@ -39,36 +91,25 @@ def execute(self) -> None:
3991 self .printer .debug ("Dry run, skipping pre-commit hook installation." )
4092 return
4193
42- self ._install_pre_commit_hooks ( )
94+ self ._install_hooks ( runner )
4395
44- def _install_pre_commit_hooks (self ) -> None :
96+ def _install_hooks (self , runner : ResolvedHookRunner ) -> None :
4597 try :
46- self .printer .info ("Installing pre-commit hooks..." )
98+ self .printer .info (f "Installing { runner . name } hooks..." )
4799 return_code = subprocess .check_call ( # noqa: S603
48- self . install_pre_commit_hooks_command ,
100+ runner . execute ( "install" ) ,
49101 # XXX We probably want to see the output, at least in verbose mode or if it fails
50102 stdout = subprocess .DEVNULL ,
51103 stderr = subprocess .DEVNULL ,
52104 )
53105 if return_code == 0 :
54- self .printer .info ("pre-commit hooks successfully installed!" )
106+ self .printer .info (f" { runner . name } hooks successfully installed!" )
55107 else :
56- self .printer .error ("Failed to install pre-commit hooks" )
108+ self .printer .error (f "Failed to install { runner . name } hooks" )
57109 except Exception as e :
58- self .printer .error ("Failed to install pre-commit hooks due to an unexpected error" )
110+ self .printer .error (f "Failed to install { runner . name } hooks due to an unexpected error" )
59111 self .printer .error (f"{ e } " )
60112
61- def _is_pre_commit_package_installed (self ) -> bool :
62- try :
63- # Try is `pre-commit --version` works
64- output = subprocess .check_output ( # noqa: S603
65- self .check_pre_commit_version_command ,
66- ).decode ()
67- except (subprocess .CalledProcessError , FileNotFoundError ):
68- return False
69- else :
70- return "pre-commit" in output
71-
72113 @staticmethod
73114 def _are_pre_commit_hooks_installed (git_root : Path ) -> bool :
74115 return (git_root / "hooks" / "pre-commit" ).exists ()
0 commit comments