worktree_service.rs

  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}