Test and filter data draft

This commit is contained in:
Kirill Bulatov 2023-11-10 15:23:08 +02:00
parent a5c615ceb4
commit 7d97dfa6be
4 changed files with 186 additions and 32 deletions

View File

@ -1,5 +1,5 @@
use ignore::gitignore::Gitignore;
use std::{ffi::OsStr, path::Path, sync::Arc};
use std::{path::Path, sync::Arc};
pub enum IgnoreStack {
None,
@ -34,24 +34,4 @@ impl IgnoreStack {
}),
}
}
pub fn is_abs_path_ignored(&self, abs_path: &Path, is_dir: bool) -> bool {
if is_dir && abs_path.file_name() == Some(OsStr::new(".git")) {
return true;
}
match self {
Self::None => false,
Self::All => true,
Self::Some {
abs_base_path,
ignore,
parent: prev,
} => match ignore.matched(abs_path.strip_prefix(abs_base_path).unwrap(), is_dir) {
ignore::Match::None => prev.is_abs_path_ignored(abs_path, is_dir),
ignore::Match::Ignore(_) => true,
ignore::Match::Whitelist(_) => false,
},
}
}
}

View File

@ -10,6 +10,12 @@ pub struct ProjectSettings {
pub lsp: HashMap<Arc<str>, LspSettings>,
#[serde(default)]
pub git: GitSettings,
// TODO kb better names and docs
// TODO kb how to react on their changes?
#[serde(default)]
pub scan_exclude_files: Vec<String>,
#[serde(default)]
pub scan_include_files: Vec<String>,
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]

View File

@ -1,5 +1,6 @@
use crate::{
copy_recursive, ignore::IgnoreStack, DiagnosticSummary, ProjectEntryId, RemoveOptions,
copy_recursive, ignore::IgnoreStack, project_settings::ProjectSettings, DiagnosticSummary,
ProjectEntryId, RemoveOptions,
};
use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
use anyhow::{anyhow, Context, Result};
@ -55,7 +56,10 @@ use std::{
time::{Duration, SystemTime},
};
use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet};
use util::{paths::HOME, ResultExt};
use util::{
paths::{PathMatcher, HOME},
ResultExt,
};
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
pub struct WorktreeId(usize);
@ -216,6 +220,8 @@ pub struct LocalSnapshot {
/// All of the git repositories in the worktree, indexed by the project entry
/// id of their parent directory.
git_repositories: TreeMap<ProjectEntryId, LocalRepositoryEntry>,
scan_exclude_files: Vec<PathMatcher>,
scan_include_files: Vec<PathMatcher>,
}
struct BackgroundScannerState {
@ -303,8 +309,34 @@ impl Worktree {
let root_name = abs_path
.file_name()
.map_or(String::new(), |f| f.to_string_lossy().to_string());
let project_settings = settings::get::<ProjectSettings>(cx);
let scan_exclude_files = project_settings.scan_exclude_files.iter()
.filter_map(|pattern| {
PathMatcher::new(pattern)
.map(Some)
.unwrap_or_else(|e| {
log::error!(
"Skipping pattern {pattern} in `scan_exclude_files` project settings due to parsing error: {e:#}"
);
None
})
})
.collect::<Vec<_>>();
let scan_include_files = project_settings.scan_include_files.iter()
.filter_map(|pattern| {
PathMatcher::new(pattern)
.map(Some)
.unwrap_or_else(|e| {
log::error!(
"Skipping pattern {pattern} in `scan_include_files` project settings due to parsing error: {e:#}"
);
None
})
})
.collect::<Vec<_>>();
let mut snapshot = LocalSnapshot {
scan_include_files,
scan_exclude_files,
ignores_by_parent_abs_path: Default::default(),
git_repositories: Default::default(),
snapshot: Snapshot {
@ -2042,7 +2074,7 @@ impl LocalSnapshot {
let mut ignore_stack = IgnoreStack::none();
for (parent_abs_path, ignore) in new_ignores.into_iter().rev() {
if ignore_stack.is_abs_path_ignored(parent_abs_path, true) {
if self.is_abs_path_ignored(parent_abs_path, &ignore_stack, true) {
ignore_stack = IgnoreStack::all();
break;
} else if let Some(ignore) = ignore {
@ -2050,7 +2082,7 @@ impl LocalSnapshot {
}
}
if ignore_stack.is_abs_path_ignored(abs_path, is_dir) {
if self.is_abs_path_ignored(abs_path, &ignore_stack, is_dir) {
ignore_stack = IgnoreStack::all();
}
ignore_stack
@ -2145,6 +2177,45 @@ impl LocalSnapshot {
paths.sort_by(|a, b| a.0.cmp(b.0));
paths
}
fn is_abs_path_ignored(
&self,
abs_path: &Path,
ignore_stack: &IgnoreStack,
is_dir: bool,
) -> bool {
dbg!(&abs_path);
if self
.scan_include_files
.iter()
.any(|include_matcher| include_matcher.is_match(abs_path))
{
dbg!("included!!");
return false;
} else if self
.scan_exclude_files
.iter()
.any(|exclude_matcher| exclude_matcher.is_match(abs_path))
{
dbg!("excluded!!");
return true;
} else if is_dir && abs_path.file_name() == Some(OsStr::new(".git")) {
return true;
}
match ignore_stack {
IgnoreStack::None => false,
IgnoreStack::All => true,
IgnoreStack::Some {
abs_base_path,
ignore,
parent: prev,
} => match ignore.matched(abs_path.strip_prefix(abs_base_path).unwrap(), is_dir) {
ignore::Match::None => self.is_abs_path_ignored(abs_path, &prev, is_dir),
ignore::Match::Ignore(_) => true,
ignore::Match::Whitelist(_) => false,
},
}
}
}
impl BackgroundScannerState {
@ -2767,7 +2838,7 @@ pub struct Entry {
pub mtime: SystemTime,
pub is_symlink: bool,
/// Whether this entry is ignored by Git.
/// Whether this entry is ignored by Zed.
///
/// We only scan ignored entries once the directory is expanded and
/// exclude them from searches.
@ -3464,7 +3535,7 @@ impl BackgroundScanner {
for entry in &mut new_entries {
let entry_abs_path = root_abs_path.join(&entry.path);
entry.is_ignored =
ignore_stack.is_abs_path_ignored(&entry_abs_path, entry.is_dir());
self.is_abs_path_ignored(&entry_abs_path, &ignore_stack, entry.is_dir());
if entry.is_dir() {
if let Some(job) = new_jobs.next().expect("missing scan job for entry") {
@ -3523,7 +3594,8 @@ impl BackgroundScanner {
}
if child_entry.is_dir() {
child_entry.is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, true);
child_entry.is_ignored =
self.is_abs_path_ignored(&child_abs_path, &ignore_stack, true);
// Avoid recursing until crash in the case of a recursive symlink
if !job.ancestor_inodes.contains(&child_entry.inode) {
@ -3547,7 +3619,8 @@ impl BackgroundScanner {
new_jobs.push(None);
}
} else {
child_entry.is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, false);
child_entry.is_ignored =
self.is_abs_path_ignored(&child_abs_path, &ignore_stack, false);
if !child_entry.is_ignored {
if let Some((repository_dir, repository, staged_statuses)) =
&job.containing_repository
@ -3825,7 +3898,7 @@ impl BackgroundScanner {
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();
entry.is_ignored = ignore_stack.is_abs_path_ignored(&abs_path, entry.is_dir());
entry.is_ignored = self.is_abs_path_ignored(&abs_path, &ignore_stack, entry.is_dir());
if entry.is_dir() {
let child_ignore_stack = if entry.is_ignored {
IgnoreStack::all()
@ -4008,6 +4081,18 @@ impl BackgroundScanner {
smol::Timer::after(Duration::from_millis(100)).await;
}
fn is_abs_path_ignored(
&self,
abs_path: &Path,
ignore_stack: &IgnoreStack,
is_dir: bool,
) -> bool {
self.state
.lock()
.snapshot
.is_abs_path_ignored(abs_path, ignore_stack, is_dir)
}
}
fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag {

View File

@ -1,6 +1,7 @@
use crate::{
project_settings::ProjectSettings,
worktree::{Event, Snapshot, WorktreeModelHandle},
Entry, EntryKind, PathChange, Worktree,
Entry, EntryKind, PathChange, Project, Worktree,
};
use anyhow::Result;
use client::Client;
@ -12,6 +13,7 @@ use postage::stream::Stream;
use pretty_assertions::assert_eq;
use rand::prelude::*;
use serde_json::json;
use settings::SettingsStore;
use std::{
env,
fmt::Write,
@ -877,6 +879,87 @@ async fn test_write_file(cx: &mut TestAppContext) {
});
}
#[gpui::test]
async fn test_ignore_inclusions_and_exclusions(cx: &mut TestAppContext) {
let dir = temp_tree(json!({
".git": {},
".gitignore": "**/target\n/node_modules\n",
"target": {},
"node_modules": {
".DS_Store": "",
"prettier": {
"package.json": "{}",
},
},
"src": {
".DS_Store": "",
"foo": {
"foo.rs": "mod another;\n",
"another.rs": "// another",
},
"bar": {
"bar.rs": "// bar",
},
"lib.rs": "mod foo;\nmod bar;\n",
},
".DS_Store": "",
}));
cx.update(|cx| {
cx.set_global(SettingsStore::test(cx));
Project::init_settings(cx);
cx.update_global::<SettingsStore, _, _>(|store, cx| {
store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
project_settings.scan_exclude_files =
vec!["**/foo/**".to_string(), "**/.DS_Store".to_string()];
project_settings.scan_include_files = vec!["**/node_modules".to_string()];
});
});
});
let tree = Worktree::local(
build_client(cx),
dir.path(),
true,
Arc::new(RealFs),
Default::default(),
&mut cx.to_async(),
)
.await
.unwrap();
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
.await;
tree.flush_fs_events(cx).await;
// tree.update(cx, |tree, cx| {
// tree.as_local().unwrap().write_file(
// Path::new("tracked-dir/file.txt"),
// "hello".into(),
// Default::default(),
// cx,
// )
// })
// .await
// .unwrap();
// tree.update(cx, |tree, cx| {
// tree.as_local().unwrap().write_file(
// Path::new("ignored-dir/file.txt"),
// "world".into(),
// Default::default(),
// cx,
// )
// })
// .await
// .unwrap();
// tree.read_with(cx, |tree, _| {
// let tracked = tree.entry_for_path("tracked-dir/file.txt").unwrap();
// let ignored = tree.entry_for_path("ignored-dir/file.txt").unwrap();
// assert!(!tracked.is_ignored);
// assert!(ignored.is_ignored);
// });
dbg!("!!!!!!!!!!!!");
}
#[gpui::test(iterations = 30)]
async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
let fs = FakeFs::new(cx.background());