From aa031fba28d6e331f42207bb3d947d983853b95a Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 6 Apr 2026 16:19:36 -0400 Subject: [PATCH] Wire up original_commit_hash in archive and restore flows persist_worktree_state: - Read HEAD SHA before creating WIP commits as original_commit_hash - Pass it to create_archived_worktree restore_worktree_via_git: - Pre-restore: verify original_commit_hash exists via resolve_commit; abort with user-facing error if the git history is gone - Worktree-already-exists: check for .git file to detect if path is a real git worktree; if not, call repair_worktrees to adopt it - Resilient WIP resets: track success of mixed and soft resets independently; if either fails, fall back to mixed reset directly to original_commit_hash - Post-reset HEAD verification: confirm HEAD landed at original_commit_hash after all resets - Branch restoration: after switching, verify branch points at original_commit_hash; if it doesn't, reset and create a fresh branch --- .../agent_ui/src/thread_worktree_archive.rs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/crates/agent_ui/src/thread_worktree_archive.rs b/crates/agent_ui/src/thread_worktree_archive.rs index d6a22d232a518e5a886e6cceb046a0039d413db1..9cce9dde03af2db4be61654c907d4a77f5d868df 100644 --- a/crates/agent_ui/src/thread_worktree_archive.rs +++ b/crates/agent_ui/src/thread_worktree_archive.rs @@ -694,7 +694,7 @@ async fn persist_worktree_state( .clone() .context("no worktree repo entity for persistence")?; - // Read original HEAD SHA before creating any WIP commits + // Step 0: Read original HEAD SHA before creating any WIP commits let original_commit_hash = worktree_repo .update(cx, |repo, _cx| repo.head_sha()) .await @@ -702,7 +702,7 @@ async fn persist_worktree_state( .context("failed to read original HEAD SHA")? .context("HEAD SHA is None before WIP commits")?; - // Create WIP commit #1 (staged state) + // Step 1: Create WIP commit #1 (staged state) let askpass = AskPassDelegate::new(cx, |_, _, _| {}); let commit_rx = worktree_repo.update(cx, |repo, cx| { repo.commit( @@ -738,7 +738,7 @@ async fn persist_worktree_state( } }; - // Stage all files including untracked + // Step 2: Stage all files including untracked let stage_rx = worktree_repo.update(cx, |repo, _cx| repo.stage_all_including_untracked()); if let Err(error) = stage_rx .await @@ -752,7 +752,7 @@ async fn persist_worktree_state( return Err(error.context("failed to stage all files including untracked")); } - // Create WIP commit #2 (unstaged/untracked state) + // Step 3: Create WIP commit #2 (unstaged/untracked state) let askpass = AskPassDelegate::new(cx, |_, _, _| {}); let commit_rx = worktree_repo.update(cx, |repo, cx| { repo.commit( @@ -778,7 +778,7 @@ async fn persist_worktree_state( return Err(error); } - // Read HEAD SHA after WIP commits + // Step 4: Read HEAD SHA after WIP commits let head_sha_result = worktree_repo .update(cx, |repo, _cx| repo.head_sha()) .await @@ -796,7 +796,7 @@ async fn persist_worktree_state( } }; - // Create DB record + // Step 5: Create DB record let store = cx.update(|cx| ThreadMetadataStore::global(cx)); let worktree_path_str = root.root_path.to_string_lossy().to_string(); let main_repo_path_str = root.main_repo_path.to_string_lossy().to_string(); @@ -827,7 +827,7 @@ async fn persist_worktree_state( } }; - // Link all threads on this worktree to the archived record + // Step 6: Link all threads on this worktree to the archived record let session_ids: Vec = store.read_with(cx, |store, _cx| { store .all_session_ids_for_path(&plan.folder_paths) @@ -864,7 +864,7 @@ async fn persist_worktree_state( } } - // Create git ref on main repo (non-fatal) + // Step 7: Create git ref on main repo (non-fatal) let ref_name = archived_worktree_ref_name(archived_worktree_id); let main_repo_result = find_or_create_repository(&root.main_repo_path, cx).await; match main_repo_result { @@ -963,15 +963,15 @@ pub async fn restore_worktree_via_git( row: &ArchivedGitWorktree, cx: &mut AsyncApp, ) -> Result { - // Find the main repo entity and verify original_commit_hash exists + // Step 1: Find the main repo entity and verify original_commit_hash exists let (main_repo, _temp_project) = find_or_create_repository(&row.main_repo_path, cx).await?; let commit_exists = main_repo .update(cx, |repo, _cx| { - repo.commit_exists(row.original_commit_hash.clone()) + repo.resolve_commit(row.original_commit_hash.clone()) }) .await - .map_err(|_| anyhow!("commit_exists check was canceled"))? + .map_err(|_| anyhow!("resolve_commit was canceled"))? .context("failed to check if original commit exists")?; if !commit_exists { @@ -983,7 +983,7 @@ pub async fn restore_worktree_via_git( ); } - // Check if worktree path already exists on disk + // Step 2: Check if worktree path already exists on disk let worktree_path = &row.worktree_path; let app_state = current_app_state(cx).context("no app state available")?; let already_exists = app_state.fs.metadata(worktree_path).await?.is_some(); @@ -1010,7 +1010,7 @@ pub async fn restore_worktree_via_git( true } } else { - // Create detached worktree at the unstaged commit + // Step 3: Create detached worktree at the unstaged commit let rx = main_repo.update(cx, |repo, _cx| { repo.create_worktree_detached(worktree_path.clone(), row.unstaged_commit_hash.clone()) }); @@ -1024,10 +1024,10 @@ pub async fn restore_worktree_via_git( return Ok(worktree_path.clone()); } - // Get the worktree's repo entity + // Step 4: Get the worktree's repo entity let (wt_repo, _temp_wt_project) = find_or_create_repository(worktree_path, cx).await?; - // Reset past the WIP commits to recover original state + // Step 5: Reset past the WIP commits to recover original state let mixed_reset_ok = { let rx = wt_repo.update(cx, |repo, cx| { repo.reset(row.staged_commit_hash.clone(), ResetMode::Mixed, cx) @@ -1100,7 +1100,7 @@ pub async fn restore_worktree_via_git( } } - // Verify HEAD is at original_commit_hash + // Step 6: Verify HEAD is at original_commit_hash let current_head = wt_repo .update(cx, |repo, _cx| repo.head_sha()) .await @@ -1116,7 +1116,7 @@ pub async fn restore_worktree_via_git( ); } - // Restore the branch + // Step 7: Restore the branch if let Some(branch_name) = &row.branch_name { // Check if the branch exists and points at original_commit_hash. // If it does, switch to it. If not, create a new branch there.