@@ -295,65 +295,47 @@ impl GitWorktreeManager {
295295 /// For repositories with many worktrees, this significantly reduces the
296296 /// total time compared to sequential processing.
297297 pub fn list_worktrees ( & self ) -> Result < Vec < WorktreeInfo > > {
298- let mut worktrees;
298+ let mut worktrees = Vec :: new ( ) ;
299299 let worktree_names = self . repo . worktrees ( ) ?;
300300
301- // Use parallel processing for better performance
302- use std:: sync:: { Arc , Mutex } ;
303- use std:: thread;
304-
305- let results = Arc :: new ( Mutex :: new ( Vec :: new ( ) ) ) ;
306- let mut handles = vec ! [ ] ;
307-
308301 for name in worktree_names. iter ( ) . flatten ( ) {
309302 if let Ok ( worktree) = self . repo . find_worktree ( name) {
310303 let path = worktree. path ( ) ;
311304 let is_current = self . is_current_worktree ( path) ;
312- let name_clone = name. to_string ( ) ;
313- let path_clone = path. to_path_buf ( ) ;
314305 let is_locked = worktree. is_locked ( ) . is_ok ( ) ;
315- let results_clone = Arc :: clone ( & results) ;
316-
317- // Spawn thread for each worktree to parallelize repository operations
318- let handle = thread:: spawn ( move || {
319- let branch = if let Ok ( wt_repo) = Repository :: open ( & path_clone) {
320- wt_repo
321- . head ( )
322- . ok ( )
323- . and_then ( |h| h. shorthand ( ) . map ( |s| s. to_string ( ) ) )
324- . unwrap_or_else ( || String :: from ( DEFAULT_BRANCH_DETACHED ) )
325- } else {
326- String :: from ( DEFAULT_BRANCH_UNKNOWN )
327- } ;
328306
329- // Get additional status info for the worktree
330- let worktree_status = get_worktree_status ( & path_clone ) ;
331-
332- let info = WorktreeInfo {
333- name : name_clone ,
334- path : path_clone ,
335- branch ,
336- is_locked ,
337- is_current ,
338- has_changes : worktree_status . has_changes ,
339- last_commit : worktree_status . last_commit ,
340- ahead_behind : worktree_status . ahead_behind ,
341- } ;
307+ let branch = if let Ok ( wt_repo ) = Repository :: open ( path ) {
308+ if let Ok ( head ) = wt_repo . head ( ) {
309+ if let Some ( shorthand ) = head . shorthand ( ) {
310+ shorthand . to_string ( )
311+ } else {
312+ String :: from ( DEFAULT_BRANCH_DETACHED )
313+ }
314+ } else {
315+ String :: from ( DEFAULT_BRANCH_DETACHED )
316+ }
317+ } else {
318+ String :: from ( DEFAULT_BRANCH_UNKNOWN )
319+ } ;
342320
343- results_clone. lock ( ) . unwrap ( ) . push ( info) ;
344- } ) ;
321+ // Get additional status info for the worktree
322+ let worktree_status = get_worktree_status ( path) ;
323+
324+ let info = WorktreeInfo {
325+ name : name. to_string ( ) ,
326+ path : path. to_path_buf ( ) ,
327+ branch,
328+ is_locked,
329+ is_current,
330+ has_changes : worktree_status. has_changes ,
331+ last_commit : worktree_status. last_commit ,
332+ ahead_behind : worktree_status. ahead_behind ,
333+ } ;
345334
346- handles . push ( handle ) ;
335+ worktrees . push ( info ) ;
347336 }
348337 }
349338
350- // Wait for all threads to complete
351- for handle in handles {
352- handle. join ( ) . unwrap ( ) ;
353- }
354-
355- worktrees = Arc :: try_unwrap ( results) . unwrap ( ) . into_inner ( ) . unwrap ( ) ;
356-
357339 // Sort by name for consistent ordering
358340 worktrees. sort_by ( |a, b| a. name . cmp ( & b. name ) ) ;
359341
@@ -370,9 +352,107 @@ impl GitWorktreeManager {
370352 ///
371353 /// `true` if the current working directory is within the given worktree path
372354 fn is_current_worktree ( & self , path : & std:: path:: Path ) -> bool {
373- std:: env:: current_dir ( )
374- . map ( |dir| dir. starts_with ( path) )
375- . unwrap_or ( false )
355+ let current_dir = match std:: env:: current_dir ( ) {
356+ Ok ( dir) => dir,
357+ Err ( _) => return false ,
358+ } ;
359+
360+ // Try to canonicalize both paths for accurate comparison
361+ match ( current_dir. canonicalize ( ) , path. canonicalize ( ) ) {
362+ ( Ok ( current_canonical) , Ok ( path_canonical) ) => {
363+ // Both paths canonicalized successfully - use canonical comparison
364+ current_canonical. starts_with ( & path_canonical)
365+ }
366+ _ => {
367+ // Canonicalization failed for one or both paths
368+ // Fallback to absolute path comparison
369+ let current_absolute = if current_dir. is_absolute ( ) {
370+ current_dir
371+ } else {
372+ // Convert relative path to absolute
373+ match std:: env:: current_dir ( ) . map ( |cwd| cwd. join ( & current_dir) ) {
374+ Ok ( abs_path) => abs_path,
375+ Err ( _) => current_dir,
376+ }
377+ } ;
378+
379+ let path_absolute = if path. is_absolute ( ) {
380+ path. to_path_buf ( )
381+ } else {
382+ // Convert relative path to absolute from repository base
383+ let repo_base = if let Some ( workdir) = self . repo . workdir ( ) {
384+ workdir. to_path_buf ( )
385+ } else {
386+ // For bare repositories, use the repository path
387+ self . repo . path ( ) . to_path_buf ( )
388+ } ;
389+ repo_base. join ( path)
390+ } ;
391+
392+ // Normalize paths by removing redundant components
393+ let current_normalized = self . normalize_path ( & current_absolute) ;
394+ let path_normalized = self . normalize_path ( & path_absolute) ;
395+
396+ current_normalized. starts_with ( & path_normalized)
397+ }
398+ }
399+ }
400+
401+ /// Normalizes a path by removing redundant components like "." and ".."
402+ ///
403+ /// This method provides a cross-platform way to normalize paths when
404+ /// canonicalization fails (e.g., for non-existent paths or due to permissions).
405+ ///
406+ /// # Arguments
407+ ///
408+ /// * `path` - The path to normalize
409+ ///
410+ /// # Returns
411+ ///
412+ /// A normalized PathBuf with redundant components removed
413+ fn normalize_path ( & self , path : & std:: path:: Path ) -> std:: path:: PathBuf {
414+ use std:: path:: Component ;
415+
416+ let mut normalized = std:: path:: PathBuf :: new ( ) ;
417+
418+ for component in path. components ( ) {
419+ match component {
420+ Component :: Prefix ( ..) => normalized. push ( component) ,
421+ Component :: RootDir => normalized. push ( component) ,
422+ Component :: CurDir => {
423+ // Skip "." components
424+ }
425+ Component :: ParentDir => {
426+ // Handle ".." by removing the last component if possible
427+ if let Some ( last_component) = normalized. components ( ) . next_back ( ) {
428+ match last_component {
429+ Component :: Prefix ( ..) | Component :: RootDir => {
430+ // Cannot go up from root or prefix
431+ normalized. push ( component) ;
432+ }
433+ Component :: ParentDir => {
434+ // Multiple ".." in sequence
435+ normalized. push ( component) ;
436+ }
437+ Component :: Normal ( _) => {
438+ // Remove the previous normal component
439+ normalized. pop ( ) ;
440+ }
441+ Component :: CurDir => {
442+ // Should not happen since we skip CurDir above
443+ normalized. push ( component) ;
444+ }
445+ }
446+ } else {
447+ // No components to remove, keep the ".."
448+ normalized. push ( component) ;
449+ }
450+ }
451+ Component :: Normal ( _) => normalized. push ( component) ,
452+ }
453+ }
454+
455+ normalized
376456 }
377457
378458 /// Gets the current branch name for a worktree
@@ -1700,4 +1780,42 @@ mod tests {
17001780
17011781 Ok ( ( ) )
17021782 }
1783+
1784+ #[ test]
1785+ fn test_normalize_path_basic ( ) -> Result < ( ) > {
1786+ if let Ok ( manager) = GitWorktreeManager :: new ( ) {
1787+ let test_path = std:: path:: Path :: new ( "/home/user/./project/../project/src" ) ;
1788+ let normalized = manager. normalize_path ( test_path) ;
1789+ let expected = std:: path:: PathBuf :: from ( "/home/user/project/src" ) ;
1790+ assert_eq ! ( normalized, expected) ;
1791+ }
1792+ Ok ( ( ) )
1793+ }
1794+
1795+ #[ test]
1796+ fn test_normalize_path_with_parent_dirs ( ) -> Result < ( ) > {
1797+ if let Ok ( manager) = GitWorktreeManager :: new ( ) {
1798+ let test_path = std:: path:: Path :: new ( "/home/user/project/../../user/project" ) ;
1799+ let normalized = manager. normalize_path ( test_path) ;
1800+ let expected = std:: path:: PathBuf :: from ( "/home/user/project" ) ;
1801+ assert_eq ! ( normalized, expected) ;
1802+ }
1803+ Ok ( ( ) )
1804+ }
1805+
1806+ #[ test]
1807+ fn test_is_current_worktree_same_path ( ) -> Result < ( ) > {
1808+ if let Ok ( manager) = GitWorktreeManager :: new ( ) {
1809+ // Test with current directory
1810+ if let Ok ( current_dir) = std:: env:: current_dir ( ) {
1811+ // Current directory should match itself
1812+ assert ! ( manager. is_current_worktree( & current_dir) ) ;
1813+
1814+ // A subdirectory of current should not match
1815+ let subdir = current_dir. join ( "subdir" ) ;
1816+ assert ! ( !manager. is_current_worktree( & subdir) ) ;
1817+ }
1818+ }
1819+ Ok ( ( ) )
1820+ }
17031821}
0 commit comments