Add support for git branches on remote projects (#19755)

Release Notes:

- Fixed a bug where the branch switcher could not be used remotely.
This commit is contained in:
Mikayla Maki 2024-10-27 15:50:54 -07:00 committed by GitHub
parent 5506669b06
commit c69da2df70
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 993 additions and 127 deletions

View file

@ -308,6 +308,8 @@ impl Server {
.add_request_handler(forward_read_only_project_request::<proto::InlayHints>)
.add_request_handler(forward_read_only_project_request::<proto::ResolveInlayHint>)
.add_request_handler(forward_read_only_project_request::<proto::OpenBufferByPath>)
.add_request_handler(forward_read_only_project_request::<proto::GitBranches>)
.add_request_handler(forward_mutating_project_request::<proto::UpdateGitBranch>)
.add_request_handler(forward_mutating_project_request::<proto::GetCompletions>)
.add_request_handler(
forward_mutating_project_request::<proto::ApplyCompletionAdditionalEdits>,

View file

@ -6575,3 +6575,95 @@ async fn test_context_collaboration_with_reconnect(
assert!(context.buffer().read(cx).read_only());
});
}
#[gpui::test]
async fn test_remote_git_branches(
executor: BackgroundExecutor,
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
) {
let mut server = TestServer::start(executor.clone()).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
server
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
.await;
let active_call_a = cx_a.read(ActiveCall::global);
client_a
.fs()
.insert_tree("/project", serde_json::json!({ ".git":{} }))
.await;
let branches = ["main", "dev", "feature-1"];
client_a
.fs()
.insert_branches(Path::new("/project/.git"), &branches);
let (project_a, worktree_id) = client_a.build_local_project("/project", cx_a).await;
let project_id = active_call_a
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let root_path = ProjectPath::root_path(worktree_id);
// Client A sees that a guest has joined.
executor.run_until_parked();
let branches_b = cx_b
.update(|cx| project_b.update(cx, |project, cx| project.branches(root_path.clone(), cx)))
.await
.unwrap();
let new_branch = branches[2];
let branches_b = branches_b
.into_iter()
.map(|branch| branch.name)
.collect::<Vec<_>>();
assert_eq!(&branches_b, &branches);
cx_b.update(|cx| {
project_b.update(cx, |project, cx| {
project.update_or_create_branch(root_path.clone(), new_branch.to_string(), cx)
})
})
.await
.unwrap();
executor.run_until_parked();
let host_branch = cx_a.update(|cx| {
project_a.update(cx, |project, cx| {
project.worktree_store().update(cx, |worktree_store, cx| {
worktree_store
.current_branch(root_path.clone(), cx)
.unwrap()
})
})
});
assert_eq!(host_branch.as_ref(), branches[2]);
// Also try creating a new branch
cx_b.update(|cx| {
project_b.update(cx, |project, cx| {
project.update_or_create_branch(root_path.clone(), "totally-new-branch".to_string(), cx)
})
})
.await
.unwrap();
executor.run_until_parked();
let host_branch = cx_a.update(|cx| {
project_a.update(cx, |project, cx| {
project.worktree_store().update(cx, |worktree_store, cx| {
worktree_store.current_branch(root_path, cx).unwrap()
})
})
});
assert_eq!(host_branch.as_ref(), "totally-new-branch");
}

View file

@ -1,7 +1,7 @@
use crate::tests::TestServer;
use call::ActiveCall;
use fs::{FakeFs, Fs as _};
use gpui::{Context as _, TestAppContext};
use gpui::{BackgroundExecutor, Context as _, TestAppContext};
use http_client::BlockedHttpClient;
use language::{language_settings::language_settings, LanguageRegistry};
use node_runtime::NodeRuntime;
@ -174,3 +174,133 @@ async fn test_sharing_an_ssh_remote_project(
);
});
}
#[gpui::test]
async fn test_ssh_collaboration_git_branches(
executor: BackgroundExecutor,
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
server_cx: &mut TestAppContext,
) {
cx_a.set_name("a");
cx_b.set_name("b");
server_cx.set_name("server");
let mut server = TestServer::start(executor.clone()).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
server
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
.await;
// Set up project on remote FS
let (opts, server_ssh) = SshRemoteClient::fake_server(cx_a, server_cx);
let remote_fs = FakeFs::new(server_cx.executor());
remote_fs
.insert_tree("/project", serde_json::json!({ ".git":{} }))
.await;
let branches = ["main", "dev", "feature-1"];
remote_fs.insert_branches(Path::new("/project/.git"), &branches);
// User A connects to the remote project via SSH.
server_cx.update(HeadlessProject::init);
let remote_http_client = Arc::new(BlockedHttpClient);
let node = NodeRuntime::unavailable();
let languages = Arc::new(LanguageRegistry::new(server_cx.executor()));
let headless_project = server_cx.new_model(|cx| {
client::init_settings(cx);
HeadlessProject::new(
HeadlessAppState {
session: server_ssh,
fs: remote_fs.clone(),
http_client: remote_http_client,
node_runtime: node,
languages,
},
cx,
)
});
let client_ssh = SshRemoteClient::fake_client(opts, cx_a).await;
let (project_a, worktree_id) = client_a
.build_ssh_project("/project", client_ssh, cx_a)
.await;
// While the SSH worktree is being scanned, user A shares the remote project.
let active_call_a = cx_a.read(ActiveCall::global);
let project_id = active_call_a
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
// User B joins the project.
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Give client A sometime to see that B has joined, and that the headless server
// has some git repositories
executor.run_until_parked();
let root_path = ProjectPath::root_path(worktree_id);
let branches_b = cx_b
.update(|cx| project_b.update(cx, |project, cx| project.branches(root_path.clone(), cx)))
.await
.unwrap();
let new_branch = branches[2];
let branches_b = branches_b
.into_iter()
.map(|branch| branch.name)
.collect::<Vec<_>>();
assert_eq!(&branches_b, &branches);
cx_b.update(|cx| {
project_b.update(cx, |project, cx| {
project.update_or_create_branch(root_path.clone(), new_branch.to_string(), cx)
})
})
.await
.unwrap();
executor.run_until_parked();
let server_branch = server_cx.update(|cx| {
headless_project.update(cx, |headless_project, cx| {
headless_project
.worktree_store
.update(cx, |worktree_store, cx| {
worktree_store
.current_branch(root_path.clone(), cx)
.unwrap()
})
})
});
assert_eq!(server_branch.as_ref(), branches[2]);
// Also try creating a new branch
cx_b.update(|cx| {
project_b.update(cx, |project, cx| {
project.update_or_create_branch(root_path.clone(), "totally-new-branch".to_string(), cx)
})
})
.await
.unwrap();
executor.run_until_parked();
let server_branch = server_cx.update(|cx| {
headless_project.update(cx, |headless_project, cx| {
headless_project
.worktree_store
.update(cx, |worktree_store, cx| {
worktree_store.current_branch(root_path, cx).unwrap()
})
})
});
assert_eq!(server_branch.as_ref(), "totally-new-branch");
}