Fix replication of head text when head matches index (#24306)

Max Brunsfeld and cole-miller created

Release Notes:

- N/A

---------

Co-authored-by: cole-miller <m@cole-miller.net>

Change summary

Cargo.lock                                       |   1 
crates/project/src/buffer_store.rs               |   8 
crates/remote_server/Cargo.toml                  |   2 
crates/remote_server/src/remote_editing_tests.rs | 120 ++++++++++++++++++
4 files changed, 126 insertions(+), 5 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -10700,6 +10700,7 @@ dependencies = [
  "sysinfo",
  "telemetry_events",
  "toml 0.8.19",
+ "unindent",
  "util",
  "worktree",
 ]

crates/project/src/buffer_store.rs 🔗

@@ -131,7 +131,7 @@ impl BufferChangeSetState {
         let diff_bases_change = match mode {
             Mode::HeadOnly => DiffBasesChange::SetHead(message.committed_text),
             Mode::IndexOnly => DiffBasesChange::SetIndex(message.staged_text),
-            Mode::IndexMatchesHead => DiffBasesChange::SetBoth(message.staged_text),
+            Mode::IndexMatchesHead => DiffBasesChange::SetBoth(message.committed_text),
             Mode::IndexAndHead => DiffBasesChange::SetEach {
                 index: message.staged_text,
                 head: message.committed_text,
@@ -402,7 +402,7 @@ impl RemoteBufferStore {
                 .await?;
             let mode = Mode::from_i32(response.mode).ok_or_else(|| anyhow!("Invalid mode"))?;
             let bases = match mode {
-                Mode::IndexMatchesHead => DiffBasesChange::SetBoth(response.staged_text),
+                Mode::IndexMatchesHead => DiffBasesChange::SetBoth(response.committed_text),
                 Mode::IndexAndHead => DiffBasesChange::SetEach {
                     head: response.committed_text,
                     index: response.staged_text,
@@ -896,7 +896,7 @@ impl LocalBufferStore {
                                 let diff_bases_change =
                                     match (needs_staged_text, needs_committed_text) {
                                         (true, true) => Some(if staged_text == committed_text {
-                                            DiffBasesChange::SetBoth(staged_text)
+                                            DiffBasesChange::SetBoth(committed_text)
                                         } else {
                                             DiffBasesChange::SetEach {
                                                 index: staged_text,
@@ -944,7 +944,7 @@ impl LocalBufferStore {
                                     (index, head, Mode::IndexAndHead)
                                 }
                                 DiffBasesChange::SetBoth(text) => {
-                                    (text, None, Mode::IndexMatchesHead)
+                                    (None, text, Mode::IndexMatchesHead)
                                 }
                             };
                             let message = proto::UpdateDiffBases {

crates/remote_server/Cargo.toml 🔗

@@ -77,7 +77,7 @@ node_runtime = { workspace = true, features = ["test-support"] }
 project = { workspace = true, features = ["test-support"] }
 remote = { workspace = true, features = ["test-support"] }
 lsp = { workspace = true, features=["test-support"] }
-
+unindent.workspace = true
 serde_json.workspace = true
 
 [build-dependencies]

crates/remote_server/src/remote_editing_tests.rs 🔗

@@ -27,6 +27,7 @@ use std::{
     path::{Path, PathBuf},
     sync::Arc,
 };
+use unindent::Unindent as _;
 use util::{path, separator};
 
 #[gpui::test]
@@ -1183,6 +1184,125 @@ async fn test_remote_rename_entry(cx: &mut TestAppContext, server_cx: &mut TestA
         assert_eq!(worktree.entry_for_path("README.rst").unwrap().id, entry.id)
     });
 }
+
+#[gpui::test]
+async fn test_remote_git_diffs(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
+    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": {
+                ".git": {},
+                "src": {
+                    "lib.rs": text_2
+                },
+                "README.md": "# project 1",
+            },
+        }),
+    )
+    .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())],
+    );
+
+    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());
+    cx.executor().run_until_parked();
+
+    let buffer = project
+        .update(cx, |project, cx| {
+            project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
+        })
+        .await
+        .unwrap();
+    let change_set = project
+        .update(cx, |project, cx| {
+            project.open_uncommitted_changes(buffer.clone(), cx)
+        })
+        .await
+        .unwrap();
+
+    change_set.read_with(cx, |change_set, cx| {
+        assert_eq!(change_set.base_text_string().unwrap(), text_1);
+        assert_eq!(
+            change_set
+                .unstaged_change_set
+                .as_ref()
+                .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();
+    change_set.read_with(cx, |change_set, cx| {
+        assert_eq!(change_set.base_text_string().unwrap(), text_1);
+        assert_eq!(
+            change_set
+                .unstaged_change_set
+                .as_ref()
+                .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())],
+    );
+
+    cx.executor().run_until_parked();
+    change_set.read_with(cx, |change_set, cx| {
+        assert_eq!(change_set.base_text_string().unwrap(), text_2);
+        assert_eq!(
+            change_set
+                .unstaged_change_set
+                .as_ref()
+                .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());