diff --git a/Cargo.lock b/Cargo.lock index be37db943b..75cb0930b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13222,6 +13222,7 @@ dependencies = [ "dap", "dap_adapters", "debug_adapter_extension", + "editor", "env_logger 0.11.8", "extension", "extension_host", @@ -13260,6 +13261,7 @@ dependencies = [ "unindent", "util", "watch", + "workspace", "worktree", "zlog", ] diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2c1d9ab44e..e99fe7fc98 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -134,6 +134,7 @@ use project::{ }, session::{Session, SessionEvent}, }, + git_store::{GitStoreEvent, RepositoryEvent}, project_settings::DiagnosticSeverity, }; @@ -1866,6 +1867,31 @@ impl Editor { _ => {} }, )); + let git_store = project.read(cx).git_store().clone(); + let project = project.clone(); + project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| { + match event { + GitStoreEvent::RepositoryUpdated( + _, + RepositoryEvent::Updated { + new_instance: true, .. + }, + _, + ) => { + this.load_diff_task = Some( + update_uncommitted_diff_for_buffer( + cx.entity(), + &project, + this.buffer.read(cx).all_buffers(), + this.buffer.clone(), + cx, + ) + .shared(), + ); + } + _ => {} + } + })); } } diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 2a3674d775..e79997a063 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -475,7 +475,7 @@ impl GitPanel { } GitStoreEvent::RepositoryUpdated( _, - RepositoryEvent::Updated { full_scan }, + RepositoryEvent::Updated { full_scan, .. }, true, ) => { this.schedule_update(*full_scan, window, cx); diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index 852d809f27..f7d0de48e2 100644 --- a/crates/project/src/git_store.rs +++ b/crates/project/src/git_store.rs @@ -292,7 +292,7 @@ pub enum RepositoryState { #[derive(Clone, Debug)] pub enum RepositoryEvent { - Updated { full_scan: bool }, + Updated { full_scan: bool, new_instance: bool }, MergeHeadsChanged, } @@ -1496,7 +1496,7 @@ impl GitStore { repo.update(cx, { let update = update.clone(); - |repo, cx| repo.apply_remote_update(update, cx) + |repo, cx| repo.apply_remote_update(update, is_new, cx) })?; this.active_repo_id.get_or_insert_with(|| { @@ -3597,7 +3597,10 @@ impl Repository { let snapshot = this.update(&mut cx, |this, cx| { this.snapshot.branch = branch; let snapshot = this.snapshot.clone(); - cx.emit(RepositoryEvent::Updated { full_scan: false }); + cx.emit(RepositoryEvent::Updated { + full_scan: false, + new_instance: false, + }); snapshot })?; if let Some(updates_tx) = updates_tx { @@ -3942,6 +3945,7 @@ impl Repository { pub(crate) fn apply_remote_update( &mut self, update: proto::UpdateRepository, + is_new: bool, cx: &mut Context, ) -> Result<()> { let conflicted_paths = TreeSet::from_ordered_entries( @@ -3975,7 +3979,10 @@ impl Repository { if update.is_last_update { self.snapshot.scan_id = update.scan_id; } - cx.emit(RepositoryEvent::Updated { full_scan: true }); + cx.emit(RepositoryEvent::Updated { + full_scan: true, + new_instance: is_new, + }); Ok(()) } @@ -4305,7 +4312,10 @@ impl Repository { .ok(); } } - cx.emit(RepositoryEvent::Updated { full_scan: false }); + cx.emit(RepositoryEvent::Updated { + full_scan: false, + new_instance: false, + }); }) }, ); @@ -4565,7 +4575,10 @@ async fn compute_snapshot( || branch != prev_snapshot.branch || statuses_by_path != prev_snapshot.statuses_by_path { - events.push(RepositoryEvent::Updated { full_scan: true }); + events.push(RepositoryEvent::Updated { + full_scan: true, + new_instance: false, + }); } // Cache merge conflict paths so they don't change from staging/unstaging, diff --git a/crates/remote_server/Cargo.toml b/crates/remote_server/Cargo.toml index 2dbe51b605..b780f57ab4 100644 --- a/crates/remote_server/Cargo.toml +++ b/crates/remote_server/Cargo.toml @@ -75,6 +75,8 @@ assistant_tools.workspace = true client = { workspace = true, features = ["test-support"] } clock = { workspace = true, features = ["test-support"] } dap = { workspace = true, features = ["test-support"] } +editor = { workspace = true, features = ["test-support"] } +workspace = { workspace = true, features = ["test-support"] } fs = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] } http_client = { workspace = true, features = ["test-support"] } diff --git a/crates/remote_server/src/remote_editing_tests.rs b/crates/remote_server/src/remote_editing_tests.rs index 81b98d953f..e9805b12a2 100644 --- a/crates/remote_server/src/remote_editing_tests.rs +++ b/crates/remote_server/src/remote_editing_tests.rs @@ -1434,6 +1434,148 @@ async fn test_remote_git_diffs(cx: &mut TestAppContext, server_cx: &mut TestAppC }); } +// TODO: this test fails on Windows. +#[cfg(not(windows))] +#[gpui::test] +async fn test_remote_git_diffs_when_recv_update_repository_delay( + cx: &mut TestAppContext, + server_cx: &mut TestAppContext, +) { + use editor::Editor; + use gpui::VisualContext; + let text_2 = " + fn one() -> usize { + 1 + } + " + .unindent(); + let text_1 = " + fn one() -> usize { + 0 + } + " + .unindent(); + + let fs = FakeFs::new(server_cx.executor()); + fs.insert_tree( + "/code", + json!({ + "project1": { + "src": { + "lib.rs": text_2 + }, + "README.md": "# project 1", + }, + }), + ) + .await; + + let (project, _headless) = init_test(&fs, cx, server_cx).await; + let (worktree, _) = project + .update(cx, |project, cx| { + project.find_or_create_worktree("/code/project1", true, cx) + }) + .await + .unwrap(); + let worktree_id = cx.update(|cx| worktree.read(cx).id()); + let buffer = project + .update(cx, |project, cx| { + project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx) + }) + .await + .unwrap(); + let buffer_id = cx.update(|cx| buffer.read(cx).remote_id()); + cx.update(|cx| { + workspace::init_settings(cx); + editor::init_settings(cx); + }); + let cx = cx.add_empty_window(); + let editor = cx.new_window_entity(|window, cx| { + Editor::for_buffer(buffer, Some(project.clone()), window, cx) + }); + + // Remote server will send proto::UpdateRepository after the instance of Editor create. + fs.insert_tree( + "/code", + json!({ + "project1": { + ".git": {}, + }, + }), + ) + .await; + + fs.set_index_for_repo( + Path::new("/code/project1/.git"), + &[("src/lib.rs".into(), text_1.clone())], + ); + fs.set_head_for_repo( + Path::new("/code/project1/.git"), + &[("src/lib.rs".into(), text_1.clone())], + "sha", + ); + + cx.executor().run_until_parked(); + let diff = editor + .read_with(cx, |editor, cx| { + editor + .buffer() + .read_with(cx, |buffer, _| buffer.diff_for(buffer_id)) + }) + .unwrap(); + + diff.read_with(cx, |diff, cx| { + assert_eq!(diff.base_text_string().unwrap(), text_1); + assert_eq!( + diff.secondary_diff() + .unwrap() + .read(cx) + .base_text_string() + .unwrap(), + text_1 + ); + }); + + // stage the current buffer's contents + fs.set_index_for_repo( + Path::new("/code/project1/.git"), + &[("src/lib.rs".into(), text_2.clone())], + ); + + cx.executor().run_until_parked(); + diff.read_with(cx, |diff, cx| { + assert_eq!(diff.base_text_string().unwrap(), text_1); + assert_eq!( + diff.secondary_diff() + .unwrap() + .read(cx) + .base_text_string() + .unwrap(), + text_2 + ); + }); + + // commit the current buffer's contents + fs.set_head_for_repo( + Path::new("/code/project1/.git"), + &[("src/lib.rs".into(), text_2.clone())], + "sha", + ); + + cx.executor().run_until_parked(); + diff.read_with(cx, |diff, cx| { + assert_eq!(diff.base_text_string().unwrap(), text_2); + assert_eq!( + diff.secondary_diff() + .unwrap() + .read(cx) + .base_text_string() + .unwrap(), + text_2 + ); + }); +} + #[gpui::test] async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestAppContext) { let fs = FakeFs::new(server_cx.executor());