diff options
Diffstat (limited to 'src/app/worktrees.rs')
| -rw-r--r-- | src/app/worktrees.rs | 194 |
1 files changed, 194 insertions, 0 deletions
diff --git a/src/app/worktrees.rs b/src/app/worktrees.rs index 12e7ae84..d33e7f3c 100644 --- a/src/app/worktrees.rs +++ b/src/app/worktrees.rs @@ -559,6 +559,52 @@ impl App { }); } + pub(crate) fn submit_worktree_create_via_api(&mut self) { + self.sync_worktree_branch_from_input(); + let Some(create) = &mut self.state.worktree_create else { + return; + }; + let branch = create.branch.trim().to_string(); + if branch.is_empty() { + create.error = Some("branch is required".into()); + return; + } + if create.creating { + return; + } + + create.branch = branch.clone(); + self.state.name_input = branch.clone(); + create.checkout_path = crate::worktree::default_checkout_path( + &self.state.worktree_directory, + &create.repo_name, + &branch, + ); + create.creating = true; + create.error = None; + let workspace_id = create.source_workspace_id.clone(); + let checkout_path = create.checkout_path.display().to_string(); + + let immediate_response = self.dispatch_tui_deferred_api_request( + "tui.worktree.create", + crate::api::schema::Method::WorktreeCreate(crate::api::schema::WorktreeCreateParams { + workspace_id: Some(workspace_id), + cwd: None, + branch: Some(branch), + path: Some(checkout_path), + base: Some("HEAD".into()), + focus: true, + label: None, + }), + ); + if let Some(message) = immediate_api_error_message(immediate_response.as_deref()) { + if let Some(create) = &mut self.state.worktree_create { + create.creating = false; + create.error = Some(message); + } + } + } + pub(crate) fn handle_worktree_remove_key(&mut self, key: KeyEvent) { match key.code { KeyCode::Esc => { @@ -656,6 +702,76 @@ impl App { }); } + pub(crate) fn submit_worktree_open_via_api(&mut self) { + let Some(open) = self.state.worktree_open.as_ref() else { + return; + }; + let Some(entry_idx) = open.selected_entry_index() else { + return; + }; + let Some(entry) = open.entries.get(entry_idx).cloned() else { + return; + }; + let source_workspace_id = open.source_workspace_id.clone(); + + let response = self.dispatch_tui_api_request( + "tui.worktree.open", + crate::api::schema::Method::WorktreeOpen(crate::api::schema::WorktreeOpenParams { + workspace_id: Some(source_workspace_id), + cwd: None, + path: Some(entry.path.display().to_string()), + branch: None, + focus: true, + label: None, + }), + ); + if serde_json::from_str::<crate::api::schema::SuccessResponse>(&response).is_ok() { + self.state.worktree_open = None; + self.state.mode = Mode::Terminal; + } else if let Ok(error) = + serde_json::from_str::<crate::api::schema::ErrorResponse>(&response) + { + if let Some(open) = &mut self.state.worktree_open { + open.error = Some(error.error.message); + } + } + } + + pub(crate) fn submit_worktree_remove_via_api(&mut self) { + let Some(remove) = self.state.worktree_remove.as_mut() else { + return; + }; + if remove.removing { + return; + } + #[cfg(windows)] + if !remove.force_confirmation + && crate::worktree::checkout_has_dirty_files(&remove.path).unwrap_or(false) + { + remove.force_confirmation = true; + remove.error = None; + return; + } + + remove.removing = true; + remove.error = None; + let workspace_id = remove.workspace_id.clone(); + let force = remove.force_confirmation; + let immediate_response = self.dispatch_tui_deferred_api_request( + "tui.worktree.remove", + crate::api::schema::Method::WorktreeRemove(crate::api::schema::WorktreeRemoveParams { + workspace_id, + force, + }), + ); + if let Some(message) = immediate_api_error_message(immediate_response.as_deref()) { + if let Some(remove) = &mut self.state.worktree_remove { + remove.removing = false; + remove.error = Some(message); + } + } + } + pub(crate) fn handle_worktree_add_finished(&mut self, result: WorktreeAddResult) { if result.api_request.is_some() { self.handle_api_worktree_add_finished(result); @@ -864,6 +980,14 @@ impl App { } } +fn immediate_api_error_message(response: Option<&str>) -> Option<String> { + response + .and_then(|response| { + serde_json::from_str::<crate::api::schema::ErrorResponse>(response).ok() + }) + .map(|response| response.error.message) +} + #[cfg(test)] mod tests { use super::*; @@ -1394,6 +1518,76 @@ mod tests { assert_eq!(create.error, None); } + #[test] + fn submit_worktree_create_via_api_restores_modal_on_immediate_rejection() { + let mut app = app_for_worktree_tests(); + app.state.worktree_directory = std::path::PathBuf::from("/w"); + app.state.workspaces = vec![crate::workspace::Workspace::test_new("source")]; + let source_workspace_id = app.state.workspaces[0].id.clone(); + let source_membership = crate::workspace::WorktreeSpaceMembership { + key: "repo-key".into(), + label: "herdr".into(), + repo_root: "/repo/herdr".into(), + checkout_path: "/repo/herdr".into(), + is_linked_worktree: false, + }; + let branch = "issue/191"; + let checkout_path = + crate::worktree::default_checkout_path(&app.state.worktree_directory, "herdr", branch); + let checkout_key = crate::worktree::canonical_or_original(&checkout_path); + app.pending_api_worktree_creates.insert(checkout_key, 1); + app.state.workspaces[0].worktree_space = Some(source_membership.clone()); + app.state.name_input = branch.into(); + app.state.worktree_create = Some(WorktreeCreateState { + source_workspace_id, + source_checkout_path: "/repo/herdr".into(), + source_existing_membership: Some(source_membership), + source_repo_root: "/repo/herdr".into(), + repo_key: "repo-key".into(), + repo_name: "herdr".into(), + branch: branch.into(), + checkout_path, + error: None, + creating: false, + }); + + app.submit_worktree_create_via_api(); + + let create = app.state.worktree_create.as_ref().unwrap(); + assert!(!create.creating); + assert_eq!( + create.error.as_deref(), + Some("worktree operation is already in progress for this checkout") + ); + } + + #[test] + fn submit_worktree_remove_via_api_restores_modal_on_immediate_rejection() { + let mut app = app_for_worktree_tests(); + app.state.workspaces = vec![crate::workspace::Workspace::test_new("issue")]; + let workspace_id = app.state.workspaces[0].id.clone(); + app.state.workspaces[0].worktree_space = Some(crate::workspace::WorktreeSpaceMembership { + key: "repo-key".into(), + label: "herdr".into(), + repo_root: "/repo/herdr".into(), + checkout_path: "/repo/herdr-issue".into(), + is_linked_worktree: true, + }); + app.open_remove_linked_worktree_confirmation(0); + app.pending_api_worktree_removes + .insert(workspace_id.clone(), 1); + + app.submit_worktree_remove_via_api(); + + let remove = app.state.worktree_remove.as_ref().unwrap(); + assert_eq!(remove.workspace_id, workspace_id); + assert!(!remove.removing); + assert_eq!( + remove.error.as_deref(), + Some("worktree operation is already in progress for this checkout") + ); + } + #[tokio::test] async fn ui_worktree_create_emits_api_parity_events_after_membership_is_committed() { let repo = create_committed_repo("app-worktree-create-event-repo"); |
