diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index a169e062f3..82fa5d6020 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -2464,21 +2464,30 @@ impl BackgroundScannerState { // Remove any git repositories whose .git entry no longer exists. let snapshot = &mut self.snapshot; - // TODO kb stop cleaning those up here? - let mut repositories = mem::take(&mut snapshot.git_repositories); - let mut repository_entries = mem::take(&mut snapshot.repository_entries); - repositories.retain(|_, entry| { - // TODO kb use fs - snapshot.abs_path().join(&entry.git_dir_path).exists() - // snapshot - // .entry_for_id(*work_directory_id) - // .map_or(false, |entry| { - // snapshot.entry_for_path(entry.path.join(*DOT_GIT)).is_some() - // }) - }); - repository_entries.retain(|_, entry| repositories.get(&entry.work_directory.0).is_some()); - snapshot.git_repositories = repositories; - snapshot.repository_entries = repository_entries; + let mut ids_to_preserve = HashSet::default(); + for (&work_directory_id, entry) in snapshot.git_repositories.iter() { + let exists_in_snapshot = snapshot + .entry_for_id(work_directory_id) + .map_or(false, |entry| { + snapshot.entry_for_path(entry.path.join(*DOT_GIT)).is_some() + }); + if exists_in_snapshot { + ids_to_preserve.insert(work_directory_id); + } else { + let git_dir_abs_path = snapshot.abs_path().join(&entry.git_dir_path); + if snapshot.is_abs_path_excluded(&git_dir_abs_path) + && !matches!(smol::block_on(fs.metadata(&git_dir_abs_path)), Ok(None)) + { + ids_to_preserve.insert(work_directory_id); + } + } + } + snapshot + .git_repositories + .retain(|work_directory_id, _| ids_to_preserve.contains(work_directory_id)); + snapshot + .repository_entries + .retain(|_, entry| ids_to_preserve.contains(&entry.work_directory.0)); } fn build_git_repository( @@ -3320,20 +3329,22 @@ impl BackgroundScanner { return false; }; - let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| { - snapshot - .entry_for_path(parent) - .map_or(false, |entry| entry.kind == EntryKind::Dir) - }); - if !parent_dir_is_loaded && !is_git_related(&abs_path) { - log::debug!("ignoring event {relative_path:?} within unloaded directory"); - return false; - } - if snapshot.is_abs_path_excluded(abs_path) && !is_git_related(&abs_path) { - log::debug!( + if !is_git_related(&abs_path) { + let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| { + snapshot + .entry_for_path(parent) + .map_or(false, |entry| entry.kind == EntryKind::Dir) + }); + if !parent_dir_is_loaded { + log::debug!("ignoring event {relative_path:?} within unloaded directory"); + return false; + } + if snapshot.is_abs_path_excluded(abs_path) { + log::debug!( "ignoring FS event for path {relative_path:?} within excluded directory" ); - return false; + return false; + } } relative_paths.push(relative_path); @@ -3573,11 +3584,6 @@ impl BackgroundScanner { // If we find a .git, we'll need to load the repository. else if child_name == *DOT_GIT { dotgit_path = Some(child_path.clone()); - { - let mut state = self.state.lock(); - state.build_git_repository(child_path.clone(), self.fs.as_ref()); - drop(state); - } } { @@ -3595,7 +3601,7 @@ impl BackgroundScanner { Ok(Some(metadata)) => metadata, Ok(None) => continue, Err(err) => { - log::error!("error processing {:?}: {:?}", child_abs_path, err); + log::error!("error processing {child_abs_path:?}: {err:?}"); continue; } }; @@ -4124,7 +4130,7 @@ impl BackgroundScanner { } } -fn is_git_related(abs_path: &&PathBuf) -> bool { +fn is_git_related(abs_path: &Path) -> bool { abs_path .components() .any(|c| c.as_os_str() == *DOT_GIT || c.as_os_str() == *GITIGNORE) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index d66de1ad2e..e43423073c 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1732,7 +1732,7 @@ mod tests { use super::*; use gpui::{AnyWindowHandle, TestAppContext, ViewHandle, WindowHandle}; use pretty_assertions::assert_eq; - use project::FakeFs; + use project::{project_settings::ProjectSettings, FakeFs}; use serde_json::json; use settings::SettingsStore; use std::{ @@ -1832,6 +1832,123 @@ mod tests { ); } + #[gpui::test] + async fn test_exclusions_in_visible_list(cx: &mut gpui::TestAppContext) { + init_test(cx); + cx.update(|cx| { + cx.update_global::(|store, cx| { + store.update_user_settings::(cx, |project_settings| { + project_settings.file_scan_exclusions = + Some(vec!["**/.git".to_string(), "**/4/**".to_string()]); + }); + }); + }); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/root1", + json!({ + ".dockerignore": "", + ".git": { + "HEAD": "", + }, + "a": { + "0": { "q": "", "r": "", "s": "" }, + "1": { "t": "", "u": "" }, + "2": { "v": "", "w": "", "x": "", "y": "" }, + }, + "b": { + "3": { "Q": "" }, + "4": { "R": "", "S": "", "T": "", "U": "" }, + }, + "C": { + "5": {}, + "6": { "V": "", "W": "" }, + "7": { "X": "" }, + "8": { "Y": {}, "Z": "" } + } + }), + ) + .await; + fs.insert_tree( + "/root2", + json!({ + "d": { + "4": "" + }, + "e": {} + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await; + let workspace = cx + .add_window(|cx| Workspace::test_new(project.clone(), cx)) + .root(cx); + let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); + assert_eq!( + visible_entries_as_strings(&panel, 0..50, cx), + &[ + "v root1", + " > a", + " > b", + " > C", + " .dockerignore", + "v root2", + " > d", + " > e", + ] + ); + + toggle_expand_dir(&panel, "root1/b", cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..50, cx), + &[ + "v root1", + " > a", + " v b <== selected", + " > 3", + " > C", + " .dockerignore", + "v root2", + " > d", + " > e", + ] + ); + + toggle_expand_dir(&panel, "root2/d", cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..50, cx), + &[ + "v root1", + " > a", + " v b", + " > 3", + " > C", + " .dockerignore", + "v root2", + " v d <== selected", + " > e", + ] + ); + + toggle_expand_dir(&panel, "root2/e", cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..50, cx), + &[ + "v root1", + " > a", + " v b", + " > 3", + " > C", + " .dockerignore", + "v root2", + " v d", + " v e <== selected", + ] + ); + } + #[gpui::test(iterations = 30)] async fn test_editing_files(cx: &mut gpui::TestAppContext) { init_test(cx); @@ -2930,6 +3047,13 @@ mod tests { client::init_settings(cx); Project::init_settings(cx); }); + cx.update(|cx| { + cx.update_global::(|store, cx| { + store.update_user_settings::(cx, |project_settings| { + project_settings.file_scan_exclusions = Some(Vec::new()); + }); + }); + }); } fn init_test_with_editor(cx: &mut TestAppContext) {