@@ -63,6 +63,9 @@ pub struct CacheDirResult {
6363 /// This is used for messaging to inform users when worktree cache sharing
6464 /// is active.
6565 pub is_shared_worktree : bool ,
66+ /// The git repository root, if detected during worktree resolution.
67+ /// Captured here so `SCM::new` can skip its own `git rev-parse` call.
68+ pub git_root : Option < AbsoluteSystemPathBuf > ,
6669}
6770
6871/// Configuration errors for turborepo.
@@ -357,8 +360,8 @@ impl ConfigurationOptions {
357360 self . env_mode . unwrap_or_default ( )
358361 }
359362
360- /// Returns the default cache directory path (relative to repo root).
361- const DEFAULT_CACHE_DIR : & ' static str = if cfg ! ( windows) {
363+ /// The default cache directory path (relative to repo root).
364+ pub const DEFAULT_CACHE_DIR : & ' static str = if cfg ! ( windows) {
362365 ".turbo\\ cache"
363366 } else {
364367 ".turbo/cache"
@@ -388,58 +391,76 @@ impl ConfigurationOptions {
388391 /// - `path`: The resolved cache directory path
389392 /// - `is_shared_worktree`: True if using shared cache from main worktree
390393 pub fn resolve_cache_dir ( & self , repo_root : & AbsoluteSystemPath ) -> CacheDirResult {
391- // If explicit cacheDir is configured, always use it (no worktree sharing)
392394 if let Some ( explicit_cache_dir) = & self . cache_dir {
393395 return CacheDirResult {
394396 path : explicit_cache_dir. clone ( ) ,
395397 is_shared_worktree : false ,
398+ git_root : None ,
396399 } ;
397400 }
398401
399- // Try to detect worktree configuration
400- match WorktreeInfo :: detect ( repo_root) {
401- Ok ( worktree_info) => {
402+ let worktree_info = WorktreeInfo :: detect ( repo_root) . ok ( ) ;
403+ self . resolve_cache_dir_with_worktree_info ( worktree_info. as_ref ( ) )
404+ }
405+
406+ /// Resolve cache directory using pre-computed worktree info.
407+ ///
408+ /// This variant avoids spawning a git subprocess, which allows the caller
409+ /// to run worktree detection on a background thread and pass the result in.
410+ pub fn resolve_cache_dir_with_worktree_info (
411+ & self ,
412+ worktree_info : Option < & WorktreeInfo > ,
413+ ) -> CacheDirResult {
414+ if let Some ( explicit_cache_dir) = & self . cache_dir {
415+ return CacheDirResult {
416+ path : explicit_cache_dir. clone ( ) ,
417+ is_shared_worktree : false ,
418+ git_root : None ,
419+ } ;
420+ }
421+
422+ match worktree_info {
423+ Some ( worktree_info) => {
402424 debug ! (
403425 "Worktree detection: current={}, main={}, is_linked={}" ,
404426 worktree_info. worktree_root,
405427 worktree_info. main_worktree_root,
406428 worktree_info. is_linked_worktree( )
407429 ) ;
430+ let git_root = Some ( worktree_info. git_root . clone ( ) ) ;
408431 if worktree_info. is_linked_worktree ( ) {
409- // We're in a linked worktree - use the main worktree's cache
410- // Use turbopath's join_component to ensure consistent path separators
411432 let main_cache_path = worktree_info
412433 . main_worktree_root
413434 . join_component ( ".turbo" )
414435 . join_component ( "cache" ) ;
415436 let result = CacheDirResult {
416437 path : Utf8PathBuf :: from ( main_cache_path. as_str ( ) ) ,
417438 is_shared_worktree : true ,
439+ git_root,
418440 } ;
419441 debug ! ( "Using shared worktree cache at: {}" , result. path) ;
420442 result
421443 } else {
422- // We're in the main worktree - use local cache
423444 debug ! (
424445 "Using local cache (main worktree): {}" ,
425446 Self :: DEFAULT_CACHE_DIR
426447 ) ;
427448 CacheDirResult {
428449 path : Utf8PathBuf :: from ( Self :: DEFAULT_CACHE_DIR ) ,
429450 is_shared_worktree : false ,
451+ git_root,
430452 }
431453 }
432454 }
433- Err ( e) => {
434- // Detection failed - silently fall back to local cache
435- // This is expected for non-git directories, so we don't warn
455+ None => {
436456 debug ! (
437- "Could not detect Git worktree configuration , using local cache: {}" ,
438- e
457+ "No worktree info available , using local cache: {}" ,
458+ Self :: DEFAULT_CACHE_DIR
439459 ) ;
440460 CacheDirResult {
441461 path : Utf8PathBuf :: from ( Self :: DEFAULT_CACHE_DIR ) ,
442462 is_shared_worktree : false ,
463+ git_root : None ,
443464 }
444465 }
445466 }
@@ -850,7 +871,10 @@ mod test {
850871 #[ test]
851872 fn test_resolve_cache_dir_default_returns_relative_path ( ) {
852873 let tmp_dir = TempDir :: new ( ) . unwrap ( ) ;
853- let repo_root = AbsoluteSystemPathBuf :: try_from ( tmp_dir. path ( ) ) . unwrap ( ) ;
874+ let repo_root = AbsoluteSystemPathBuf :: try_from ( tmp_dir. path ( ) )
875+ . unwrap ( )
876+ . to_realpath ( )
877+ . unwrap ( ) ;
854878
855879 // Initialize git repo
856880 std:: process:: Command :: new ( "git" )
@@ -870,6 +894,56 @@ mod test {
870894 ) ;
871895 }
872896
897+ #[ test]
898+ fn test_resolve_cache_dir_captures_git_root ( ) {
899+ let tmp_dir = TempDir :: new ( ) . unwrap ( ) ;
900+ let repo_root = AbsoluteSystemPathBuf :: try_from ( tmp_dir. path ( ) )
901+ . unwrap ( )
902+ . to_realpath ( )
903+ . unwrap ( ) ;
904+
905+ std:: process:: Command :: new ( "git" )
906+ . args ( [ "init" , "." ] )
907+ . current_dir ( & repo_root)
908+ . output ( )
909+ . expect ( "git init failed" ) ;
910+
911+ let config = ConfigurationOptions :: default ( ) ;
912+ let result = config. resolve_cache_dir ( & repo_root) ;
913+
914+ // git_root should be captured from worktree detection so SCM::new
915+ // can skip its own git rev-parse subprocess
916+ assert ! (
917+ result. git_root. is_some( ) ,
918+ "git_root should be captured when worktree detection succeeds"
919+ ) ;
920+ assert_eq ! (
921+ result. git_root. unwrap( ) ,
922+ repo_root,
923+ "git_root should match repo root in a non-worktree repo"
924+ ) ;
925+ }
926+
927+ #[ test]
928+ fn test_resolve_cache_dir_explicit_skips_git_root ( ) {
929+ let tmp_dir = TempDir :: new ( ) . unwrap ( ) ;
930+ let repo_root = AbsoluteSystemPath :: from_std_path ( tmp_dir. path ( ) ) . unwrap ( ) ;
931+
932+ let config = ConfigurationOptions {
933+ cache_dir : Some ( camino:: Utf8PathBuf :: from ( "/my/cache" ) ) ,
934+ ..Default :: default ( )
935+ } ;
936+
937+ let result = config. resolve_cache_dir ( repo_root) ;
938+
939+ // When explicit cache_dir is set, no worktree detection runs,
940+ // so git_root is not available
941+ assert ! (
942+ result. git_root. is_none( ) ,
943+ "git_root should be None when explicit cache_dir bypasses detection"
944+ ) ;
945+ }
946+
873947 /// Integration test that verifies linked worktree returns absolute path to
874948 /// main cache
875949 #[ test]
0 commit comments