diff --git a/crates/agent_ui/src/thread_worktree_archive.rs b/crates/agent_ui/src/thread_worktree_archive.rs index 2cbebcec5a98d4853dfdac0d1aef2535fc4d1bec..732519e25376dc167a690b0e86c680b7437bf807 100644 --- a/crates/agent_ui/src/thread_worktree_archive.rs +++ b/crates/agent_ui/src/thread_worktree_archive.rs @@ -21,26 +21,47 @@ use crate::thread_metadata_store::{ArchivedGitWorktree, ThreadMetadataStore}; /// A thread can have multiple folder paths open, so there may be multiple /// `RootPlan`s per archival operation. Each one captures everything needed to /// persist the worktree's git state and then remove it from disk. +/// +/// All fields are gathered synchronously by [`build_root_plan`] while the +/// worktree is still loaded in open projects. This is important because +/// workspace removal tears down project and repository entities, making +/// them unavailable for the later async persist/remove steps. #[derive(Clone)] pub struct RootPlan { /// Absolute path of the git worktree on disk. pub root_path: PathBuf, /// Absolute path to the main git repository this worktree is linked to. + /// Used both for creating a git ref to prevent GC of WIP commits during + /// [`persist_worktree_state`], and for `git worktree remove` during + /// [`remove_root`]. pub main_repo_path: PathBuf, /// Every open `Project` that has this worktree loaded, so they can all - /// release it during removal. + /// call `remove_worktree` and release it during [`remove_root`]. + /// Multiple projects can reference the same path when the user has the + /// worktree open in more than one workspace. pub affected_projects: Vec, /// The `Repository` entity for this worktree, used to run git commands - /// (commit, reset, etc.). `None` if this path isn't a git worktree. + /// (create WIP commits, stage files, reset) during + /// [`persist_worktree_state`]. `None` when the `GitStore` hasn't created + /// a `Repository` for this worktree yet — in that case, + /// `persist_worktree_state` falls back to creating a temporary headless + /// project to obtain one. pub worktree_repo: Option>, /// The branch the worktree was on, so it can be restored later. + /// `None` if the worktree was in detached HEAD state or if no + /// `Repository` entity was available at planning time (in which case + /// `persist_worktree_state` reads it from the repo snapshot instead). pub branch_name: Option, } /// A `Project` that references a worktree being archived, paired with the -/// `WorktreeId` it uses for that worktree. The same worktree path can appear -/// in multiple open workspaces/projects, and each one needs to remove it -/// during archival. +/// `WorktreeId` it uses for that worktree. +/// +/// The same worktree path can appear in multiple open workspaces/projects +/// (e.g. when the user has two windows open that both include the same +/// linked worktree). Each one needs to call `remove_worktree` and wait for +/// the release during [`remove_root`], otherwise the project would still +/// hold a reference to the directory and `git worktree remove` would fail. #[derive(Clone)] pub struct AffectedProject { pub project: Entity, @@ -64,10 +85,26 @@ pub struct PersistOutcome { /// Builds a [`RootPlan`] for archiving the git worktree at `path`. /// -/// This is a synchronous planning step that inspects all open workspaces to -/// find which projects have this worktree loaded and to locate the -/// `Repository` entity for it. Returns `None` if the path isn't a linked -/// git worktree in any open workspace. +/// This is a synchronous planning step that must run *before* any workspace +/// removal, because it needs live project and repository entities that are +/// torn down when a workspace is removed. It does three things: +/// +/// 1. Finds every `Project` across all open workspaces that has this +/// worktree loaded (`affected_projects`). +/// 2. Looks for a `Repository` entity whose snapshot identifies this path +/// as a linked worktree (`worktree_repo`), which is needed for the git +/// operations in [`persist_worktree_state`]. +/// 3. Determines the `main_repo_path` — the parent repo that owns this +/// linked worktree — needed for both git ref creation and +/// `git worktree remove`. +/// +/// When no `Repository` entity is available (e.g. the `GitStore` hasn't +/// finished scanning), the function falls back to deriving `main_repo_path` +/// from the worktree snapshot's `root_repo_common_dir`. In that case +/// `worktree_repo` is `None` and [`persist_worktree_state`] will create a +/// temporary headless project to obtain one. +/// +/// Returns `None` if no open project has this path as a visible worktree. pub fn build_root_plan( path: &Path, workspaces: &[Entity],