fix(tui/branch): preserve chat history on /branch switch; finalize at repo root to avoid worktree checkout error

- Carry conversation history + strict ordering across AppEvent::SwitchCwd so /branch does not appear to start a new session.
- In /branch finalize, resolve the main repository root via git_common_dir and perform checkout/merge there. This prevents failures like: fatal: 'main' is already checked out (checked out in worktree).
- Keeps ordering stable and shows the switch notice before any prompt runs.
This commit is contained in:
James Peter
2025-09-11 23:53:38 +10:00
parent ca952e112e
commit 0ae8848bda
2 changed files with 84 additions and 6 deletions
+13 -1
View File
@@ -883,6 +883,14 @@ impl App<'_> {
}
}
AppEvent::SwitchCwd(new_cwd, initial_prompt) => {
// Preserve current chat history and ordering before swapping sessions
let carried = match &mut self.app_state {
AppState::Chat { widget } => {
Some(widget.export_history_for_session_swap())
}
_ => None,
};
// Rebuild the chat widget bound to a new cwd, preserving
// current configuration and terminal properties.
let mut cfg = self.config.clone();
@@ -896,11 +904,15 @@ impl App<'_> {
self.terminal_info.clone(),
self.show_order_overlay,
);
// Adopt prior history so the conversation remains visible.
if let Some(state) = carried {
new_widget.import_history_for_session_swap(state);
}
new_widget.enable_perf(self.timing_enabled);
self.app_state = AppState::Chat { widget: Box::new(new_widget) };
// Surface a BackgroundEvent so the user can see the effective cwd
// in the new session before any prompt runs.
// in the new session.
{
use codex_core::protocol::{BackgroundEventEvent, Event, EventMsg};
let msg = format!("✅ Switched to worktree: {}", new_cwd.display());
+71 -5
View File
@@ -451,6 +451,69 @@ enum SystemPlacement {
}
impl ChatWidget<'_> {
/// Export history and ordering state so the UI can swap sessions (e.g., /branch)
/// without losing visible conversation. This drains the current widget's history.
pub(crate) fn export_history_for_session_swap(
&mut self,
) -> (
Vec<Box<dyn HistoryCell>>,
Vec<(u64, i32, u64)>,
Vec<Option<String>>,
u64,
u64,
u64,
) {
let history = std::mem::take(&mut self.history_cells);
let order_ok = std::mem::take(&mut self.cell_order_seq);
let order: Vec<(u64, i32, u64)> =
order_ok.into_iter().map(|o| (o.req, o.out, o.seq)).collect();
let order_dbg = std::mem::take(&mut self.cell_order_dbg);
(
history,
order,
order_dbg,
self.last_seen_request_index,
self.current_request_index,
self.internal_seq,
)
}
/// Import prior history and ordering metadata into a fresh widget.
pub(crate) fn import_history_for_session_swap(
&mut self,
state: (
Vec<Box<dyn HistoryCell>>,
Vec<(u64, i32, u64)>,
Vec<Option<String>>,
u64,
u64,
u64,
),
) {
let (
history,
order,
order_dbg,
last_seen_req,
current_req,
internal_seq,
) = state;
// Replace any starter cells (welcome, etc.) with the carried history
self.history_cells = history;
self.cell_order_seq = order
.into_iter()
.map(|(req, out, seq)| OrderKey { req, out, seq })
.collect();
self.cell_order_dbg = order_dbg;
self.last_seen_request_index = last_seen_req;
self.current_request_index = current_req;
self.internal_seq = internal_seq;
self.welcome_shown = true; // avoid duplicating prelude items later
self.bottom_pane.set_has_chat_history(true);
self.invalidate_height_cache();
self.request_redraw();
}
/// Compute an OrderKey for system (non‑LLM) notices in a way that avoids
/// creating multiple synthetic request buckets before the first provider turn.
fn system_order_key(
@@ -8468,10 +8531,13 @@ impl ChatWidget<'_> {
tokio::spawn(async move {
use tokio::process::Command;
// Resolve repo root and current branch name
let git_root = match codex_core::git_worktree::get_git_root_from(&work_cwd).await {
Ok(p) => p,
Err(e) => {
// Resolve the MAIN repository root (not the linked worktree root).
// Using git_common_dir avoids attempting to checkout the default branch
// inside the temporary worktree, which can fail with
// "already checked out in worktree" errors.
let git_root = match codex_core::git_info::resolve_root_git_project_for_trust(&work_cwd) {
Some(p) => p,
None => {
use codex_core::protocol::BackgroundEventEvent;
use codex_core::protocol::Event;
use codex_core::protocol::EventMsg;
@@ -8479,7 +8545,7 @@ impl ChatWidget<'_> {
id: uuid::Uuid::new_v4().to_string(),
event_seq: 0,
msg: EventMsg::BackgroundEvent(BackgroundEventEvent {
message: format!("`/branch finalize` — not a git repo: {}", e),
message: "`/branch finalize` — not a git repo".to_string(),
}),
order: None,
}));