@@ -1399,7 +1399,44 @@ impl GitStore {
diffs.remove(buffer_id);
}
}
+ BufferStoreEvent::BufferChangedFilePath { buffer, .. } => {
+ // Whenever a buffer's file path changes, it's possible that the
+ // new path is actually a path that is being tracked by a git
+ // repository. In that case, we'll want to update the buffer's
+ // `BufferDiffState`, in case it already has one.
+ let buffer_id = buffer.read(cx).remote_id();
+ let diff_state = self.diffs.get(&buffer_id);
+ let repo = self.repository_and_path_for_buffer_id(buffer_id, cx);
+
+ if let Some(diff_state) = diff_state
+ && let Some((repo, repo_path)) = repo
+ {
+ let buffer = buffer.clone();
+ let diff_state = diff_state.clone();
+
+ cx.spawn(async move |_git_store, cx| {
+ async {
+ let diff_bases_change = repo
+ .update(cx, |repo, cx| {
+ repo.load_committed_text(buffer_id, repo_path, cx)
+ })?
+ .await?;
+ diff_state.update(cx, |diff_state, cx| {
+ let buffer_snapshot = buffer.read(cx).text_snapshot();
+ diff_state.diff_bases_changed(
+ buffer_snapshot,
+ Some(diff_bases_change),
+ cx,
+ );
+ })
+ }
+ .await
+ .log_err();
+ })
+ .detach();
+ }
+ }
_ => {}
}
}
@@ -10045,6 +10045,120 @@ async fn test_repository_deduplication(cx: &mut gpui::TestAppContext) {
pretty_assertions::assert_eq!(repos, [Path::new(path!("/root/project")).into()]);
}
+#[gpui::test]
+async fn test_buffer_changed_file_path_updates_git_diff(cx: &mut gpui::TestAppContext) {
+ init_test(cx);
+
+ let file_1_committed = String::from(r#"file_1_committed"#);
+ let file_1_staged = String::from(r#"file_1_staged"#);
+ let file_2_committed = String::from(r#"file_2_committed"#);
+ let file_2_staged = String::from(r#"file_2_staged"#);
+ let buffer_contents = String::from(r#"buffer"#);
+
+ let fs = FakeFs::new(cx.background_executor.clone());
+ fs.insert_tree(
+ path!("/dir"),
+ json!({
+ ".git": {},
+ "src": {
+ "file_1.rs": file_1_committed.clone(),
+ "file_2.rs": file_2_committed.clone(),
+ }
+ }),
+ )
+ .await;
+
+ fs.set_head_for_repo(
+ path!("/dir/.git").as_ref(),
+ &[
+ ("src/file_1.rs", file_1_committed.clone()),
+ ("src/file_2.rs", file_2_committed.clone()),
+ ],
+ "deadbeef",
+ );
+ fs.set_index_for_repo(
+ path!("/dir/.git").as_ref(),
+ &[
+ ("src/file_1.rs", file_1_staged.clone()),
+ ("src/file_2.rs", file_2_staged.clone()),
+ ],
+ );
+
+ let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
+
+ let buffer = project
+ .update(cx, |project, cx| {
+ project.open_local_buffer(path!("/dir/src/file_1.rs"), cx)
+ })
+ .await
+ .unwrap();
+
+ buffer.update(cx, |buffer, cx| {
+ buffer.edit([(0..buffer.len(), buffer_contents.as_str())], None, cx);
+ });
+
+ let unstaged_diff = project
+ .update(cx, |project, cx| {
+ project.open_unstaged_diff(buffer.clone(), cx)
+ })
+ .await
+ .unwrap();
+
+ cx.run_until_parked();
+
+ unstaged_diff.update(cx, |unstaged_diff, _cx| {
+ let base_text = unstaged_diff.base_text_string().unwrap();
+ assert_eq!(base_text, file_1_staged, "Should start with file_1 staged");
+ });
+
+ // Save the buffer as `file_2.rs`, which should trigger the
+ // `BufferChangedFilePath` event.
+ project
+ .update(cx, |project, cx| {
+ let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
+ let path = ProjectPath {
+ worktree_id,
+ path: rel_path("src/file_2.rs").into(),
+ };
+ project.save_buffer_as(buffer.clone(), path, cx)
+ })
+ .await
+ .unwrap();
+
+ cx.run_until_parked();
+
+ // Verify that the diff bases have been updated to file_2's contents due to
+ // the `BufferChangedFilePath` event being handled.
+ unstaged_diff.update(cx, |unstaged_diff, cx| {
+ let snapshot = buffer.read(cx).snapshot();
+ let base_text = unstaged_diff.base_text_string().unwrap();
+ assert_eq!(
+ base_text, file_2_staged,
+ "Diff bases should be automatically updated to file_2 staged content"
+ );
+
+ let hunks: Vec<_> = unstaged_diff.hunks(&snapshot, cx).collect();
+ assert!(!hunks.is_empty(), "Should have diff hunks for file_2");
+ });
+
+ let uncommitted_diff = project
+ .update(cx, |project, cx| {
+ project.open_uncommitted_diff(buffer.clone(), cx)
+ })
+ .await
+ .unwrap();
+
+ cx.run_until_parked();
+
+ uncommitted_diff.update(cx, |uncommitted_diff, _cx| {
+ let base_text = uncommitted_diff.base_text_string().unwrap();
+ assert_eq!(
+ base_text, file_2_committed,
+ "Uncommitted diff should compare against file_2 committed content"
+ );
+ });
+}
+
async fn search(
project: &Entity<Project>,
query: SearchQuery,