1+ use std:: borrow:: Cow ;
12use std:: fmt:: Write ;
23use std:: fmt:: { Display , Formatter } ;
34use std:: ops:: AddAssign ;
@@ -15,7 +16,9 @@ use crate::cli::ExitStatus;
1516use crate :: cli:: cache_size:: { dir_size_bytes, human_readable_bytes} ;
1617use crate :: cli:: run:: InstallCache ;
1718use crate :: config:: { self , Error as ConfigError , Repo as ConfigRepo , load_config} ;
18- use crate :: hook:: { HOOK_MARKER , HookEnvKey , HookSpec , InstallInfo , Repo as HookRepo } ;
19+ use crate :: hook:: {
20+ HOOK_MARKER , HookEnvRequirement , HookSpec , InstallInfo , Repo as HookRepo , RepoIdentityRef ,
21+ } ;
1922use crate :: printer:: Printer ;
2023use crate :: store:: { CacheBucket , REPO_MARKER , Store , ToolBucket } ;
2124
@@ -160,7 +163,7 @@ pub(crate) async fn cache_gc(
160163 let mut used_tools: FxHashSet < ToolBucket > = FxHashSet :: default ( ) ;
161164 let mut used_tool_versions: FxHashMap < ToolBucket , FxHashSet < String > > = FxHashMap :: default ( ) ;
162165 let mut used_cache: FxHashSet < CacheBucket > = FxHashSet :: default ( ) ;
163- let mut used_env_keys : Vec < HookEnvKey > = Vec :: new ( ) ;
166+ let mut used_env_requirements : Vec < HookEnvRequirement > = Vec :: new ( ) ;
164167
165168 // Always keep Prek's own cache.
166169 used_cache. insert ( CacheBucket :: Prek ) ;
@@ -187,7 +190,7 @@ pub(crate) async fn cache_gc(
187190 } ;
188191 kept_configs. insert ( config_path) ;
189192
190- used_env_keys . extend ( hook_env_keys_from_config ( store, & config) ) ;
193+ used_env_requirements . extend ( hook_env_requirements_from_config ( store, & config) ) ;
191194
192195 // Mark repos referenced by this config (if present in store).
193196 // We do this via config parsing (no clone), so GC won't keep repos for missing configs.
@@ -206,17 +209,20 @@ pub(crate) async fn cache_gc(
206209 }
207210
208211 // Mark tools/caches from hook languages.
209- for key in & used_env_keys {
210- used_tools. extend ( key . language . tool_buckets ( ) ) ;
211- used_cache. extend ( key . language . cache_buckets ( ) ) ;
212+ for requirement in & used_env_requirements {
213+ used_tools. extend ( requirement . language . tool_buckets ( ) ) ;
214+ used_cache. extend ( requirement . language . cache_buckets ( ) ) ;
212215 }
213216
214217 // Mark hook environments by matching already-installed env metadata.
215218 // While doing this, try to derive the specific tool *version* directories in use from
216219 // `InstallInfo.toolchain` (which is persisted in `.prek-hook.json`).
217220 for installed in install_cache. installed_hooks ( store) . await {
218221 let info = installed. info_ref ( ) ;
219- if used_env_keys. iter ( ) . any ( |k| k. matches_install_info ( info) ) {
222+ if used_env_requirements
223+ . iter ( )
224+ . any ( |requirement| requirement. as_ref ( ) . is_satisfied_by ( info) )
225+ {
220226 if let Some ( dir) = info
221227 . env_path
222228 . file_name ( )
@@ -352,8 +358,11 @@ fn print_removed_details(printer: Printer, verb: &str, removal: &Removal) -> Res
352358 Ok ( ( ) )
353359}
354360
355- fn hook_env_keys_from_config ( store : & Store , config : & config:: Config ) -> Vec < HookEnvKey > {
356- let mut keys = Vec :: new ( ) ;
361+ fn hook_env_requirements_from_config (
362+ store : & Store ,
363+ config : & config:: Config ,
364+ ) -> Vec < HookEnvRequirement > {
365+ let mut requirements = Vec :: new ( ) ;
357366
358367 for repo_config in & config. repos {
359368 match repo_config {
@@ -375,7 +384,7 @@ fn hook_env_keys_from_config(store: &Store, config: &config::Config) -> Vec<Hook
375384 }
376385 } ;
377386
378- let remote_dep = repo_config. to_string ( ) ;
387+ let remote_repo = RepoIdentityRef :: new ( & repo_config. repo , & repo_config . rev ) ;
379388
380389 for hook_config in & repo_config. hooks {
381390 let Some ( manifest_hook) = repo. get_hook ( & hook_config. id ) else {
@@ -385,23 +394,23 @@ fn hook_env_keys_from_config(store: &Store, config: &config::Config) -> Vec<Hook
385394 let mut hook_spec = manifest_hook. clone ( ) ;
386395 hook_spec. apply_remote_hook_overrides ( hook_config) ;
387396
388- match HookEnvKey :: from_hook_spec ( config, hook_spec, Some ( & remote_dep ) ) {
389- Ok ( Some ( key ) ) => keys . push ( key ) ,
397+ match HookEnvRequirement :: from_hook_spec ( config, hook_spec, Some ( remote_repo ) ) {
398+ Ok ( Some ( requirement ) ) => requirements . push ( requirement ) ,
390399 Ok ( None ) => { }
391400 Err ( err) => {
392- warn ! ( hook = %hook_config. id, repo = %remote_dep , %err, "Failed to compute hook env key , skipping" ) ;
401+ warn ! ( hook = %hook_config. id, repo = %remote_repo , %err, "Failed to compute hook environment requirement , skipping" ) ;
393402 }
394403 }
395404 }
396405 }
397406 ConfigRepo :: Local ( repo_config) => {
398407 for hook in & repo_config. hooks {
399408 let hook_spec = HookSpec :: from ( hook. clone ( ) ) ;
400- match HookEnvKey :: from_hook_spec ( config, hook_spec, None ) {
401- Ok ( Some ( key ) ) => keys . push ( key ) ,
409+ match HookEnvRequirement :: from_hook_spec ( config, hook_spec, None ) {
410+ Ok ( Some ( requirement ) ) => requirements . push ( requirement ) ,
402411 Ok ( None ) => { }
403412 Err ( err) => {
404- warn ! ( hook = %hook. id, %err, "Failed to compute hook env key , skipping" ) ;
413+ warn ! ( hook = %hook. id, %err, "Failed to compute hook environment requirement , skipping" ) ;
405414 }
406415 }
407416 }
@@ -410,7 +419,7 @@ fn hook_env_keys_from_config(store: &Store, config: &config::Config) -> Vec<Hook
410419 }
411420 }
412421
413- keys
422+ requirements
414423}
415424
416425fn mark_tool_versions_from_install_info (
@@ -731,7 +740,7 @@ fn detail_lines_for_entry(
731740 info. language_version
732741 ) ) ;
733742
734- let ( repo_dep, deps) = split_repo_dependency ( & info. dependencies ) ;
743+ let ( repo_dep, deps) = marker_repo_and_dependencies ( info) ;
735744 if let Some ( repo_dep) = repo_dep {
736745 lines. push ( format ! (
737746 "{}: {}" ,
@@ -780,8 +789,8 @@ fn truncate_end(s: &str, max_chars: usize) -> String {
780789 out
781790}
782791
783- fn split_repo_dependency ( deps : & FxHashSet < String > ) -> ( Option < String > , Vec < String > ) {
784- // Best-effort: the remote repo dependency is typically `repo@rev`.
792+ fn split_repo_dependency ( deps : & [ String ] ) -> ( Option < String > , Vec < String > ) {
793+ // Legacy markers stored the remote repo identity as a `repo@rev` dependency .
785794 // Prefer URL-like values to avoid accidentally treating PEP508 deps as repo identifiers.
786795 let mut repo_dep: Option < String > = None ;
787796 let mut rest = Vec :: new ( ) ;
@@ -804,6 +813,18 @@ fn split_repo_dependency(deps: &FxHashSet<String>) -> (Option<String>, Vec<Strin
804813 ( repo_dep, rest)
805814}
806815
816+ fn marker_repo_and_dependencies ( info : & InstallInfo ) -> ( Option < String > , Cow < ' _ , [ String ] > ) {
817+ if info. schema_version ( ) == 0 {
818+ let ( repo, dependencies) = split_repo_dependency ( & info. dependencies ) ;
819+ ( repo, Cow :: Owned ( dependencies) )
820+ } else {
821+ (
822+ info. repo ( ) . map ( |repo| repo. to_string ( ) ) ,
823+ Cow :: Borrowed ( & info. dependencies ) ,
824+ )
825+ }
826+ }
827+
807828fn format_dependency_list ( deps : & [ String ] , max_items : usize , max_chars : usize ) -> String {
808829 if deps. is_empty ( ) {
809830 return String :: new ( ) ;
@@ -843,10 +864,11 @@ mod tests {
843864
844865 #[ test]
845866 fn split_repo_dependency_prefers_url_like_repo_at_rev ( ) {
846- let mut deps = FxHashSet :: default ( ) ;
847- deps. insert ( "requests==2.32.0" . to_string ( ) ) ;
848- deps. insert ( "black==24.1.0" . to_string ( ) ) ;
849- deps. insert ( "https://github.com/pre-commit/pre-commit-hooks@v1.0.0" . to_string ( ) ) ;
867+ let deps = vec ! [
868+ "requests==2.32.0" . to_string( ) ,
869+ "black==24.1.0" . to_string( ) ,
870+ "https://github.com/pre-commit/pre-commit-hooks@v1.0.0" . to_string( ) ,
871+ ] ;
850872
851873 let ( repo_dep, rest) = split_repo_dependency ( & deps) ;
852874
@@ -859,15 +881,39 @@ mod tests {
859881
860882 #[ test]
861883 fn split_repo_dependency_returns_none_when_no_repo_like_dep ( ) {
862- let mut deps = FxHashSet :: default ( ) ;
863- deps. insert ( "requests==2.32.0" . to_string ( ) ) ;
864- deps. insert ( "black==24.1.0" . to_string ( ) ) ;
884+ let deps = vec ! [ "requests==2.32.0" . to_string( ) , "black==24.1.0" . to_string( ) ] ;
865885
866886 let ( repo_dep, rest) = split_repo_dependency ( & deps) ;
867887 assert ! ( repo_dep. is_none( ) ) ;
868888 assert_eq ! ( rest, vec![ "black==24.1.0" , "requests==2.32.0" ] ) ;
869889 }
870890
891+ #[ test]
892+ fn marker_repo_and_dependencies_uses_structured_repo ( ) {
893+ let info: InstallInfo = serde_json:: from_value ( serde_json:: json!( {
894+ "schema_version" : 1 ,
895+ "language" : "python" ,
896+ "language_version" : "3.12.0" ,
897+ "repo" : {
898+ "url" : "https://example.com/repo" ,
899+ "rev" : "v1.0.0" ,
900+ } ,
901+ "dependencies" : [ "https://example.com/dependency@v2.0.0" ] ,
902+ "env_path" : "/tmp/hook-env" ,
903+ "toolchain" : "/usr/bin/python3" ,
904+ "extra" : { } ,
905+ } ) )
906+ . expect ( "deserialize install info" ) ;
907+
908+ let ( repo, dependencies) = marker_repo_and_dependencies ( & info) ;
909+
910+ assert_eq ! ( repo. as_deref( ) , Some ( "https://example.com/repo@v1.0.0" ) ) ;
911+ assert_eq ! (
912+ dependencies. as_ref( ) ,
913+ & [ "https://example.com/dependency@v2.0.0" . to_string( ) ]
914+ ) ;
915+ }
916+
871917 #[ test]
872918 fn format_dependency_list_includes_more_suffix ( ) {
873919 let deps = vec ! [ "a" . to_string( ) , "b" . to_string( ) , "c" . to_string( ) ] ;
0 commit comments