@@ -8,7 +8,9 @@ use super::worktree::{
88 build_clone_destination_path, sanitize_clone_dir_name, sanitize_worktree_name,
99} ;
1010use crate :: backend:: app_server:: WorkspaceSession ;
11- use crate :: shared:: workspaces_core:: rename_worktree_core;
11+ use crate :: shared:: workspaces_core:: {
12+ remove_workspace_core, remove_worktree_core, rename_worktree_core,
13+ } ;
1214use crate :: storage:: { read_workspaces, write_workspaces} ;
1315use crate :: types:: {
1416 AppSettings , WorkspaceEntry , WorkspaceInfo , WorkspaceKind , WorkspaceSettings , WorktreeInfo ,
@@ -394,3 +396,120 @@ fn rename_worktree_updates_name_when_unmodified() {
394396 assert_eq ! ( updated. name, "feature/new" ) ;
395397 } ) ;
396398}
399+
400+ #[ test]
401+ fn remove_workspace_succeeds_when_parent_repo_folder_is_missing ( ) {
402+ run_async ( async {
403+ let temp_dir = std:: env:: temp_dir ( ) . join ( format ! ( "codex-monitor-test-{}" , Uuid :: new_v4( ) ) ) ;
404+ let parent_repo_path = temp_dir. join ( "deleted-parent-repo" ) ;
405+ let child_path = temp_dir. join ( "worktrees" ) . join ( "parent" ) . join ( "feature-a" ) ;
406+ std:: fs:: create_dir_all ( & child_path) . expect ( "create child path" ) ;
407+
408+ let parent = WorkspaceEntry {
409+ id : "parent" . to_string ( ) ,
410+ name : "Parent" . to_string ( ) ,
411+ path : parent_repo_path. to_string_lossy ( ) . to_string ( ) ,
412+ codex_bin : None ,
413+ kind : WorkspaceKind :: Main ,
414+ parent_id : None ,
415+ worktree : None ,
416+ settings : WorkspaceSettings :: default ( ) ,
417+ } ;
418+ let child = WorkspaceEntry {
419+ id : "wt-missing-parent" . to_string ( ) ,
420+ name : "feature-a" . to_string ( ) ,
421+ path : child_path. to_string_lossy ( ) . to_string ( ) ,
422+ codex_bin : None ,
423+ kind : WorkspaceKind :: Worktree ,
424+ parent_id : Some ( parent. id . clone ( ) ) ,
425+ worktree : Some ( WorktreeInfo {
426+ branch : "feature-a" . to_string ( ) ,
427+ } ) ,
428+ settings : WorkspaceSettings :: default ( ) ,
429+ } ;
430+ let workspaces = Mutex :: new ( HashMap :: from ( [
431+ ( parent. id . clone ( ) , parent. clone ( ) ) ,
432+ ( child. id . clone ( ) , child. clone ( ) ) ,
433+ ] ) ) ;
434+ let sessions: Mutex < HashMap < String , Arc < WorkspaceSession > > > = Mutex :: new ( HashMap :: new ( ) ) ;
435+ let storage_path = temp_dir. join ( "workspaces.json" ) ;
436+
437+ remove_workspace_core (
438+ parent. id . clone ( ) ,
439+ & workspaces,
440+ & sessions,
441+ & storage_path,
442+ |_root, _args| async move {
443+ panic ! ( "git should not run when parent repo folder is missing" ) ;
444+ } ,
445+ |_error| false ,
446+ |path| std:: fs:: remove_dir_all ( path) . map_err ( |err| err. to_string ( ) ) ,
447+ true ,
448+ true ,
449+ )
450+ . await
451+ . expect ( "remove workspace" ) ;
452+
453+ assert ! ( !child_path. exists( ) ) ;
454+ let workspaces_guard = workspaces. lock ( ) . await ;
455+ assert ! ( workspaces_guard. is_empty( ) ) ;
456+ } ) ;
457+ }
458+
459+ #[ test]
460+ fn remove_worktree_succeeds_when_parent_repo_folder_is_missing ( ) {
461+ run_async ( async {
462+ let temp_dir = std:: env:: temp_dir ( ) . join ( format ! ( "codex-monitor-test-{}" , Uuid :: new_v4( ) ) ) ;
463+ let parent_repo_path = temp_dir. join ( "deleted-parent-repo" ) ;
464+ let child_path = temp_dir. join ( "worktrees" ) . join ( "parent" ) . join ( "feature-b" ) ;
465+ std:: fs:: create_dir_all ( & child_path) . expect ( "create child path" ) ;
466+
467+ let parent = WorkspaceEntry {
468+ id : "parent" . to_string ( ) ,
469+ name : "Parent" . to_string ( ) ,
470+ path : parent_repo_path. to_string_lossy ( ) . to_string ( ) ,
471+ codex_bin : None ,
472+ kind : WorkspaceKind :: Main ,
473+ parent_id : None ,
474+ worktree : None ,
475+ settings : WorkspaceSettings :: default ( ) ,
476+ } ;
477+ let child = WorkspaceEntry {
478+ id : "wt-remove-only" . to_string ( ) ,
479+ name : "feature-b" . to_string ( ) ,
480+ path : child_path. to_string_lossy ( ) . to_string ( ) ,
481+ codex_bin : None ,
482+ kind : WorkspaceKind :: Worktree ,
483+ parent_id : Some ( parent. id . clone ( ) ) ,
484+ worktree : Some ( WorktreeInfo {
485+ branch : "feature-b" . to_string ( ) ,
486+ } ) ,
487+ settings : WorkspaceSettings :: default ( ) ,
488+ } ;
489+ let workspaces = Mutex :: new ( HashMap :: from ( [
490+ ( parent. id . clone ( ) , parent. clone ( ) ) ,
491+ ( child. id . clone ( ) , child. clone ( ) ) ,
492+ ] ) ) ;
493+ let sessions: Mutex < HashMap < String , Arc < WorkspaceSession > > > = Mutex :: new ( HashMap :: new ( ) ) ;
494+ let storage_path = temp_dir. join ( "workspaces.json" ) ;
495+
496+ remove_worktree_core (
497+ child. id . clone ( ) ,
498+ & workspaces,
499+ & sessions,
500+ & storage_path,
501+ |_root, _args| async move {
502+ panic ! ( "git should not run when parent repo folder is missing" ) ;
503+ } ,
504+ |_error| false ,
505+ |path| std:: fs:: remove_dir_all ( path) . map_err ( |err| err. to_string ( ) ) ,
506+ )
507+ . await
508+ . expect ( "remove worktree" ) ;
509+
510+ assert ! ( !child_path. exists( ) ) ;
511+ let workspaces_guard = workspaces. lock ( ) . await ;
512+ assert ! ( workspaces_guard. contains_key( & parent. id) ) ;
513+ assert ! ( !workspaces_guard. contains_key( & child. id) ) ;
514+ } ) ;
515+ }
0 commit comments