Fix file git status refresh on .gitignore update (#9466)

This PR fixes file name coloring in the project panel and tabs when
.gitignore file is updated. It's intended to fix #7831.

There's another, less vivid, problem with git-aware labels coloring.
It's about files that are both ignored and contained in the git index.
I'll file a separate issue for it to keep this fix focused.

Release Notes:

- Fixed file Git status refreshing on .gitignore update (#7831).
This commit is contained in:
Andrew Lygin 2024-03-19 05:35:38 +03:00 committed by GitHub
parent 1e1fb21c81
commit 192cd5f2d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 132 additions and 65 deletions

View file

@ -4225,6 +4225,9 @@ impl BackgroundScanner {
let mut entries_by_id_edits = Vec::new();
let mut entries_by_path_edits = Vec::new();
let path = job.abs_path.strip_prefix(&snapshot.abs_path).unwrap();
let repo = snapshot
.local_repo_for_path(path)
.map_or(None, |local_repo| Some(local_repo.1));
for mut entry in snapshot.child_entries(path).cloned() {
let was_ignored = entry.is_ignored;
let abs_path: Arc<Path> = snapshot.abs_path().join(&entry.path).into();
@ -4259,6 +4262,15 @@ impl BackgroundScanner {
let mut path_entry = snapshot.entries_by_id.get(&entry.id, &()).unwrap().clone();
path_entry.scan_id = snapshot.scan_id;
path_entry.is_ignored = entry.is_ignored;
if !entry.is_dir() && !entry.is_ignored && !entry.is_external {
if let Some(repo) = repo {
if let Some(mtime) = &entry.mtime {
let repo_path = RepoPath(entry.path.to_path_buf());
let repo = repo.repo_ptr.lock();
entry.git_status = repo.status(&repo_path, *mtime);
}
}
}
entries_by_id_edits.push(Edit::Insert(path_entry));
entries_by_path_edits.push(Edit::Insert(entry));
}

View file

@ -851,24 +851,16 @@ async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
cx.read(|cx| {
let tree = tree.read(cx);
assert!(
!tree
.entry_for_path("tracked-dir/tracked-file1")
.unwrap()
.is_ignored
);
assert!(
tree.entry_for_path("tracked-dir/ancestor-ignored-file1")
.unwrap()
.is_ignored
);
assert!(
tree.entry_for_path("ignored-dir/ignored-file1")
.unwrap()
.is_ignored
);
assert_entry_git_state(tree, "tracked-dir/tracked-file1", None, false);
assert_entry_git_state(tree, "tracked-dir/ancestor-ignored-file1", None, true);
assert_entry_git_state(tree, "ignored-dir/ignored-file1", None, true);
});
fs.set_status_for_repo_via_working_copy_change(
&Path::new("/root/tree/.git"),
&[(Path::new("tracked-dir/tracked-file2"), GitFileStatus::Added)],
);
fs.create_file(
"/root/tree/tracked-dir/tracked-file2".as_ref(),
Default::default(),
@ -891,26 +883,77 @@ async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
cx.executor().run_until_parked();
cx.read(|cx| {
let tree = tree.read(cx);
assert!(
!tree
.entry_for_path("tracked-dir/tracked-file2")
.unwrap()
.is_ignored
);
assert!(
tree.entry_for_path("tracked-dir/ancestor-ignored-file2")
.unwrap()
.is_ignored
);
assert!(
tree.entry_for_path("ignored-dir/ignored-file2")
.unwrap()
.is_ignored
assert_entry_git_state(
tree,
"tracked-dir/tracked-file2",
Some(GitFileStatus::Added),
false,
);
assert_entry_git_state(tree, "tracked-dir/ancestor-ignored-file2", None, true);
assert_entry_git_state(tree, "ignored-dir/ignored-file2", None, true);
assert!(tree.entry_for_path(".git").unwrap().is_ignored);
});
}
#[gpui::test]
async fn test_update_gitignore(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree(
"/root",
json!({
".git": {},
".gitignore": "*.txt\n",
"a.xml": "<a></a>",
"b.txt": "Some text"
}),
)
.await;
let tree = Worktree::local(
build_client(cx),
"/root".as_ref(),
true,
fs.clone(),
Default::default(),
&mut cx.to_async(),
)
.await
.unwrap();
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
.await;
tree.read_with(cx, |tree, _| {
tree.as_local()
.unwrap()
.refresh_entries_for_paths(vec![Path::new("").into()])
})
.recv()
.await;
cx.read(|cx| {
let tree = tree.read(cx);
assert_entry_git_state(tree, "a.xml", None, false);
assert_entry_git_state(tree, "b.txt", None, true);
});
fs.atomic_write("/root/.gitignore".into(), "*.xml".into())
.await
.unwrap();
fs.set_status_for_repo_via_working_copy_change(
&Path::new("/root/.git"),
&[(Path::new("b.txt"), GitFileStatus::Added)],
);
cx.executor().run_until_parked();
cx.read(|cx| {
let tree = tree.read(cx);
assert_entry_git_state(tree, "a.xml", None, true);
assert_entry_git_state(tree, "b.txt", Some(GitFileStatus::Added), false);
});
}
#[gpui::test]
async fn test_write_file(cx: &mut TestAppContext) {
init_test(cx);
@ -2580,3 +2623,14 @@ fn init_test(cx: &mut gpui::TestAppContext) {
WorktreeSettings::register(cx);
});
}
fn assert_entry_git_state(
tree: &Worktree,
path: &str,
git_status: Option<GitFileStatus>,
is_ignored: bool,
) {
let entry = tree.entry_for_path(path).expect("entry {path} not found");
assert_eq!(entry.git_status, git_status);
assert_eq!(entry.is_ignored, is_ignored);
}