1use std::path::PathBuf;
2use std::sync::Arc;
3
4use anyhow::anyhow;
5use collections::HashSet;
6use fs::Fs;
7use gpui::{AsyncWindowContext, Entity, SharedString, WeakEntity};
8use project::Project;
9use project::git_store::Repository;
10use project::project_settings::ProjectSettings;
11use project::trusted_worktrees::{PathTrust, TrustedWorktrees};
12use remote::RemoteConnectionOptions;
13use settings::Settings;
14use workspace::{MultiWorkspace, OpenMode, PreviousWorkspaceState, Workspace, dock::DockPosition};
15use zed_actions::NewWorktreeBranchTarget;
16
17use util::ResultExt as _;
18
19use crate::git_panel::show_error_toast;
20use crate::worktree_names;
21
22/// Whether a worktree operation is creating a new one or switching to an
23/// existing one. Controls whether the source workspace's state (dock layout,
24/// open files, agent panel draft) is inherited by the destination.
25enum WorktreeOperation {
26 Create,
27 Switch,
28}
29
30/// Classifies the project's visible worktrees into git-managed repositories
31/// and non-git paths. Each unique repository is returned only once.
32pub fn classify_worktrees(
33 project: &Project,
34 cx: &gpui::App,
35) -> (Vec<Entity<Repository>>, Vec<PathBuf>) {
36 let repositories = project.repositories(cx).clone();
37 let mut git_repos: Vec<Entity<Repository>> = Vec::new();
38 let mut non_git_paths: Vec<PathBuf> = Vec::new();
39 let mut seen_repo_ids = HashSet::default();
40
41 for worktree in project.visible_worktrees(cx) {
42 let wt_path = worktree.read(cx).abs_path();
43
44 let matching_repo = repositories
45 .iter()
46 .filter_map(|(id, repo)| {
47 let work_dir = repo.read(cx).work_directory_abs_path.clone();
48 if wt_path.starts_with(work_dir.as_ref()) {
49 Some((*id, repo.clone(), work_dir.as_ref().components().count()))
50 } else {
51 None
52 }
53 })
54 .max_by(
55 |(left_id, _left_repo, left_depth), (right_id, _right_repo, right_depth)| {
56 left_depth
57 .cmp(right_depth)
58 .then_with(|| left_id.cmp(right_id))
59 },
60 );
61
62 if let Some((id, repo, _)) = matching_repo {
63 if seen_repo_ids.insert(id) {
64 git_repos.push(repo);
65 }
66 } else {
67 non_git_paths.push(wt_path.to_path_buf());
68 }
69 }
70
71 (git_repos, non_git_paths)
72}
73
74/// Resolves a branch target into the ref the new worktree should be based on.
75/// Returns `None` for `CurrentBranch`, meaning "use the current HEAD".
76pub fn resolve_worktree_branch_target(branch_target: &NewWorktreeBranchTarget) -> Option<String> {
77 match branch_target {
78 NewWorktreeBranchTarget::CurrentBranch => None,
79 NewWorktreeBranchTarget::ExistingBranch { name } => Some(name.clone()),
80 }
81}
82
83/// Kicks off an async git-worktree creation for each repository. Returns:
84///
85/// - `creation_infos`: a vec of `(repo, new_path, receiver)` tuples.
86/// - `path_remapping`: `(old_work_dir, new_worktree_path)` pairs for remapping editor tabs.
87fn start_worktree_creations(
88 git_repos: &[Entity<Repository>],
89 worktree_name: Option<String>,
90 existing_worktree_names: &[String],
91 existing_worktree_paths: &HashSet<PathBuf>,
92 base_ref: Option<String>,
93 worktree_directory_setting: &str,
94 rng: &mut impl rand::Rng,
95 cx: &mut gpui::App,
96) -> anyhow::Result<(
97 Vec<(
98 Entity<Repository>,
99 PathBuf,
100 futures::channel::oneshot::Receiver<anyhow::Result<()>>,
101 )>,
102 Vec<(PathBuf, PathBuf)>,
103)> {
104 let mut creation_infos = Vec::new();
105 let mut path_remapping = Vec::new();
106
107 let worktree_name = worktree_name.unwrap_or_else(|| {
108 let existing_refs: Vec<&str> = existing_worktree_names.iter().map(|s| s.as_str()).collect();
109 worktree_names::generate_worktree_name(&existing_refs, rng)
110 .unwrap_or_else(|| "worktree".to_string())
111 });
112
113 for repo in git_repos {
114 let (work_dir, new_path, receiver) = repo.update(cx, |repo, _cx| {
115 let new_path =
116 repo.path_for_new_linked_worktree(&worktree_name, worktree_directory_setting)?;
117 if existing_worktree_paths.contains(&new_path) {
118 anyhow::bail!("A worktree already exists at {}", new_path.display());
119 }
120 let target = git::repository::CreateWorktreeTarget::Detached {
121 base_sha: base_ref.clone(),
122 };
123 let receiver = repo.create_worktree(target, new_path.clone());
124 let work_dir = repo.work_directory_abs_path.clone();
125 anyhow::Ok((work_dir, new_path, receiver))
126 })?;
127 path_remapping.push((work_dir.to_path_buf(), new_path.clone()));
128 creation_infos.push((repo.clone(), new_path, receiver));
129 }
130
131 Ok((creation_infos, path_remapping))
132}
133
134/// Waits for every in-flight worktree creation to complete. If any
135/// creation fails, all successfully-created worktrees are rolled back
136/// (removed) so the project isn't left in a half-migrated state.
137pub async fn await_and_rollback_on_failure(
138 creation_infos: Vec<(
139 Entity<Repository>,
140 PathBuf,
141 futures::channel::oneshot::Receiver<anyhow::Result<()>>,
142 )>,
143 fs: Arc<dyn Fs>,
144 cx: &mut AsyncWindowContext,
145) -> anyhow::Result<Vec<PathBuf>> {
146 let mut created_paths: Vec<PathBuf> = Vec::new();
147 let mut repos_and_paths: Vec<(Entity<Repository>, PathBuf)> = Vec::new();
148 let mut first_error: Option<anyhow::Error> = None;
149
150 for (repo, new_path, receiver) in creation_infos {
151 repos_and_paths.push((repo.clone(), new_path.clone()));
152 match receiver.await {
153 Ok(Ok(())) => {
154 created_paths.push(new_path);
155 }
156 Ok(Err(err)) => {
157 if first_error.is_none() {
158 first_error = Some(err);
159 }
160 }
161 Err(_canceled) => {
162 if first_error.is_none() {
163 first_error = Some(anyhow!("Worktree creation was canceled"));
164 }
165 }
166 }
167 }
168
169 let Some(err) = first_error else {
170 return Ok(created_paths);
171 };
172
173 // Rollback all attempted worktrees
174 let mut rollback_futures = Vec::new();
175 for (rollback_repo, rollback_path) in &repos_and_paths {
176 let receiver = cx
177 .update(|_, cx| {
178 rollback_repo.update(cx, |repo, _cx| {
179 repo.remove_worktree(rollback_path.clone(), true)
180 })
181 })
182 .ok();
183
184 rollback_futures.push((rollback_path.clone(), receiver));
185 }
186
187 let mut rollback_failures: Vec<String> = Vec::new();
188 for (path, receiver_opt) in rollback_futures {
189 let mut git_remove_failed = false;
190
191 if let Some(receiver) = receiver_opt {
192 match receiver.await {
193 Ok(Ok(())) => {}
194 Ok(Err(rollback_err)) => {
195 log::error!(
196 "git worktree remove failed for {}: {rollback_err}",
197 path.display()
198 );
199 git_remove_failed = true;
200 }
201 Err(canceled) => {
202 log::error!(
203 "git worktree remove failed for {}: {canceled}",
204 path.display()
205 );
206 git_remove_failed = true;
207 }
208 }
209 } else {
210 log::error!(
211 "failed to dispatch git worktree remove for {}",
212 path.display()
213 );
214 git_remove_failed = true;
215 }
216
217 if git_remove_failed {
218 if let Err(fs_err) = fs
219 .remove_dir(
220 &path,
221 fs::RemoveOptions {
222 recursive: true,
223 ignore_if_not_exists: true,
224 },
225 )
226 .await
227 {
228 let msg = format!("{}: failed to remove directory: {fs_err}", path.display());
229 log::error!("{}", msg);
230 rollback_failures.push(msg);
231 }
232 }
233 }
234 let mut error_message = format!("Failed to create worktree: {err}");
235 if !rollback_failures.is_empty() {
236 error_message.push_str("\n\nFailed to clean up: ");
237 error_message.push_str(&rollback_failures.join(", "));
238 }
239 Err(anyhow!(error_message))
240}
241
242/// Propagates worktree trust from the source workspace to the new workspace.
243/// If the source project's worktrees are all trusted, the new worktree paths
244/// will also be trusted automatically.
245fn maybe_propagate_worktree_trust(
246 source_workspace: &WeakEntity<Workspace>,
247 new_workspace: &Entity<Workspace>,
248 paths: &[PathBuf],
249 cx: &mut AsyncWindowContext,
250) {
251 cx.update(|_, cx| {
252 if ProjectSettings::get_global(cx).session.trust_all_worktrees {
253 return;
254 }
255 let Some(trusted_store) = TrustedWorktrees::try_get_global(cx) else {
256 return;
257 };
258
259 let source_is_trusted = source_workspace
260 .upgrade()
261 .map(|workspace| {
262 let source_worktree_store = workspace.read(cx).project().read(cx).worktree_store();
263 !trusted_store
264 .read(cx)
265 .has_restricted_worktrees(&source_worktree_store, cx)
266 })
267 .unwrap_or(false);
268
269 if !source_is_trusted {
270 return;
271 }
272
273 let worktree_store = new_workspace.read(cx).project().read(cx).worktree_store();
274 let paths_to_trust: HashSet<_> = paths
275 .iter()
276 .filter_map(|path| {
277 let (worktree, _) = worktree_store.read(cx).find_worktree(path, cx)?;
278 Some(PathTrust::Worktree(worktree.read(cx).id()))
279 })
280 .collect();
281
282 if !paths_to_trust.is_empty() {
283 trusted_store.update(cx, |store, cx| {
284 store.trust(&worktree_store, paths_to_trust, cx);
285 });
286 }
287 })
288 .ok();
289}
290
291/// Handles the `CreateWorktree` action generically, without any agent panel involvement.
292/// Creates a new git worktree, opens the workspace, restores layout and files.
293pub fn handle_create_worktree(
294 workspace: &mut Workspace,
295 action: &zed_actions::CreateWorktree,
296 window: &mut gpui::Window,
297 fallback_focused_dock: Option<DockPosition>,
298 cx: &mut gpui::Context<Workspace>,
299) {
300 let project = workspace.project().clone();
301
302 if project.read(cx).repositories(cx).is_empty() {
303 log::error!("create_worktree: no git repository in the project");
304 return;
305 }
306 if project.read(cx).is_via_collab() {
307 log::error!("create_worktree: not supported in collab projects");
308 return;
309 }
310
311 // Guard against concurrent creation
312 if workspace.active_worktree_creation().label.is_some() {
313 return;
314 }
315
316 let previous_state =
317 workspace.capture_state_for_worktree_switch(window, fallback_focused_dock, cx);
318 let workspace_handle = workspace.weak_handle();
319 let window_handle = window.window_handle().downcast::<MultiWorkspace>();
320 let remote_connection_options = project.read(cx).remote_connection_options(cx);
321
322 let (git_repos, non_git_paths) = classify_worktrees(project.read(cx), cx);
323
324 if git_repos.is_empty() {
325 show_error_toast(
326 cx.entity(),
327 "worktree create",
328 anyhow!("No git repositories found in the project"),
329 cx,
330 );
331 return;
332 }
333
334 if remote_connection_options.is_some() {
335 let is_disconnected = project
336 .read(cx)
337 .remote_client()
338 .is_some_and(|client| client.read(cx).is_disconnected());
339 if is_disconnected {
340 show_error_toast(
341 cx.entity(),
342 "worktree create",
343 anyhow!("Cannot create worktree: remote connection is not active"),
344 cx,
345 );
346 return;
347 }
348 }
349
350 let worktree_name = action.worktree_name.clone();
351 let branch_target = action.branch_target.clone();
352 let display_name: SharedString = worktree_name
353 .as_deref()
354 .unwrap_or("worktree")
355 .to_string()
356 .into();
357
358 workspace.set_active_worktree_creation(Some(display_name), false, cx);
359
360 cx.spawn_in(window, async move |_workspace_entity, mut cx| {
361 let result = do_create_worktree(
362 git_repos,
363 non_git_paths,
364 worktree_name,
365 branch_target,
366 previous_state,
367 workspace_handle.clone(),
368 window_handle,
369 remote_connection_options,
370 &mut cx,
371 )
372 .await;
373
374 if let Err(err) = &result {
375 log::error!("Failed to create worktree: {err}");
376 workspace_handle
377 .update(cx, |workspace, cx| {
378 workspace.set_active_worktree_creation(None, false, cx);
379 show_error_toast(cx.entity(), "worktree create", anyhow!("{err:#}"), cx);
380 })
381 .ok();
382 }
383
384 result
385 })
386 .detach_and_log_err(cx);
387}
388
389pub fn handle_switch_worktree(
390 workspace: &mut Workspace,
391 action: &zed_actions::SwitchWorktree,
392 window: &mut gpui::Window,
393 fallback_focused_dock: Option<DockPosition>,
394 cx: &mut gpui::Context<Workspace>,
395) {
396 let project = workspace.project().clone();
397
398 if project.read(cx).repositories(cx).is_empty() {
399 log::error!("switch_to_worktree: no git repository in the project");
400 return;
401 }
402 if project.read(cx).is_via_collab() {
403 log::error!("switch_to_worktree: not supported in collab projects");
404 return;
405 }
406
407 // Guard against concurrent creation
408 if workspace.active_worktree_creation().label.is_some() {
409 return;
410 }
411
412 let previous_state =
413 workspace.capture_state_for_worktree_switch(window, fallback_focused_dock, cx);
414 let workspace_handle = workspace.weak_handle();
415 let window_handle = window.window_handle().downcast::<MultiWorkspace>();
416 let remote_connection_options = project.read(cx).remote_connection_options(cx);
417
418 let (git_repos, non_git_paths) = classify_worktrees(project.read(cx), cx);
419
420 let git_repo_work_dirs: Vec<PathBuf> = git_repos
421 .iter()
422 .map(|repo| repo.read(cx).work_directory_abs_path.to_path_buf())
423 .collect();
424
425 let display_name: SharedString = action.display_name.clone().into();
426
427 workspace.set_active_worktree_creation(Some(display_name), true, cx);
428
429 let worktree_path = action.path.clone();
430
431 cx.spawn_in(window, async move |_workspace_entity, mut cx| {
432 let result = do_switch_worktree(
433 worktree_path,
434 git_repo_work_dirs,
435 non_git_paths,
436 previous_state,
437 workspace_handle.clone(),
438 window_handle,
439 remote_connection_options,
440 &mut cx,
441 )
442 .await;
443
444 if let Err(err) = &result {
445 log::error!("Failed to switch worktree: {err}");
446 workspace_handle
447 .update(cx, |workspace, cx| {
448 workspace.set_active_worktree_creation(None, false, cx);
449 show_error_toast(cx.entity(), "worktree switch", anyhow!("{err:#}"), cx);
450 })
451 .ok();
452 }
453
454 result
455 })
456 .detach_and_log_err(cx);
457}
458
459async fn do_create_worktree(
460 git_repos: Vec<Entity<Repository>>,
461 non_git_paths: Vec<PathBuf>,
462 worktree_name: Option<String>,
463 branch_target: NewWorktreeBranchTarget,
464 previous_state: PreviousWorkspaceState,
465 workspace: WeakEntity<Workspace>,
466 window_handle: Option<gpui::WindowHandle<MultiWorkspace>>,
467 remote_connection_options: Option<RemoteConnectionOptions>,
468 cx: &mut AsyncWindowContext,
469) -> anyhow::Result<()> {
470 // List existing worktrees from all repos to detect name collisions
471 let worktree_receivers: Vec<_> = cx.update(|_, cx| {
472 git_repos
473 .iter()
474 .map(|repo| repo.update(cx, |repo, _cx| repo.worktrees()))
475 .collect()
476 })?;
477 let worktree_directory_setting = cx.update(|_, cx| {
478 ProjectSettings::get_global(cx)
479 .git
480 .worktree_directory
481 .clone()
482 })?;
483
484 let mut existing_worktree_names = Vec::new();
485 let mut existing_worktree_paths = HashSet::default();
486 for result in futures::future::join_all(worktree_receivers).await {
487 match result {
488 Ok(Ok(worktrees)) => {
489 for worktree in worktrees {
490 if let Some(name) = worktree
491 .path
492 .parent()
493 .and_then(|p| p.file_name())
494 .and_then(|n| n.to_str())
495 {
496 existing_worktree_names.push(name.to_string());
497 }
498 existing_worktree_paths.insert(worktree.path.clone());
499 }
500 }
501 Ok(Err(err)) => {
502 Err::<(), _>(err).log_err();
503 }
504 Err(_) => {}
505 }
506 }
507
508 let mut rng = rand::rng();
509
510 let base_ref = resolve_worktree_branch_target(&branch_target);
511
512 let (creation_infos, path_remapping) = cx.update(|_, cx| {
513 start_worktree_creations(
514 &git_repos,
515 worktree_name,
516 &existing_worktree_names,
517 &existing_worktree_paths,
518 base_ref,
519 &worktree_directory_setting,
520 &mut rng,
521 cx,
522 )
523 })??;
524
525 let fs = cx.update(|_, cx| <dyn Fs>::global(cx))?;
526
527 let created_paths = await_and_rollback_on_failure(creation_infos, fs, cx).await?;
528
529 let mut all_paths = created_paths;
530 let has_non_git = !non_git_paths.is_empty();
531 all_paths.extend(non_git_paths.iter().cloned());
532
533 open_worktree_workspace(
534 all_paths,
535 path_remapping,
536 non_git_paths,
537 has_non_git,
538 previous_state,
539 workspace,
540 window_handle,
541 remote_connection_options,
542 WorktreeOperation::Create,
543 cx,
544 )
545 .await
546}
547
548async fn do_switch_worktree(
549 worktree_path: PathBuf,
550 git_repo_work_dirs: Vec<PathBuf>,
551 non_git_paths: Vec<PathBuf>,
552 previous_state: PreviousWorkspaceState,
553 workspace: WeakEntity<Workspace>,
554 window_handle: Option<gpui::WindowHandle<MultiWorkspace>>,
555 remote_connection_options: Option<RemoteConnectionOptions>,
556 cx: &mut AsyncWindowContext,
557) -> anyhow::Result<()> {
558 let path_remapping: Vec<(PathBuf, PathBuf)> = git_repo_work_dirs
559 .iter()
560 .map(|work_dir| (work_dir.clone(), worktree_path.clone()))
561 .collect();
562
563 let mut all_paths = vec![worktree_path];
564 let has_non_git = !non_git_paths.is_empty();
565 all_paths.extend(non_git_paths.iter().cloned());
566
567 open_worktree_workspace(
568 all_paths,
569 path_remapping,
570 non_git_paths,
571 has_non_git,
572 previous_state,
573 workspace,
574 window_handle,
575 remote_connection_options,
576 WorktreeOperation::Switch,
577 cx,
578 )
579 .await
580}
581
582/// Core workspace opening logic shared by both create and switch flows.
583async fn open_worktree_workspace(
584 all_paths: Vec<PathBuf>,
585 path_remapping: Vec<(PathBuf, PathBuf)>,
586 non_git_paths: Vec<PathBuf>,
587 has_non_git: bool,
588 previous_state: PreviousWorkspaceState,
589 workspace: WeakEntity<Workspace>,
590 window_handle: Option<gpui::WindowHandle<MultiWorkspace>>,
591 remote_connection_options: Option<RemoteConnectionOptions>,
592 operation: WorktreeOperation,
593 cx: &mut AsyncWindowContext,
594) -> anyhow::Result<()> {
595 let window_handle = window_handle
596 .ok_or_else(|| anyhow!("No window handle available for workspace creation"))?;
597
598 let focused_dock = previous_state.focused_dock;
599
600 let is_creating_new_worktree = matches!(operation, WorktreeOperation::Create);
601
602 let source_for_transfer = if is_creating_new_worktree {
603 Some(workspace.clone())
604 } else {
605 None
606 };
607
608 let (workspace_task, modal_workspace) =
609 window_handle.update(cx, |multi_workspace, window, cx| {
610 let path_list = util::path_list::PathList::new(&all_paths);
611 let active_workspace = multi_workspace.workspace().clone();
612 let modal_workspace = active_workspace.clone();
613
614 let init: Option<
615 Box<
616 dyn FnOnce(&mut Workspace, &mut gpui::Window, &mut gpui::Context<Workspace>)
617 + Send,
618 >,
619 > = if is_creating_new_worktree {
620 let dock_structure = previous_state.dock_structure;
621 Some(Box::new(
622 move |workspace: &mut Workspace,
623 window: &mut gpui::Window,
624 cx: &mut gpui::Context<Workspace>| {
625 workspace.set_dock_structure(dock_structure, window, cx);
626 },
627 ))
628 } else {
629 None
630 };
631
632 let task = multi_workspace.find_or_create_workspace_with_source_workspace(
633 path_list,
634 remote_connection_options,
635 None,
636 move |connection_options, window, cx| {
637 remote_connection::connect_with_modal(
638 &active_workspace,
639 connection_options,
640 window,
641 cx,
642 )
643 },
644 &[],
645 init,
646 OpenMode::Add,
647 source_for_transfer.clone(),
648 window,
649 cx,
650 );
651 (task, modal_workspace)
652 })?;
653
654 let result = workspace_task.await;
655 remote_connection::dismiss_connection_modal(&modal_workspace, cx);
656 let new_workspace = result?;
657
658 let panels_task = new_workspace.update(cx, |workspace, _cx| workspace.take_panels_task());
659
660 if let Some(task) = panels_task {
661 task.await.log_err();
662 }
663
664 new_workspace
665 .update(cx, |workspace, cx| {
666 workspace.project().read(cx).wait_for_initial_scan(cx)
667 })
668 .await;
669
670 new_workspace
671 .update(cx, |workspace, cx| {
672 let repos = workspace
673 .project()
674 .read(cx)
675 .repositories(cx)
676 .values()
677 .cloned()
678 .collect::<Vec<_>>();
679
680 let tasks = repos
681 .into_iter()
682 .map(|repo| repo.update(cx, |repo, _| repo.barrier()));
683 futures::future::join_all(tasks)
684 })
685 .await;
686
687 maybe_propagate_worktree_trust(&workspace, &new_workspace, &all_paths, cx);
688
689 if is_creating_new_worktree {
690 window_handle.update(cx, |_multi_workspace, window, cx| {
691 new_workspace.update(cx, |workspace, cx| {
692 if has_non_git {
693 struct WorktreeCreationToast;
694 let toast_id =
695 workspace::notifications::NotificationId::unique::<WorktreeCreationToast>();
696 workspace.show_toast(
697 workspace::Toast::new(
698 toast_id,
699 "Some project folders are not git repositories. \
700 They were included as-is without creating a worktree.",
701 ),
702 cx,
703 );
704 }
705
706 // Remap every previously-open file path into the new worktree.
707 let remap_path = |original_path: PathBuf| -> Option<PathBuf> {
708 let best_match = path_remapping
709 .iter()
710 .filter_map(|(old_root, new_root)| {
711 original_path.strip_prefix(old_root).ok().map(|relative| {
712 (old_root.components().count(), new_root.join(relative))
713 })
714 })
715 .max_by_key(|(depth, _)| *depth);
716
717 if let Some((_, remapped_path)) = best_match {
718 return Some(remapped_path);
719 }
720
721 for non_git in &non_git_paths {
722 if original_path.starts_with(non_git) {
723 return Some(original_path);
724 }
725 }
726 None
727 };
728
729 let remapped_active_path =
730 previous_state.active_file_path.and_then(|p| remap_path(p));
731
732 let mut paths_to_open: Vec<PathBuf> = Vec::new();
733 let mut seen = HashSet::default();
734 for path in previous_state.open_file_paths {
735 if let Some(remapped) = remap_path(path) {
736 if remapped_active_path.as_ref() != Some(&remapped)
737 && seen.insert(remapped.clone())
738 {
739 paths_to_open.push(remapped);
740 }
741 }
742 }
743
744 if let Some(active) = &remapped_active_path {
745 if seen.insert(active.clone()) {
746 paths_to_open.push(active.clone());
747 }
748 }
749
750 if !paths_to_open.is_empty() {
751 let should_focus_center = focused_dock.is_none();
752 let open_task = workspace.open_paths(
753 paths_to_open,
754 workspace::OpenOptions {
755 focus: Some(false),
756 ..Default::default()
757 },
758 None,
759 window,
760 cx,
761 );
762 cx.spawn_in(window, async move |workspace, cx| {
763 for item in open_task.await.into_iter().flatten() {
764 item.log_err();
765 }
766 if should_focus_center {
767 workspace.update_in(cx, |workspace, window, cx| {
768 workspace.focus_center_pane(window, cx);
769 })?;
770 }
771 anyhow::Ok(())
772 })
773 .detach_and_log_err(cx);
774 }
775 });
776 })?;
777 }
778
779 // Clear the creation status on the SOURCE workspace so its title bar
780 // stops showing the loading indicator immediately.
781 workspace
782 .update(cx, |ws, cx| {
783 ws.set_active_worktree_creation(None, false, cx);
784 })
785 .ok();
786
787 window_handle.update(cx, |multi_workspace, window, cx| {
788 multi_workspace.activate(new_workspace.clone(), source_for_transfer, window, cx);
789
790 new_workspace.update(cx, |workspace, cx| {
791 workspace.run_create_worktree_tasks(window, cx);
792 });
793 })?;
794
795 if is_creating_new_worktree {
796 if let Some(dock_position) = focused_dock {
797 window_handle.update(cx, |_multi_workspace, window, cx| {
798 new_workspace.update(cx, |workspace, cx| {
799 let dock = workspace.dock_at_position(dock_position);
800 if let Some(panel) = dock.read(cx).active_panel() {
801 panel.panel_focus_handle(cx).focus(window, cx);
802 }
803 });
804 })?;
805 }
806 }
807
808 anyhow::Ok(())
809}