diff --git a/crates/agent_ui/src/thread_worktree_archive.rs b/crates/agent_ui/src/thread_worktree_archive.rs index 1d392a86352b53..997e8d5d0ab67f 100644 --- a/crates/agent_ui/src/thread_worktree_archive.rs +++ b/crates/agent_ui/src/thread_worktree_archive.rs @@ -692,7 +692,7 @@ pub async fn restore_worktree_via_git( if let Some(branch_name) = &row.branch_name { // Attempt to check out the branch the worktree was previously on. let checkout_result = wt_repo - .update(cx, |repo, _cx| repo.change_branch(branch_name.clone())) + .update(cx, |repo, cx| repo.change_branch(branch_name.clone(), cx)) .await; match checkout_result.map_err(|e| anyhow!("{e}")).flatten() { diff --git a/crates/collab/tests/integration/git_tests.rs b/crates/collab/tests/integration/git_tests.rs index 1397bd271b1371..47a328e9729e88 100644 --- a/crates/collab/tests/integration/git_tests.rs +++ b/crates/collab/tests/integration/git_tests.rs @@ -907,8 +907,8 @@ async fn test_branch_list_sync( .unwrap(); cx_b.update(|cx| { - repo_b.update(cx, |repository, _cx| { - repository.change_branch("totally-new-branch".to_string()) + repo_b.update(cx, |repository, cx| { + repository.change_branch("totally-new-branch".to_string(), cx) }) }) .await diff --git a/crates/collab/tests/integration/integration_tests.rs b/crates/collab/tests/integration/integration_tests.rs index e6c377e3d451e6..6cb4977935d231 100644 --- a/crates/collab/tests/integration/integration_tests.rs +++ b/crates/collab/tests/integration/integration_tests.rs @@ -7353,8 +7353,8 @@ async fn test_remote_git_branches( assert_eq!(branches_b, branches_set); cx_b.update(|cx| { - repo_b.update(cx, |repository, _cx| { - repository.change_branch(new_branch.to_string()) + repo_b.update(cx, |repository, cx| { + repository.change_branch(new_branch.to_string(), cx) }) }) .await @@ -7391,8 +7391,8 @@ async fn test_remote_git_branches( .unwrap(); cx_b.update(|cx| { - repo_b.update(cx, |repository, _cx| { - repository.change_branch("totally-new-branch".to_string()) + repo_b.update(cx, |repository, cx| { + repository.change_branch("totally-new-branch".to_string(), cx) }) }) .await diff --git a/crates/collab/tests/integration/remote_editing_collaboration_tests.rs b/crates/collab/tests/integration/remote_editing_collaboration_tests.rs index d039bce3b39fdd..77c74921076459 100644 --- a/crates/collab/tests/integration/remote_editing_collaboration_tests.rs +++ b/crates/collab/tests/integration/remote_editing_collaboration_tests.rs @@ -311,8 +311,8 @@ async fn test_ssh_collaboration_git_branches( assert_eq!(&branches_b, &branches_set); cx_b.update(|cx| { - repo_b.update(cx, |repo_b, _cx| { - repo_b.change_branch(new_branch.to_string()) + repo_b.update(cx, |repo_b, cx| { + repo_b.change_branch(new_branch.to_string(), cx) }) }) .await @@ -351,8 +351,8 @@ async fn test_ssh_collaboration_git_branches( .unwrap(); cx_b.update(|cx| { - repo_b.update(cx, |repo_b, _cx| { - repo_b.change_branch("totally-new-branch".to_string()) + repo_b.update(cx, |repo_b, cx| { + repo_b.change_branch("totally-new-branch".to_string(), cx) }) }) .await diff --git a/crates/git_ui/src/branch_picker.rs b/crates/git_ui/src/branch_picker.rs index 37323558717914..103137e3440f47 100644 --- a/crates/git_ui/src/branch_picker.rs +++ b/crates/git_ui/src/branch_picker.rs @@ -1566,8 +1566,10 @@ impl PickerDelegate for BranchListDelegate { let branch = branch.clone(); cx.spawn(async move |_, cx| { - repo.update(cx, |repo, _| repo.change_branch(branch.name().to_string())) - .await??; + repo.update(cx, |repo, cx| { + repo.change_branch(branch.name().to_string(), cx) + }) + .await??; anyhow::Ok(()) }) diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index eb998cb7d9620a..e42a64b21f7844 100644 --- a/crates/project/src/git_store.rs +++ b/crates/project/src/git_store.rs @@ -3607,8 +3607,8 @@ impl GitStore { let branch_name = envelope.payload.branch_name; repository_handle - .update(&mut cx, |repository_handle, _| { - repository_handle.change_branch(branch_name) + .update(&mut cx, |repository_handle, cx| { + repository_handle.change_branch(branch_name, cx) }) .await??; @@ -8673,9 +8673,13 @@ impl Repository { ) } - pub fn change_branch(&mut self, branch_name: String) -> oneshot::Receiver> { + pub fn change_branch( + &mut self, + branch_name: String, + cx: &mut Context, + ) -> oneshot::Receiver> { let id = self.id; - self.send_job( + let receiver = self.send_job( "change_branch", Some(format!("git switch {branch_name}").into()), move |repo, _cx| async move { @@ -8696,7 +8700,29 @@ impl Repository { } } }, - ) + ); + + // The worktree file watcher does not reliably observe the `.git/HEAD` + // change produced by `git switch` (notably over SSH, where the switch + // runs on the remote and the downstream `UpdateRepository` never gets + // sent). Refresh explicitly so the new branch propagates to the UI / + // downstream clients. Mirrors `reset`. `schedule_scan` is keyed by + // `ReloadGitState`, so any watcher-triggered scan is deduped. + let scan_updates_tx = + self.git_store() + .and_then(|git_store| match &git_store.read(cx).state { + GitStoreState::Local { downstream, .. } => Some( + downstream + .as_ref() + .map(|downstream| downstream.updates_tx.clone()), + ), + _ => None, + }); + if let Some(updates_tx) = scan_updates_tx { + self.schedule_scan(updates_tx, cx); + } + + receiver } pub fn delete_branch( diff --git a/crates/remote_server/src/remote_editing_tests.rs b/crates/remote_server/src/remote_editing_tests.rs index 271ce2ad27e3d3..20212b570930cd 100644 --- a/crates/remote_server/src/remote_editing_tests.rs +++ b/crates/remote_server/src/remote_editing_tests.rs @@ -3320,8 +3320,8 @@ async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestA assert_eq!(&remote_branches, &branches_set); cx.update(|cx| { - repository.update(cx, |repository, _cx| { - repository.change_branch(new_branch.to_string()) + repository.update(cx, |repository, cx| { + repository.change_branch(new_branch.to_string(), cx) }) }) .await @@ -3349,6 +3349,20 @@ async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestA assert_eq!(server_branch.name(), branches[2]); + // The downstream (client) repository must observe the new branch too — + // its snapshot is updated via the `UpdateRepository` the server sends + // after the scan triggered by `change_branch`. + let client_branch = cx.update(|cx| { + repository + .read(cx) + .branch + .as_ref() + .unwrap() + .name() + .to_string() + }); + assert_eq!(client_branch, branches[2]); + // Also try creating a new branch cx.update(|cx| { repository.update(cx, |repo, _cx| { @@ -3360,8 +3374,8 @@ async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestA .unwrap(); cx.update(|cx| { - repository.update(cx, |repo, _cx| { - repo.change_branch("totally-new-branch".to_string()) + repository.update(cx, |repo, cx| { + repo.change_branch("totally-new-branch".to_string(), cx) }) }) .await @@ -3389,6 +3403,17 @@ async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestA assert_eq!(server_branch.name(), "totally-new-branch"); + let client_branch = cx.update(|cx| { + repository + .read(cx) + .branch + .as_ref() + .unwrap() + .name() + .to_string() + }); + assert_eq!(client_branch, "totally-new-branch"); + let default_branch = cx .update(|cx| repository.update(cx, |repository, _cx| repository.default_branch(false))) .await