Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions crates/agent_ui/src/thread_metadata_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,27 +200,30 @@ fn migrate_thread_remote_connections(cx: &mut App, migration_task: Task<anyhow::
return Ok(());
}

let recent_workspaces = workspace_db.recent_project_workspaces(fs.as_ref()).await?;
let recent_workspaces = workspace_db
.recent_project_workspaces_ungrouped(fs.as_ref())
.await?;

let mut local_path_lists = HashSet::<PathList>::default();
let mut remote_path_lists = HashMap::<PathList, RemoteConnectionOptions>::default();

recent_workspaces
.iter()
.filter(|(_, location, path_list, _)| {
!path_list.is_empty() && matches!(location, &SerializedWorkspaceLocation::Local)
.filter(|workspace| {
!workspace.paths.is_empty()
&& matches!(workspace.location, SerializedWorkspaceLocation::Local)
})
.for_each(|(_, _, path_list, _)| {
local_path_lists.insert(path_list.clone());
.for_each(|workspace| {
local_path_lists.insert(workspace.paths.clone());
});

for (_, location, path_list, _) in recent_workspaces {
match location {
for workspace in recent_workspaces {
match workspace.location {
SerializedWorkspaceLocation::Remote(remote_connection)
if !local_path_lists.contains(&path_list) =>
if !local_path_lists.contains(&workspace.paths) =>
{
remote_path_lists
.entry(path_list)
.entry(workspace.paths)
.or_insert(remote_connection);
}
_ => {}
Expand Down
52 changes: 26 additions & 26 deletions crates/agent_ui/src/threads_archive_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ use ui_input::ErasedEditor;
use util::ResultExt;
use util::paths::PathExt;
use workspace::{
CloseWindow, ModalView, PathList, SerializedWorkspaceLocation, Workspace, WorkspaceDb,
WorkspaceId, resolve_worktree_workspaces,
CloseWindow, ModalView, PathList, RecentWorkspace, SerializedWorkspaceLocation, Workspace,
WorkspaceDb, WorkspaceId,
};

use zed_actions::agents_sidebar::FocusSidebarFilter;
Expand Down Expand Up @@ -1127,7 +1127,6 @@ impl ProjectPickerModal {
.await
.log_err()
.unwrap_or_default();
let workspaces = resolve_worktree_workspaces(workspaces, fs.as_ref()).await;
this.update_in(cx, move |this, window, cx| {
this.picker.update(cx, move |picker, cx| {
picker.delegate.workspaces = workspaces;
Expand Down Expand Up @@ -1182,12 +1181,7 @@ struct ProjectPickerDelegate {
archive_view: WeakEntity<ThreadsArchiveView>,
current_workspace_id: Option<WorkspaceId>,
sibling_workspace_ids: HashSet<WorkspaceId>,
workspaces: Vec<(
WorkspaceId,
SerializedWorkspaceLocation,
PathList,
DateTime<Utc>,
)>,
workspaces: Vec<RecentWorkspace>,
filtered_entries: Vec<ProjectPickerEntry>,
selected_index: usize,
focus_handle: FocusHandle,
Expand Down Expand Up @@ -1332,9 +1326,10 @@ impl PickerDelegate for ProjectPickerDelegate {
.workspaces
.iter()
.enumerate()
.filter(|(_, (id, _, _, _))| self.is_sibling_workspace(*id))
.map(|(id, (_, _, paths, _))| {
let combined_string = paths
.filter(|(_, workspace)| self.is_sibling_workspace(workspace.workspace_id))
.map(|(id, workspace)| {
let combined_string = workspace
.identity_paths
.ordered_paths()
.map(|path| path.compact().to_string_lossy().into_owned())
.collect::<Vec<_>>()
Expand Down Expand Up @@ -1364,11 +1359,13 @@ impl PickerDelegate for ProjectPickerDelegate {
.workspaces
.iter()
.enumerate()
.filter(|(_, (id, _, _, _))| {
!self.is_current_workspace(*id) && !self.is_sibling_workspace(*id)
.filter(|(_, workspace)| {
!self.is_current_workspace(workspace.workspace_id)
&& !self.is_sibling_workspace(workspace.workspace_id)
})
.map(|(id, (_, _, paths, _))| {
let combined_string = paths
.map(|(id, workspace)| {
let combined_string = workspace
.identity_paths
.ordered_paths()
.map(|path| path.compact().to_string_lossy().into_owned())
.collect::<Vec<_>>()
Expand Down Expand Up @@ -1406,8 +1403,8 @@ impl PickerDelegate for ProjectPickerDelegate {
entries.push(ProjectPickerEntry::Header("This Window".into()));

if is_empty_query {
for (id, (workspace_id, _, _, _)) in self.workspaces.iter().enumerate() {
if self.is_sibling_workspace(*workspace_id) {
for (id, workspace) in self.workspaces.iter().enumerate() {
if self.is_sibling_workspace(workspace.workspace_id) {
entries.push(ProjectPickerEntry::Workspace(StringMatch {
candidate_id: id,
score: 0.0,
Expand All @@ -1433,9 +1430,9 @@ impl PickerDelegate for ProjectPickerDelegate {
entries.push(ProjectPickerEntry::Header("Recent Projects".into()));

if is_empty_query {
for (id, (workspace_id, _, _, _)) in self.workspaces.iter().enumerate() {
if !self.is_current_workspace(*workspace_id)
&& !self.is_sibling_workspace(*workspace_id)
for (id, workspace) in self.workspaces.iter().enumerate() {
if !self.is_current_workspace(workspace.workspace_id)
&& !self.is_sibling_workspace(workspace.workspace_id)
{
entries.push(ProjectPickerEntry::Workspace(StringMatch {
candidate_id: id,
Expand Down Expand Up @@ -1468,11 +1465,11 @@ impl PickerDelegate for ProjectPickerDelegate {
Some(ProjectPickerEntry::Workspace(hit)) => hit.candidate_id,
_ => return,
};
let Some((_workspace_id, _location, paths, _)) = self.workspaces.get(candidate_id) else {
let Some(workspace) = self.workspaces.get(candidate_id) else {
return;
};

self.update_working_directories_and_unarchive(paths.clone(), window, cx);
self.update_working_directories_and_unarchive(workspace.paths.clone(), window, cx);
cx.emit(DismissEvent);
}

Expand Down Expand Up @@ -1504,17 +1501,20 @@ impl PickerDelegate for ProjectPickerDelegate {
.into_any_element(),
),
ProjectPickerEntry::Workspace(hit) => {
let (_, location, paths, _) = self.workspaces.get(hit.candidate_id)?;
let workspace = self.workspaces.get(hit.candidate_id)?;
let location = &workspace.location;

let ordered_paths: Vec<_> = paths
let ordered_paths: Vec<_> = workspace
.identity_paths
.ordered_paths()
.map(|p| p.compact().to_string_lossy().to_string())
.collect();

let tooltip_path: SharedString = ordered_paths.join("\n").into();

let mut path_start_offset = 0;
let match_labels: Vec<_> = paths
let match_labels: Vec<_> = workspace
.identity_paths
.ordered_paths()
.map(|p| p.compact())
.map(|path| {
Expand Down
20 changes: 0 additions & 20 deletions crates/git/src/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,26 +60,6 @@ pub const GRAPH_CHUNK_SIZE: usize = 1000;
/// Default value for the `git.worktree_directory` setting.
pub const DEFAULT_WORKTREE_DIRECTORY: &str = "../worktrees";

/// Determine the original (main) repository's working directory.
///
/// For linked worktrees, `common_dir` differs from `repository_dir` and
/// points to the main repo's `.git` directory, so we can derive the main
/// repo's working directory from it. For normal repos and submodules,
/// `common_dir` equals `repository_dir`, and the original repo is simply
/// `work_directory` itself.
pub fn original_repo_path(
work_directory: &Path,
common_dir: &Path,
repository_dir: &Path,
) -> PathBuf {
if common_dir != repository_dir {
original_repo_path_from_common_dir(common_dir)
.unwrap_or_else(|| work_directory.to_path_buf())
} else {
work_directory.to_path_buf()
}
}

/// Given the git common directory (from `commondir()`), derive the original
/// repository's working directory.
///
Expand Down
11 changes: 7 additions & 4 deletions crates/project/src/git_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7916,14 +7916,17 @@ impl Repository {
}

/// If `path` is a git linked worktree checkout, resolves it to the main
/// repository's working directory path. Returns `None` if `path` is a normal
/// repository, not a git repo, or if resolution fails.
/// repository's identity path. For regular linked worktrees this is the main
/// repository's working directory; for linked worktrees backed by a bare repo
/// such as `.bare`, this is the parent project directory users think of as the
/// repository root. Returns `None` if `path` is a normal repository, not a git
/// repo, or if resolution fails.
///
/// Resolution works by:
/// 1. Reading the `.git` file to get the `gitdir:` pointer
/// 2. Following that to the worktree-specific git directory
/// 3. Reading the `commondir` file to find the shared `.git` directory
/// 4. Deriving the main repo's working directory from the common dir
/// 4. Deriving the main repo's identity path from the common dir
pub async fn resolve_git_worktree_to_main_repo(fs: &dyn Fs, path: &Path) -> Option<PathBuf> {
let dot_git = path.join(".git");
let metadata = fs.metadata(&dot_git).await.ok()??;
Expand All @@ -7940,7 +7943,7 @@ pub async fn resolve_git_worktree_to_main_repo(fs: &dyn Fs, path: &Path) -> Opti
.canonicalize(&gitdir_abs.join(commondir_content.trim()))
.await
.ok()?;
git::repository::original_repo_path_from_common_dir(&common_dir)
Some(repo_identity_path(&common_dir).to_path_buf())
}

/// Validates that the resolved worktree directory is acceptable:
Expand Down
7 changes: 6 additions & 1 deletion crates/project/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ pub use prettier_store::PrettierStore;
use project_settings::{ProjectSettings, SettingsObserver, SettingsObserverEvent};
#[cfg(target_os = "windows")]
use remote::wsl_path_to_windows_path;
use remote::{RemoteClient, RemoteConnectionOptions};
use remote::{RemoteClient, RemoteConnectionOptions, same_remote_connection_identity};
use rpc::{
AnyProtoClient, ErrorCode,
proto::{LanguageServerPromptResponse, REMOTE_SERVER_PROJECT_ID},
Expand Down Expand Up @@ -6226,6 +6226,11 @@ impl ProjectGroupKey {
pub fn host(&self) -> Option<RemoteConnectionOptions> {
self.host.clone()
}

pub fn matches(&self, other: &ProjectGroupKey) -> bool {
self.paths == other.paths
&& same_remote_connection_identity(self.host.as_ref(), other.host.as_ref())
}
}

pub fn path_suffix(path: &Path, detail: usize) -> String {
Expand Down
29 changes: 29 additions & 0 deletions crates/project/tests/integration/git_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1666,6 +1666,35 @@ mod resolve_worktree_tests {
assert_eq!(result, None);
}

#[gpui::test]
async fn test_resolve_git_worktree_bare_repo_identity_path(cx: &mut TestAppContext) {
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/monty/.bare",
json!({
"worktrees": {
"feature-a": {
"commondir": "../../",
"HEAD": "ref: refs/heads/feature-a"
}
}
}),
)
.await;
fs.insert_tree(
"/monty/feature-a",
json!({
".git": "gitdir: /monty/.bare/worktrees/feature-a",
"src": { "main.rs": "" }
}),
)
.await;

let result =
resolve_git_worktree_to_main_repo(fs.as_ref(), Path::new("/monty/feature-a")).await;
assert_eq!(result, Some(PathBuf::from("/monty")));
}

#[gpui::test]
async fn test_resolve_git_worktree_no_git_returns_none(cx: &mut TestAppContext) {
let fs = FakeFs::new(cx.executor());
Expand Down
Loading
Loading