use std::error::Error;use std::fmt;use std::path::PathBuf;use std::sync::Arc; use anyhow::anyhow;use askpass::AskPassDelegate;use collections::HashSet;use fs::Fs;use gpui::{ AsyncWindowContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, SharedString, Task, TaskExt, WeakEntity,};use project::Project;use project::git_store::Repository;use project::project_settings::ProjectSettings;use project::trusted_worktrees::{PathTrust, TrustedWorktrees};use remote::RemoteConnectionOptions;use settings::Settings;use ui::prelude::*;use workspace::{ MultiWorkspace, OpenMode, PreviousWorkspaceState, ToastView, Workspace, dock::DockPosition,};use zed_actions::NewWorktreeBranchTarget; use git::repository::{FetchOptions, Remote}; use util::ResultExt as _; use crate::askpass_modal::AskPassModal;use crate::git_panel::{open_output, show_error_toast};use crate::worktree_names; /// A remote-tracking branch reference parsed into its remote and branch parts,/// e.g. `origin/main` -> remote `origin`, branch `main`.#[derive(Clone, Debug, PartialEq, Eq)]pub struct RemoteBranchName { pub remote_name: String, pub branch_name: String,} impl RemoteBranchName { pub fn parse(name: &str) -> Option<Self> { let name = name.strip_prefix("refs/remotes/").unwrap_or(name); let (remote_name, branch_name) = name.split_once('/')?; if remote_name.is_empty() || branch_name.is_empty() { return None; } Some(Self { remote_name: remote_name.to_string(), branch_name: branch_name.to_string(), }) } pub fn display_name(&self) -> String { format!("{}/{}", self.remote_name, self.branch_name) }} /// A "create new worktree" option offered to the user. The set of targets is/// derived from repository state by [`worktree_create_targets`] so that the/// worktree picker and the sidebar's new-thread menu stay in sync.#[derive(Clone, Debug, PartialEq, Eq)]pub enum WorktreeCreateTarget { CurrentBranch, DefaultBranch(RemoteBranchName),} impl WorktreeCreateTarget { pub fn branch_target(&self) -> NewWorktreeBranchTarget { match self { WorktreeCreateTarget::CurrentBranch => NewWorktreeBranchTarget::CurrentBranch, WorktreeCreateTarget::DefaultBranch(default_branch) => { NewWorktreeBranchTarget::RemoteBranch { remote_name: default_branch.remote_name.clone(), branch_name: default_branch.branch_name.clone(), } } } } pub fn branch_label( &self, has_multiple_repositories: bool, current_branch_name: Option<&str>, ) -> String { match self { WorktreeCreateTarget::DefaultBranch(default_branch) => default_branch.display_name(), WorktreeCreateTarget::CurrentBranch => { if has_multiple_repositories { "current branches".to_string() } else { current_branch_name.unwrap_or("HEAD").to_string() } } } }} /// Determines which "create new worktree" options to surface for the given/// repository state: prefer the remote default branch when it differs from the/// current branch, and otherwise offer the current branch.pub fn worktree_create_targets( has_multiple_repositories: bool, default_branch: Option<RemoteBranchName>, current_branch_name: Option<&str>,) -> Vec<WorktreeCreateTarget> { if has_multiple_repositories { return vec![WorktreeCreateTarget::CurrentBranch]; } let Some(default_branch) = default_branch else { return vec![WorktreeCreateTarget::CurrentBranch]; }; let is_different = current_branch_name.is_none_or(|current| current != default_branch.branch_name); let mut targets = vec![WorktreeCreateTarget::DefaultBranch(default_branch)]; if is_different { targets.push(WorktreeCreateTarget::CurrentBranch); } targets} /// Whether a worktree operation is creating a new one or switching to an/// existing one. Controls whether the source workspace's state (dock layout,/// open files, agent panel draft) is inherited by the destination.enum WorktreeOperation { Create, Switch,} #[derive(Clone, Copy, PartialEq, Eq)]enum RemoteBranchFetchMode { Fetch, UseLocal,} impl RemoteBranchFetchMode { fn should_fetch(self) -> bool { matches!(self, Self::Fetch) }} #[derive(Debug)]struct WorktreeFetchError { remote_name: String, branch_name: String, source: anyhow::Error,} impl WorktreeFetchError { fn remote_branch_name(&self) -> String { format!("{}/{}", self.remote_name, self.branch_name) } fn output(&self) -> String { format!("git fetch {} failed:\n{:#}", self.remote_name, self.source) }} impl fmt::Display for WorktreeFetchError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { write!( formatter, "git fetch {} failed while creating worktree from {}: {}", self.remote_name, self.remote_branch_name(), self.source ) }} impl Error for WorktreeFetchError { fn source(&self) -> Option<&(dyn Error + 'static)> { Some(self.source.as_ref()) }} struct WorktreeFetchFailedToast { workspace: WeakEntity<Workspace>, worktree_name: Option<String>, branch_target: NewWorktreeBranchTarget, focused_dock: Option<DockPosition>, remote_branch_name: String, operation: SharedString, output: String, focus_handle: FocusHandle,} impl WorktreeFetchFailedToast { fn new( workspace: WeakEntity<Workspace>, worktree_name: Option<String>, branch_target: NewWorktreeBranchTarget, focused_dock: Option<DockPosition>, fetch_error: &WorktreeFetchError, cx: &mut gpui::Context<Self>, ) -> Self { Self { workspace, worktree_name, branch_target, focused_dock, remote_branch_name: fetch_error.remote_branch_name(), operation: format!("fetch {}", fetch_error.remote_name).into(), output: fetch_error.output(), focus_handle: cx.focus_handle(), } }} impl Focusable for WorktreeFetchFailedToast { fn focus_handle(&self, _cx: &gpui::App) -> FocusHandle { self.focus_handle.clone() }} impl EventEmitter<DismissEvent> for WorktreeFetchFailedToast {} impl ToastView for WorktreeFetchFailedToast { fn action(&self) -> Option<workspace::ToastAction> { None } fn auto_dismiss(&self) -> bool { false }} impl Render for WorktreeFetchFailedToast { fn render(&mut self, _window: &mut Window, cx: &mut gpui::Context<Self>) -> impl IntoElement { let workspace_for_retry = self.workspace.clone(); let worktree_name = self.worktree_name.clone(); let branch_target = self.branch_target.clone(); let focused_dock = self.focused_dock; let workspace_for_log = self.workspace.clone(); let operation = self.operation.clone(); let output = self.output.clone(); h_flex() .id("worktree-fetch-failed-toast") .elevation_3(cx) .gap_2() .py_1p5() .pl_2p5() .pr_1p5() .flex_none() .bg(cx.theme().colors().surface_background) .shadow_lg() .child( Icon::new(IconName::XCircle) .size(IconSize::Small) .color(Color::Error), ) .child(Label::new(format!( "git fetch failed for {}", self.remote_branch_name ))) .child( Button::new( "use-local-worktree-base", format!("Use local {}", self.remote_branch_name), ) .color(Color::Muted) .on_click(cx.listener(move |_, _event, window, cx| { cx.emit(DismissEvent); if let Some(workspace) = workspace_for_retry.upgrade() { workspace.update(cx, |workspace, cx| { let task = create_worktree_workspace_inner( workspace, &zed_actions::CreateWorktree { worktree_name: worktree_name.clone(), branch_target: branch_target.clone(), }, window, focused_dock, RemoteBranchFetchMode::UseLocal, // User-initiated retry of a foreground create. true, cx, ); task.detach_and_log_err(cx); }); } })), ) .child( Button::new("view-worktree-fetch-log", "Show Error Logs") .color(Color::Muted) .on_click(cx.listener(move |_, _event, window, cx| { cx.emit(DismissEvent); let output = output.clone(); let operation = operation.clone(); workspace_for_log .update(cx, move |workspace, cx| { open_output(operation, workspace, &output, window, cx) }) .ok(); })), ) .child( IconButton::new("dismiss-worktree-fetch-failed-toast", IconName::Close) .shape(ui::IconButtonShape::Square) .icon_size(IconSize::Small) .icon_color(Color::Muted) .on_click(cx.listener(|_, _event, _window, cx| { cx.emit(DismissEvent); })), ) }} /// Classifies the project's visible worktrees into git-managed repositories/// and non-git paths. Each unique repository is returned only once.pub fn classify_worktrees( project: &Project, cx: &gpui::App,) -> (Vec<Entity<Repository>>, Vec<PathBuf>) { let repositories = project.repositories(cx).clone(); let mut git_repos: Vec<Entity<Repository>> = Vec::new(); let mut non_git_paths: Vec<PathBuf> = Vec::new(); let mut seen_repo_ids = HashSet::default(); for worktree in project.visible_worktrees(cx) { let wt_path = worktree.read(cx).abs_path(); let matching_repo = repositories .iter() .filter_map(|(id, repo)| { let work_dir = repo.read(cx).work_directory_abs_path.clone(); if wt_path.starts_with(work_dir.as_ref()) { Some((*id, repo.clone(), work_dir.as_ref().components().count())) } else { None } }) .max_by( |(left_id, _left_repo, left_depth), (right_id, _right_repo, right_depth)| { left_depth .cmp(right_depth) .then_with(|| left_id.cmp(right_id)) }, ); if let Some((id, repo, _)) = matching_repo { if seen_repo_ids.insert(id) { git_repos.push(repo); } } else { non_git_paths.push(wt_path.to_path_buf()); } } (git_repos, non_git_paths)} /// Resolves a branch target into the ref the new worktree should be based on./// Returns `None` for `CurrentBranch`, meaning "use the current HEAD".pub fn resolve_worktree_branch_target(branch_target: &NewWorktreeBranchTarget) -> Option<String> { match branch_target { NewWorktreeBranchTarget::CurrentBranch => None, NewWorktreeBranchTarget::ExistingBranch { name } => Some(name.clone()), NewWorktreeBranchTarget::RemoteBranch { remote_name, branch_name, } => Some(format!("refs/remotes/{remote_name}/{branch_name}")), }} fn remote_branch_to_fetch(branch_target: &NewWorktreeBranchTarget) -> Option<(&str, &str)> { match branch_target { NewWorktreeBranchTarget::RemoteBranch { remote_name, branch_name, } => Some((remote_name, branch_name)), NewWorktreeBranchTarget::CurrentBranch | NewWorktreeBranchTarget::ExistingBranch { .. } => { None } }} fn create_worktree_askpass_delegate( workspace: WeakEntity<Workspace>, operation: impl Into<SharedString>, window: &mut Window, cx: &mut Context<Workspace>,) -> AskPassDelegate { let operation = operation.into(); let window = window.window_handle(); AskPassDelegate::new(&mut cx.to_async(), move |prompt, tx, cx| { window .update(cx, |_, window, cx| { workspace.update(cx, |workspace, cx| { workspace.toggle_modal(window, cx, |window, cx| { AskPassModal::new(operation.clone(), prompt.into(), tx, window, cx) }); }) }) .ok(); })} async fn fetch_remote_for_worktree_base( git_repos: &[Entity<Repository>], remote_name: String, askpass_delegates: Vec<AskPassDelegate>, cx: &mut AsyncWindowContext,) -> anyhow::Result<()> { if askpass_delegates.len() != git_repos.len() { return Err(anyhow!( "Unable to fetch {remote_name}: missing credential prompt delegate" )); } let fetches = cx.update(|_, cx| { git_repos .iter() .cloned() .zip(askpass_delegates) .map(|(repo, askpass)| { repo.update(cx, |repo, cx| { repo.fetch( FetchOptions::Remote(Remote { name: remote_name.clone().into(), }), askpass, cx, ) }) }) .collect::<Vec<_>>() })?; for fetch in futures::future::join_all(fetches).await { fetch??; } Ok(())} /// Kicks off an async git-worktree creation for each repository. Returns:////// - `creation_infos`: a vec of `(repo, new_path, receiver)` tuples./// - `path_remapping`: `(old_work_dir, new_worktree_path)` pairs for remapping editor tabs.////// Multiple entries in `git_repos` can be linked worktrees of the *same*/// underlying repository (e.g. a project that has both the main checkout and/// one of its linked worktrees open as separate Zed worktrees). Those entries/// resolve to the same target path via [`Repository::path_for_new_linked_worktree`],/// so we create the new worktree only once and remap every contributing/// work directory onto it. Without this dedup, the second `git worktree add`/// fails with "already exists".fn start_worktree_creations( git_repos: &[Entity<Repository>], worktree_name: Option<String>, existing_worktree_names: &[String], existing_worktree_paths: &HashSet<PathBuf>, base_ref: Option<String>, worktree_directory_setting: &str, rng: &mut impl rand::Rng, cx: &mut gpui::App,) -> anyhow::Result<( Vec<( Entity<Repository>, PathBuf, futures::channel::oneshot::Receiver<anyhow::Result<()>>, )>, Vec<(PathBuf, PathBuf)>,)> { let mut creation_infos = Vec::new(); let mut path_remapping = Vec::new(); let mut scheduled_paths: HashSet<PathBuf> = HashSet::default(); let worktree_name = worktree_name.unwrap_or_else(|| { let existing_refs: Vec<&str> = existing_worktree_names.iter().map(|s| s.as_str()).collect(); worktree_names::generate_worktree_name(&existing_refs, rng) .unwrap_or_else(|| "worktree".to_string()) }); for repo in git_repos { let (work_dir, new_path, receiver) = repo.update(cx, |repo, _cx| { let new_path = repo.path_for_new_linked_worktree(&worktree_name, worktree_directory_setting)?; if existing_worktree_paths.contains(&new_path) { anyhow::bail!("A worktree already exists at {}", new_path.display()); } let work_dir = repo.work_directory_abs_path.clone(); // Only the first repo that resolves to a given target path // actually creates the worktree; subsequent linked worktrees of // the same repository just contribute a path remapping. let receiver = if scheduled_paths.contains(&new_path) { None } else { let target = git::repository::CreateWorktreeTarget::Detached { base_sha: base_ref.clone(), }; Some(repo.create_worktree(target, new_path.clone())) }; anyhow::Ok((work_dir, new_path, receiver)) })?;Lines 1–500 of 1783. Load more
use std::error::Error; use std::fmt; use std::path::PathBuf; use std::sync::Arc; use anyhow::anyhow; use askpass::AskPassDelegate; use collections::HashSet; use fs::Fs; use gpui::{ AsyncWindowContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, SharedString, Task, TaskExt, WeakEntity, }; use project::Project; use project::git_store::Repository; use project::project_settings::ProjectSettings; use project::trusted_worktrees::{PathTrust, TrustedWorktrees}; use remote::RemoteConnectionOptions; use settings::Settings; use ui::prelude::*; use workspace::{ MultiWorkspace, OpenMode, PreviousWorkspaceState, ToastView, Workspace, dock::DockPosition, }; use zed_actions::NewWorktreeBranchTarget; use git::repository::{FetchOptions, Remote}; use util::ResultExt as _; use crate::askpass_modal::AskPassModal; use crate::git_panel::{open_output, show_error_toast}; use crate::worktree_names; /// A remote-tracking branch reference parsed into its remote and branch parts, /// e.g. `origin/main` -> remote `origin`, branch `main`. #[derive(Clone, Debug, PartialEq, Eq)] pub struct RemoteBranchName { pub remote_name: String, pub branch_name: String, } impl RemoteBranchName { pub fn parse(name: &str) -> Option<Self> { let name = name.strip_prefix("refs/remotes/").unwrap_or(name); let (remote_name, branch_name) = name.split_once('/')?; if remote_name.is_empty() || branch_name.is_empty() { return None; } Some(Self { remote_name: remote_name.to_string(), branch_name: branch_name.to_string(), }) } pub fn display_name(&self) -> String { format!("{}/{}", self.remote_name, self.branch_name) } } /// A "create new worktree" option offered to the user. The set of targets is /// derived from repository state by [`worktree_create_targets`] so that the /// worktree picker and the sidebar's new-thread menu stay in sync. #[derive(Clone, Debug, PartialEq, Eq)] pub enum WorktreeCreateTarget { CurrentBranch, DefaultBranch(RemoteBranchName), } impl WorktreeCreateTarget { pub fn branch_target(&self) -> NewWorktreeBranchTarget { match self { WorktreeCreateTarget::CurrentBranch => NewWorktreeBranchTarget::CurrentBranch, WorktreeCreateTarget::DefaultBranch(default_branch) => { NewWorktreeBranchTarget::RemoteBranch { remote_name: default_branch.remote_name.clone(), branch_name: default_branch.branch_name.clone(), } } } } pub fn branch_label( &self, has_multiple_repositories: bool, current_branch_name: Option<&str>, ) -> String { match self { WorktreeCreateTarget::DefaultBranch(default_branch) => default_branch.display_name(), WorktreeCreateTarget::CurrentBranch => { if has_multiple_repositories { "current branches".to_string() } else { current_branch_name.unwrap_or("HEAD").to_string() } } } } } /// Determines which "create new worktree" options to surface for the given /// repository state: prefer the remote default branch when it differs from the /// current branch, and otherwise offer the current branch. pub fn worktree_create_targets( has_multiple_repositories: bool, default_branch: Option<RemoteBranchName>, current_branch_name: Option<&str>, ) -> Vec<WorktreeCreateTarget> { if has_multiple_repositories { return vec![WorktreeCreateTarget::CurrentBranch]; } let Some(default_branch) = default_branch else { return vec![WorktreeCreateTarget::CurrentBranch]; }; let is_different = current_branch_name.is_none_or(|current| current != default_branch.branch_name); let mut targets = vec![WorktreeCreateTarget::DefaultBranch(default_branch)]; if is_different { targets.push(WorktreeCreateTarget::CurrentBranch); } targets } /// Whether a worktree operation is creating a new one or switching to an /// existing one. Controls whether the source workspace's state (dock layout, /// open files, agent panel draft) is inherited by the destination. enum WorktreeOperation { Create, Switch, } #[derive(Clone, Copy, PartialEq, Eq)] enum RemoteBranchFetchMode { Fetch, UseLocal, } impl RemoteBranchFetchMode { fn should_fetch(self) -> bool { matches!(self, Self::Fetch) } } #[derive(Debug)] struct WorktreeFetchError { remote_name: String, branch_name: String, source: anyhow::Error, } impl WorktreeFetchError { fn remote_branch_name(&self) -> String { format!("{}/{}", self.remote_name, self.branch_name) } fn output(&self) -> String { format!("git fetch {} failed:\n{:#}", self.remote_name, self.source) } } impl fmt::Display for WorktreeFetchError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { write!( formatter, "git fetch {} failed while creating worktree from {}: {}", self.remote_name, self.remote_branch_name(), self.source ) } } impl Error for WorktreeFetchError { fn source(&self) -> Option<&(dyn Error + 'static)> { Some(self.source.as_ref()) } } struct WorktreeFetchFailedToast { workspace: WeakEntity<Workspace>, worktree_name: Option<String>, branch_target: NewWorktreeBranchTarget, focused_dock: Option<DockPosition>, remote_branch_name: String, operation: SharedString, output: String, focus_handle: FocusHandle, } impl WorktreeFetchFailedToast { fn new( workspace: WeakEntity<Workspace>, worktree_name: Option<String>, branch_target: NewWorktreeBranchTarget, focused_dock: Option<DockPosition>, fetch_error: &WorktreeFetchError, cx: &mut gpui::Context<Self>, ) -> Self { Self { workspace, worktree_name, branch_target, focused_dock, remote_branch_name: fetch_error.remote_branch_name(), operation: format!("fetch {}", fetch_error.remote_name).into(), output: fetch_error.output(), focus_handle: cx.focus_handle(), } } } impl Focusable for WorktreeFetchFailedToast { fn focus_handle(&self, _cx: &gpui::App) -> FocusHandle { self.focus_handle.clone() } } impl EventEmitter<DismissEvent> for WorktreeFetchFailedToast {} impl ToastView for WorktreeFetchFailedToast { fn action(&self) -> Option<workspace::ToastAction> { None } fn auto_dismiss(&self) -> bool { false } } impl Render for WorktreeFetchFailedToast { fn render(&mut self, _window: &mut Window, cx: &mut gpui::Context<Self>) -> impl IntoElement { let workspace_for_retry = self.workspace.clone(); let worktree_name = self.worktree_name.clone(); let branch_target = self.branch_target.clone(); let focused_dock = self.focused_dock; let workspace_for_log = self.workspace.clone(); let operation = self.operation.clone(); let output = self.output.clone(); h_flex() .id("worktree-fetch-failed-toast") .elevation_3(cx) .gap_2() .py_1p5() .pl_2p5() .pr_1p5() .flex_none() .bg(cx.theme().colors().surface_background) .shadow_lg() .child( Icon::new(IconName::XCircle) .size(IconSize::Small) .color(Color::Error), ) .child(Label::new(format!( "git fetch failed for {}", self.remote_branch_name ))) .child( Button::new( "use-local-worktree-base", format!("Use local {}", self.remote_branch_name), ) .color(Color::Muted) .on_click(cx.listener(move |_, _event, window, cx| { cx.emit(DismissEvent); if let Some(workspace) = workspace_for_retry.upgrade() { workspace.update(cx, |workspace, cx| { let task = create_worktree_workspace_inner( workspace, &zed_actions::CreateWorktree { worktree_name: worktree_name.clone(), branch_target: branch_target.clone(), }, window, focused_dock, RemoteBranchFetchMode::UseLocal, // User-initiated retry of a foreground create. true, cx, ); task.detach_and_log_err(cx); }); } })), ) .child( Button::new("view-worktree-fetch-log", "Show Error Logs") .color(Color::Muted) .on_click(cx.listener(move |_, _event, window, cx| { cx.emit(DismissEvent); let output = output.clone(); let operation = operation.clone(); workspace_for_log .update(cx, move |workspace, cx| { open_output(operation, workspace, &output, window, cx) }) .ok(); })), ) .child( IconButton::new("dismiss-worktree-fetch-failed-toast", IconName::Close) .shape(ui::IconButtonShape::Square) .icon_size(IconSize::Small) .icon_color(Color::Muted) .on_click(cx.listener(|_, _event, _window, cx| { cx.emit(DismissEvent); })), ) } } /// Classifies the project's visible worktrees into git-managed repositories /// and non-git paths. Each unique repository is returned only once. pub fn classify_worktrees( project: &Project, cx: &gpui::App, ) -> (Vec<Entity<Repository>>, Vec<PathBuf>) { let repositories = project.repositories(cx).clone(); let mut git_repos: Vec<Entity<Repository>> = Vec::new(); let mut non_git_paths: Vec<PathBuf> = Vec::new(); let mut seen_repo_ids = HashSet::default(); for worktree in project.visible_worktrees(cx) { let wt_path = worktree.read(cx).abs_path(); let matching_repo = repositories .iter() .filter_map(|(id, repo)| { let work_dir = repo.read(cx).work_directory_abs_path.clone(); if wt_path.starts_with(work_dir.as_ref()) { Some((*id, repo.clone(), work_dir.as_ref().components().count())) } else { None } }) .max_by( |(left_id, _left_repo, left_depth), (right_id, _right_repo, right_depth)| { left_depth .cmp(right_depth) .then_with(|| left_id.cmp(right_id)) }, ); if let Some((id, repo, _)) = matching_repo { if seen_repo_ids.insert(id) { git_repos.push(repo); } } else { non_git_paths.push(wt_path.to_path_buf()); } } (git_repos, non_git_paths) } /// Resolves a branch target into the ref the new worktree should be based on. /// Returns `None` for `CurrentBranch`, meaning "use the current HEAD". pub fn resolve_worktree_branch_target(branch_target: &NewWorktreeBranchTarget) -> Option<String> { match branch_target { NewWorktreeBranchTarget::CurrentBranch => None, NewWorktreeBranchTarget::ExistingBranch { name } => Some(name.clone()), NewWorktreeBranchTarget::RemoteBranch { remote_name, branch_name, } => Some(format!("refs/remotes/{remote_name}/{branch_name}")), } } fn remote_branch_to_fetch(branch_target: &NewWorktreeBranchTarget) -> Option<(&str, &str)> { match branch_target { NewWorktreeBranchTarget::RemoteBranch { remote_name, branch_name, } => Some((remote_name, branch_name)), NewWorktreeBranchTarget::CurrentBranch | NewWorktreeBranchTarget::ExistingBranch { .. } => { None } } } fn create_worktree_askpass_delegate( workspace: WeakEntity<Workspace>, operation: impl Into<SharedString>, window: &mut Window, cx: &mut Context<Workspace>, ) -> AskPassDelegate { let operation = operation.into(); let window = window.window_handle(); AskPassDelegate::new(&mut cx.to_async(), move |prompt, tx, cx| { window .update(cx, |_, window, cx| { workspace.update(cx, |workspace, cx| { workspace.toggle_modal(window, cx, |window, cx| { AskPassModal::new(operation.clone(), prompt.into(), tx, window, cx) }); }) }) .ok(); }) } async fn fetch_remote_for_worktree_base( git_repos: &[Entity<Repository>], remote_name: String, askpass_delegates: Vec<AskPassDelegate>, cx: &mut AsyncWindowContext, ) -> anyhow::Result<()> { if askpass_delegates.len() != git_repos.len() { return Err(anyhow!( "Unable to fetch {remote_name}: missing credential prompt delegate" )); } let fetches = cx.update(|_, cx| { git_repos .iter() .cloned() .zip(askpass_delegates) .map(|(repo, askpass)| { repo.update(cx, |repo, cx| { repo.fetch( FetchOptions::Remote(Remote { name: remote_name.clone().into(), }), askpass, cx, ) }) }) .collect::<Vec<_>>() })?; for fetch in futures::future::join_all(fetches).await { fetch??; } Ok(()) } /// Kicks off an async git-worktree creation for each repository. Returns: /// /// - `creation_infos`: a vec of `(repo, new_path, receiver)` tuples. /// - `path_remapping`: `(old_work_dir, new_worktree_path)` pairs for remapping editor tabs. /// /// Multiple entries in `git_repos` can be linked worktrees of the *same* /// underlying repository (e.g. a project that has both the main checkout and /// one of its linked worktrees open as separate Zed worktrees). Those entries /// resolve to the same target path via [`Repository::path_for_new_linked_worktree`], /// so we create the new worktree only once and remap every contributing /// work directory onto it. Without this dedup, the second `git worktree add` /// fails with "already exists". fn start_worktree_creations( git_repos: &[Entity<Repository>], worktree_name: Option<String>, existing_worktree_names: &[String], existing_worktree_paths: &HashSet<PathBuf>, base_ref: Option<String>, worktree_directory_setting: &str, rng: &mut impl rand::Rng, cx: &mut gpui::App, ) -> anyhow::Result<( Vec<( Entity<Repository>, PathBuf, futures::channel::oneshot::Receiver<anyhow::Result<()>>, )>, Vec<(PathBuf, PathBuf)>, )> { let mut creation_infos = Vec::new(); let mut path_remapping = Vec::new(); let mut scheduled_paths: HashSet<PathBuf> = HashSet::default(); let worktree_name = worktree_name.unwrap_or_else(|| { let existing_refs: Vec<&str> = existing_worktree_names.iter().map(|s| s.as_str()).collect(); worktree_names::generate_worktree_name(&existing_refs, rng) .unwrap_or_else(|| "worktree".to_string()) }); for repo in git_repos { let (work_dir, new_path, receiver) = repo.update(cx, |repo, _cx| { let new_path = repo.path_for_new_linked_worktree(&worktree_name, worktree_directory_setting)?; if existing_worktree_paths.contains(&new_path) { anyhow::bail!("A worktree already exists at {}", new_path.display()); } let work_dir = repo.work_directory_abs_path.clone(); // Only the first repo that resolves to a given target path // actually creates the worktree; subsequent linked worktrees of // the same repository just contribute a path remapping. let receiver = if scheduled_paths.contains(&new_path) { None } else { let target = git::repository::CreateWorktreeTarget::Detached { base_sha: base_ref.clone(), }; Some(repo.create_worktree(target, new_path.clone())) }; anyhow::Ok((work_dir, new_path, receiver)) })?;