Skip to content

Commit d896af2

Browse files
authored
git: Handle buffer file path changes (#41944)
Update `GitStore.on_buffer_store_event` so that, when a `BufferStoreEvent::BufferChangedFilePath` event is received, we check if there's any diff state for the buffer and, if so, update it according to the new file path, in case the file exists in the repository. Closes #40499 Release Notes: - Fixed issue with git diff tracking when updating a buffer's file from an untracked to a tracked file
1 parent c748b17 commit d896af2

File tree

2 files changed

+151
-0
lines changed

2 files changed

+151
-0
lines changed

crates/project/src/git_store.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1399,7 +1399,44 @@ impl GitStore {
13991399
diffs.remove(buffer_id);
14001400
}
14011401
}
1402+
BufferStoreEvent::BufferChangedFilePath { buffer, .. } => {
1403+
// Whenever a buffer's file path changes, it's possible that the
1404+
// new path is actually a path that is being tracked by a git
1405+
// repository. In that case, we'll want to update the buffer's
1406+
// `BufferDiffState`, in case it already has one.
1407+
let buffer_id = buffer.read(cx).remote_id();
1408+
let diff_state = self.diffs.get(&buffer_id);
1409+
let repo = self.repository_and_path_for_buffer_id(buffer_id, cx);
1410+
1411+
if let Some(diff_state) = diff_state
1412+
&& let Some((repo, repo_path)) = repo
1413+
{
1414+
let buffer = buffer.clone();
1415+
let diff_state = diff_state.clone();
1416+
1417+
cx.spawn(async move |_git_store, cx| {
1418+
async {
1419+
let diff_bases_change = repo
1420+
.update(cx, |repo, cx| {
1421+
repo.load_committed_text(buffer_id, repo_path, cx)
1422+
})?
1423+
.await?;
14021424

1425+
diff_state.update(cx, |diff_state, cx| {
1426+
let buffer_snapshot = buffer.read(cx).text_snapshot();
1427+
diff_state.diff_bases_changed(
1428+
buffer_snapshot,
1429+
Some(diff_bases_change),
1430+
cx,
1431+
);
1432+
})
1433+
}
1434+
.await
1435+
.log_err();
1436+
})
1437+
.detach();
1438+
}
1439+
}
14031440
_ => {}
14041441
}
14051442
}

crates/project/src/project_tests.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10045,6 +10045,120 @@ async fn test_repository_deduplication(cx: &mut gpui::TestAppContext) {
1004510045
pretty_assertions::assert_eq!(repos, [Path::new(path!("/root/project")).into()]);
1004610046
}
1004710047

10048+
#[gpui::test]
10049+
async fn test_buffer_changed_file_path_updates_git_diff(cx: &mut gpui::TestAppContext) {
10050+
init_test(cx);
10051+
10052+
let file_1_committed = String::from(r#"file_1_committed"#);
10053+
let file_1_staged = String::from(r#"file_1_staged"#);
10054+
let file_2_committed = String::from(r#"file_2_committed"#);
10055+
let file_2_staged = String::from(r#"file_2_staged"#);
10056+
let buffer_contents = String::from(r#"buffer"#);
10057+
10058+
let fs = FakeFs::new(cx.background_executor.clone());
10059+
fs.insert_tree(
10060+
path!("/dir"),
10061+
json!({
10062+
".git": {},
10063+
"src": {
10064+
"file_1.rs": file_1_committed.clone(),
10065+
"file_2.rs": file_2_committed.clone(),
10066+
}
10067+
}),
10068+
)
10069+
.await;
10070+
10071+
fs.set_head_for_repo(
10072+
path!("/dir/.git").as_ref(),
10073+
&[
10074+
("src/file_1.rs", file_1_committed.clone()),
10075+
("src/file_2.rs", file_2_committed.clone()),
10076+
],
10077+
"deadbeef",
10078+
);
10079+
fs.set_index_for_repo(
10080+
path!("/dir/.git").as_ref(),
10081+
&[
10082+
("src/file_1.rs", file_1_staged.clone()),
10083+
("src/file_2.rs", file_2_staged.clone()),
10084+
],
10085+
);
10086+
10087+
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
10088+
10089+
let buffer = project
10090+
.update(cx, |project, cx| {
10091+
project.open_local_buffer(path!("/dir/src/file_1.rs"), cx)
10092+
})
10093+
.await
10094+
.unwrap();
10095+
10096+
buffer.update(cx, |buffer, cx| {
10097+
buffer.edit([(0..buffer.len(), buffer_contents.as_str())], None, cx);
10098+
});
10099+
10100+
let unstaged_diff = project
10101+
.update(cx, |project, cx| {
10102+
project.open_unstaged_diff(buffer.clone(), cx)
10103+
})
10104+
.await
10105+
.unwrap();
10106+
10107+
cx.run_until_parked();
10108+
10109+
unstaged_diff.update(cx, |unstaged_diff, _cx| {
10110+
let base_text = unstaged_diff.base_text_string().unwrap();
10111+
assert_eq!(base_text, file_1_staged, "Should start with file_1 staged");
10112+
});
10113+
10114+
// Save the buffer as `file_2.rs`, which should trigger the
10115+
// `BufferChangedFilePath` event.
10116+
project
10117+
.update(cx, |project, cx| {
10118+
let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
10119+
let path = ProjectPath {
10120+
worktree_id,
10121+
path: rel_path("src/file_2.rs").into(),
10122+
};
10123+
project.save_buffer_as(buffer.clone(), path, cx)
10124+
})
10125+
.await
10126+
.unwrap();
10127+
10128+
cx.run_until_parked();
10129+
10130+
// Verify that the diff bases have been updated to file_2's contents due to
10131+
// the `BufferChangedFilePath` event being handled.
10132+
unstaged_diff.update(cx, |unstaged_diff, cx| {
10133+
let snapshot = buffer.read(cx).snapshot();
10134+
let base_text = unstaged_diff.base_text_string().unwrap();
10135+
assert_eq!(
10136+
base_text, file_2_staged,
10137+
"Diff bases should be automatically updated to file_2 staged content"
10138+
);
10139+
10140+
let hunks: Vec<_> = unstaged_diff.hunks(&snapshot, cx).collect();
10141+
assert!(!hunks.is_empty(), "Should have diff hunks for file_2");
10142+
});
10143+
10144+
let uncommitted_diff = project
10145+
.update(cx, |project, cx| {
10146+
project.open_uncommitted_diff(buffer.clone(), cx)
10147+
})
10148+
.await
10149+
.unwrap();
10150+
10151+
cx.run_until_parked();
10152+
10153+
uncommitted_diff.update(cx, |uncommitted_diff, _cx| {
10154+
let base_text = uncommitted_diff.base_text_string().unwrap();
10155+
assert_eq!(
10156+
base_text, file_2_committed,
10157+
"Uncommitted diff should compare against file_2 committed content"
10158+
);
10159+
});
10160+
}
10161+
1004810162
async fn search(
1004910163
project: &Entity<Project>,
1005010164
query: SearchQuery,

0 commit comments

Comments
 (0)