From d328e22579fc6408804275fc16559c826e697881 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Fri, 20 Mar 2026 18:11:01 -0700 Subject: [PATCH] agent-generated code to canonicalize worktree paths before grouping into project groups --- crates/sidebar/src/sidebar.rs | 82 +++++++++++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 8 deletions(-) diff --git a/crates/sidebar/src/sidebar.rs b/crates/sidebar/src/sidebar.rs index 6c4142df5c3f65919a701a29e9a2f7dbd3dd2216..3a21b6535fe58df4df8ab56f35315150827f886a 100644 --- a/crates/sidebar/src/sidebar.rs +++ b/crates/sidebar/src/sidebar.rs @@ -208,6 +208,49 @@ fn workspace_path_list(workspace: &Entity, cx: &App) -> PathList { PathList::new(&workspace.read(cx).root_paths(cx)) } +use std::path::PathBuf; + +/// Build a mapping from git worktree checkout paths to their root repo paths. +/// +/// For each open workspace's repositories, registers both the main repo path +/// (identity mapping) and every linked worktree path → root repo path. +fn build_worktree_root_mapping( + workspaces: &[Entity], + cx: &App, +) -> HashMap> { + let mut mapping = HashMap::default(); + for workspace in workspaces { + for snapshot in root_repository_snapshots(workspace, cx) { + let root = &snapshot.original_repo_abs_path; + mapping.insert(root.to_path_buf(), root.clone()); + mapping.insert(snapshot.work_directory_abs_path.to_path_buf(), root.clone()); + for wt in snapshot.linked_worktrees() { + mapping.insert(wt.path.clone(), root.clone()); + } + } + } + mapping +} + +/// Map each path in a `PathList` to its root repo path (if known), producing +/// a canonical `PathList` suitable for grouping threads across worktrees. +fn canonicalize_path_list( + path_list: &PathList, + worktree_to_root: &HashMap>, +) -> PathList { + let canonical_paths: Vec = path_list + .paths() + .iter() + .map(|p| { + worktree_to_root + .get(p) + .map(|root| root.to_path_buf()) + .unwrap_or_else(|| p.clone()) + }) + .collect(); + PathList::new(&canonical_paths) +} + fn workspace_label_from_path_list(path_list: &PathList) -> SharedString { let mut names = Vec::with_capacity(path_list.paths().len()); for abs_path in path_list.paths() { @@ -690,6 +733,24 @@ impl Sidebar { } } + // Build a mapping from worktree checkout paths → root repo paths so + // that threads saved against a worktree checkout can be grouped under + // the root repo's sidebar header. + let worktree_to_root = build_worktree_root_mapping(&workspaces, cx); + + // Build a canonicalized thread index: for each thread in the store, + // map its folder_paths to root repo paths and index by the result. + let thread_store = SidebarThreadMetadataStore::global(cx); + let canonical_threads: HashMap> = { + let store = thread_store.read(cx); + let mut index: HashMap> = HashMap::default(); + for entry in store.entries() { + let canonical = canonicalize_path_list(&entry.folder_paths, &worktree_to_root); + index.entry(canonical).or_default().push(entry); + } + index + }; + let has_open_projects = workspaces .iter() .any(|ws| !workspace_path_list(ws, cx).paths().is_empty()); @@ -718,10 +779,13 @@ impl Sidebar { if should_load_threads { let mut seen_session_ids: HashSet = HashSet::new(); - // Read threads from the store cache for this workspace's path list. - let thread_store = SidebarThreadMetadataStore::global(cx); - let workspace_rows: Vec<_> = - thread_store.read(cx).entries_for_path(&path_list).collect(); + // Read threads from the canonicalized index so that threads + // saved against a git worktree checkout are grouped under the + // root repo's header. + let workspace_rows: Vec = canonical_threads + .get(&path_list) + .cloned() + .unwrap_or_default(); for row in workspace_rows { seen_session_ids.insert(row.session_id.clone()); let (agent, icon, icon_from_external_svg) = match &row.agent_id { @@ -799,10 +863,12 @@ impl Sidebar { None => ThreadEntryWorkspace::Closed(worktree_path_list.clone()), }; - let worktree_rows: Vec<_> = thread_store - .read(cx) - .entries_for_path(worktree_path_list) - .collect(); + let canonical_wt_path = + canonicalize_path_list(worktree_path_list, &worktree_to_root); + let worktree_rows: Vec = canonical_threads + .get(&canonical_wt_path) + .cloned() + .unwrap_or_default(); for row in worktree_rows { if !seen_session_ids.insert(row.session_id.clone()) { continue;