diff --git a/Cargo.lock b/Cargo.lock index 8d6115fd1e..b8b7d9ad22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -448,16 +448,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "crossbeam-channel" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" -dependencies = [ - "crossbeam-utils 0.7.2", - "maybe-uninit", -] - [[package]] name = "crossbeam-channel" version = "0.5.0" @@ -465,7 +455,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.2", + "crossbeam-utils", ] [[package]] @@ -475,18 +465,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f6cb3c7f5b8e51bc3ebb73a2327ad4abdbd119dc13223f14f961d2f38486756" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.2", -] - -[[package]] -name = "crossbeam-utils" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" -dependencies = [ - "autocfg", - "cfg-if 0.1.10", - "lazy_static", + "crossbeam-utils", ] [[package]] @@ -891,8 +870,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "globset" -version = "0.4.4" -source = "git+https://github.com/zed-industries/ripgrep?rev=1d152118f35b3e3590216709b86277062d79b8a0#1d152118f35b3e3590216709b86277062d79b8a0" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c152169ef1e421390738366d2f796655fec62621dabbd0fd476f905934061e4a" dependencies = [ "aho-corasick", "bstr", @@ -967,11 +947,11 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "ignore" -version = "0.4.11" -source = "git+https://github.com/zed-industries/ripgrep?rev=1d152118f35b3e3590216709b86277062d79b8a0#1d152118f35b3e3590216709b86277062d79b8a0" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b287fb45c60bb826a0dc68ff08742b9d88a2fea13d6e0c286b3172065aaf878c" dependencies = [ - "crossbeam-channel 0.4.4", - "crossbeam-utils 0.7.2", + "crossbeam-utils", "globset", "lazy_static", "log", @@ -1095,12 +1075,6 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" - [[package]] name = "memchr" version = "2.3.4" @@ -1616,7 +1590,7 @@ dependencies = [ "base64", "blake2b_simd", "constant_time_eq", - "crossbeam-utils 0.8.2", + "crossbeam-utils", ] [[package]] @@ -1711,7 +1685,7 @@ dependencies = [ name = "scoped-pool" version = "0.0.1" dependencies = [ - "crossbeam-channel 0.5.0", + "crossbeam-channel", ] [[package]] @@ -2246,7 +2220,7 @@ version = "0.1.0" dependencies = [ "anyhow", "arrayvec", - "crossbeam-channel 0.5.0", + "crossbeam-channel", "ctor", "dirs", "easy-parallel", diff --git a/zed/Cargo.toml b/zed/Cargo.toml index fde1cfdbbe..c38db66d0f 100644 --- a/zed/Cargo.toml +++ b/zed/Cargo.toml @@ -22,7 +22,7 @@ easy-parallel = "3.1.0" fsevent = {path = "../fsevent"} futures-core = "0.3" gpui = {path = "../gpui"} -ignore = {git = "https://github.com/zed-industries/ripgrep", rev = "1d152118f35b3e3590216709b86277062d79b8a0"} +ignore = "0.4" lazy_static = "1.4.0" libc = "0.2" log = "0.4" diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index b4b798e7e8..2ce2af9305 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -9,7 +9,7 @@ use anyhow::{anyhow, Context, Result}; use fuzzy::PathEntry; pub use fuzzy::{match_paths, PathMatch}; use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, Task}; -use ignore::dir::{Ignore, IgnoreBuilder}; +use ignore::gitignore::Gitignore; use parking_lot::Mutex; use postage::{ prelude::{Sink, Stream}, @@ -29,6 +29,8 @@ use std::{ time::Duration, }; +const GITIGNORE: &'static str = ".gitignore"; + #[derive(Clone, Debug)] enum ScanState { Idle, @@ -58,6 +60,7 @@ impl Worktree { id, path: path.into(), root_inode: None, + ignores: Default::default(), entries: Default::default(), }; let (event_stream, event_stream_handle) = @@ -229,6 +232,7 @@ pub struct Snapshot { id: usize, path: Arc, root_inode: Option, + ignores: HashMap, entries: SumTree, } @@ -268,6 +272,48 @@ impl Snapshot { }) } + pub fn is_path_ignored(&self, path: impl AsRef) -> Result { + if let Some(inode) = self.inode_for_path(path.as_ref()) { + self.is_inode_ignored(inode) + } else { + Ok(false) + } + } + + pub fn is_inode_ignored(&self, mut inode: u64) -> Result { + let mut components = Vec::new(); + let mut relative_path = PathBuf::new(); + let mut entry = self + .entries + .get(&inode) + .ok_or_else(|| anyhow!("entry does not exist in worktree"))?; + while let Some(parent) = entry.parent() { + let parent_entry = self.entries.get(&parent).unwrap(); + if let Entry::Dir { children, .. } = parent_entry { + let (_, child_name) = children + .iter() + .find(|(child_inode, _)| *child_inode == inode) + .unwrap(); + components.push(child_name.as_ref()); + inode = parent; + + if let Some(ignore) = self.ignores.get(&inode) { + relative_path.clear(); + relative_path.extend(components.iter().rev()); + match ignore.matched_path_or_any_parents(&relative_path, entry.is_dir()) { + ignore::Match::Whitelist(_) => return Ok(false), + ignore::Match::Ignore(_) => return Ok(true), + ignore::Match::None => {} + } + } + } else { + unreachable!(); + } + entry = parent_entry; + } + Ok(false) + } + pub fn path_for_inode(&self, mut inode: u64, include_root: bool) -> Result { let mut components = Vec::new(); let mut entry = self @@ -311,13 +357,18 @@ impl Snapshot { // Insert the entry in its new parent with the correct name. if let Some(new_parent_inode) = entry.parent() { + let name = name.unwrap(); + if name == GITIGNORE { + self.insert_ignore_file(new_parent_inode); + } + let mut new_parent = self.entries.get(&new_parent_inode).unwrap().clone(); if let Entry::Dir { children, .. } = &mut new_parent { *children = children .iter() .filter(|(inode, _)| *inode != entry.inode()) .cloned() - .chain(Some((entry.inode(), name.unwrap().into()))) + .chain(Some((entry.inode(), name.into()))) .collect::>() .into(); } else { @@ -346,6 +397,10 @@ impl Snapshot { let mut new_children = Vec::new(); let mut old_children = HashMap::>::new(); for (name, child) in children.into_iter() { + if *name == *GITIGNORE { + self.insert_ignore_file(parent_inode); + } + new_children.push((child.inode(), name.into())); if let Some(old_child) = self.entries.get(&child.inode()) { if let Some(old_parent_inode) = old_child.parent() { @@ -389,6 +444,11 @@ impl Snapshot { self.clear_descendants(entry.inode(), &mut edits); if let Some(parent_inode) = entry.parent() { + if let Some(file_name) = path.file_name() { + if file_name == GITIGNORE { + self.remove_ignore_file(parent_inode); + } + } let parent = self.entries.get(&parent_inode).unwrap().clone(); self.remove_children(parent, &mut edits, |inode| inode == entry.inode()); } @@ -402,12 +462,19 @@ impl Snapshot { fn clear_descendants(&mut self, inode: u64, edits: &mut Vec>) { let mut stack = vec![inode]; while let Some(inode) = stack.pop() { + let mut has_gitignore = false; if let Entry::Dir { children, .. } = self.entries.get(&inode).unwrap() { - for (child_inode, _) in children.iter() { + for (child_inode, child_name) in children.iter() { + if **child_name == *GITIGNORE { + has_gitignore = true; + } edits.push(Edit::Remove(*child_inode)); stack.push(*child_inode); } } + if has_gitignore { + self.remove_ignore_file(inode); + } } } @@ -430,6 +497,22 @@ impl Snapshot { edits.push(Edit::Insert(parent)); } + fn insert_ignore_file(&mut self, dir_inode: u64) { + let mut path = self.path.to_path_buf(); + path.push(self.path_for_inode(dir_inode, false).unwrap()); + path.push(GITIGNORE); + let (ignore, err) = Gitignore::new(&path); + if let Some(err) = err { + log::info!("error in ignore file {:?} - {:?}", path, err); + } + + self.ignores.insert(dir_inode, ignore); + } + + fn remove_ignore_file(&mut self, dir_inode: u64) { + self.ignores.remove(&dir_inode); + } + fn fmt_entry( &self, f: &mut fmt::Formatter<'_>, @@ -500,7 +583,6 @@ pub enum Entry { inode: u64, parent: Option, is_symlink: bool, - is_ignored: bool, children: Arc<[(u64, Arc)]>, pending: bool, }, @@ -508,7 +590,6 @@ pub enum Entry { inode: u64, parent: Option, is_symlink: bool, - is_ignored: bool, path: PathEntry, }, } @@ -647,19 +728,11 @@ impl BackgroundScanner { let name = Arc::from(path.file_name().unwrap_or(OsStr::new("/"))); let relative_path = PathBuf::from(&name); - let mut ignore = IgnoreBuilder::new().build().add_parents(&path).unwrap(); - if metadata.is_dir() { - ignore = ignore.add_child(&path).unwrap(); - } - let is_ignored = ignore.matched(&path, metadata.is_dir()).is_ignore(); - if metadata.file_type().is_dir() { - let is_ignored = is_ignored || name.as_ref() == ".git"; let dir_entry = Entry::Dir { parent: None, inode, is_symlink, - is_ignored, children: Arc::from([]), pending: true, }; @@ -676,7 +749,6 @@ impl BackgroundScanner { inode, path: path.clone(), relative_path, - ignore: Some(ignore), scan_queue: tx.clone(), })) .unwrap(); @@ -704,10 +776,9 @@ impl BackgroundScanner { None, Entry::File { parent: None, - path: PathEntry::new(inode, &relative_path, is_ignored), + path: PathEntry::new(inode, &relative_path), inode, is_symlink, - is_ignored, }, ); snapshot.root_inode = Some(inode); @@ -732,25 +803,12 @@ impl BackgroundScanner { let child_path = job.path.join(child_name.as_ref()); if child_metadata.is_dir() { - let mut is_ignored = true; - let mut ignore = None; - - if let Some(parent_ignore) = job.ignore.as_ref() { - let child_ignore = parent_ignore.add_child(&child_path).unwrap(); - is_ignored = child_ignore.matched(&child_path, true).is_ignore() - || child_name.as_ref() == ".git"; - if !is_ignored { - ignore = Some(child_ignore); - } - } - new_entries.push(( child_name, Entry::Dir { parent: Some(job.inode), inode: child_inode, is_symlink: child_is_symlink, - is_ignored, children: Arc::from([]), pending: true, }, @@ -759,22 +817,16 @@ impl BackgroundScanner { inode: child_inode, path: Arc::from(child_path), relative_path: child_relative_path, - ignore, scan_queue: scan_queue.clone(), }); } else { - let is_ignored = job - .ignore - .as_ref() - .map_or(true, |i| i.matched(&child_path, false).is_ignore()); new_entries.push(( child_name, Entry::File { parent: Some(job.inode), - path: PathEntry::new(child_inode, &child_relative_path, is_ignored), + path: PathEntry::new(child_inode, &child_relative_path), inode: child_inode, is_symlink: child_is_symlink, - is_ignored, }, )); }; @@ -815,7 +867,7 @@ impl BackgroundScanner { snapshot.remove_path(&relative_path); match self.fs_entry_for_path(&root_path, &path) { - Ok(Some((fs_entry, ignore))) => { + Ok(Some(fs_entry)) => { let is_dir = fs_entry.is_dir(); let inode = fs_entry.inode(); @@ -829,7 +881,6 @@ impl BackgroundScanner { .root_name() .map_or(PathBuf::new(), PathBuf::from) .join(relative_path), - ignore: Some(ignore), scan_queue: scan_queue_tx.clone(), })) .unwrap(); @@ -862,7 +913,7 @@ impl BackgroundScanner { true } - fn fs_entry_for_path(&self, root_path: &Path, path: &Path) -> Result> { + fn fs_entry_for_path(&self, root_path: &Path, path: &Path) -> Result> { let metadata = match fs::metadata(&path) { Err(err) => { return match (err.kind(), err.raw_os_error()) { @@ -874,11 +925,6 @@ impl BackgroundScanner { Ok(metadata) => metadata, }; - let mut ignore = IgnoreBuilder::new().build().add_parents(&path).unwrap(); - if metadata.is_dir() { - ignore = ignore.add_child(&path).unwrap(); - } - let is_ignored = ignore.matched(&path, metadata.is_dir()).is_ignore(); let inode = metadata.ino(); let is_symlink = fs::symlink_metadata(&path) .context("failed to read symlink metadata")? @@ -899,7 +945,6 @@ impl BackgroundScanner { inode, parent, is_symlink, - is_ignored, pending: true, children: Arc::from([]), } @@ -908,18 +953,16 @@ impl BackgroundScanner { inode, parent, is_symlink, - is_ignored, path: PathEntry::new( inode, root_path .parent() .map_or(path, |parent| path.strip_prefix(parent).unwrap()), - is_ignored, ), } }; - Ok(Some((entry, ignore))) + Ok(Some(entry)) } } @@ -927,7 +970,6 @@ struct ScanJob { inode: u64, path: Arc, relative_path: PathBuf, - ignore: Option, scan_queue: crossbeam_channel::Sender>, } @@ -948,19 +990,6 @@ impl WorktreeHandle for ModelHandle { } } -trait UnwrapIgnoreTuple { - fn unwrap(self) -> Ignore; -} - -impl UnwrapIgnoreTuple for (Ignore, Option) { - fn unwrap(self) -> Ignore { - if let Some(error) = self.1 { - log::error!("error loading gitignore data: {}", error); - } - self.0 - } -} - #[cfg(test)] mod tests { use super::*; @@ -1114,31 +1143,17 @@ mod tests { app.read(|ctx| tree.read(ctx).scan_complete()).await; app.read(|ctx| { let tree = tree.read(ctx); - assert!(!tree - .entry_for_path("tracked-dir/tracked-file1") - .unwrap() - .is_ignored()); - assert!(tree - .entry_for_path("ignored-dir/ignored-file1") - .unwrap() - .is_ignored()); + assert!(!tree.is_path_ignored("tracked-dir/tracked-file1").unwrap()); + assert!(tree.is_path_ignored("ignored-dir/ignored-file1").unwrap()); }); fs::write(dir.path().join("tracked-dir/tracked-file2"), "").unwrap(); fs::write(dir.path().join("ignored-dir/ignored-file2"), "").unwrap(); - // tree.condition(&app, move |_, ctx| file2.path(ctx) == Path::new("d/file2")) - // .await; - app.read(|ctx| tree.read(ctx).scan_complete()).await; + app.read(|ctx| tree.read(ctx).next_scan_complete()).await; app.read(|ctx| { let tree = tree.read(ctx); - assert!(!tree - .entry_for_path("tracked-dir/tracked-file2") - .unwrap() - .is_ignored()); - assert!(tree - .entry_for_path("ignored-dir/ignored-file2") - .unwrap() - .is_ignored()); + assert!(!tree.is_path_ignored("tracked-dir/tracked-file2").unwrap()); + assert!(tree.is_path_ignored("ignored-dir/ignored-file2").unwrap()); }); }); } @@ -1177,6 +1192,7 @@ mod tests { path: root_dir.path().into(), root_inode: None, entries: Default::default(), + ignores: Default::default(), })), notify_tx, 0, @@ -1206,6 +1222,7 @@ mod tests { path: root_dir.path().into(), root_inode: None, entries: Default::default(), + ignores: Default::default(), })), notify_tx, 1, @@ -1330,14 +1347,6 @@ mod tests { .collect() } - impl Entry { - fn is_ignored(&self) -> bool { - match self { - Entry::Dir { is_ignored, .. } | Entry::File { is_ignored, .. } => *is_ignored, - } - } - } - impl Snapshot { fn check_invariants(&self) { for entry in self.entries.items() { diff --git a/zed/src/worktree/fuzzy.rs b/zed/src/worktree/fuzzy.rs index dbb2985fac..c647164a9b 100644 --- a/zed/src/worktree/fuzzy.rs +++ b/zed/src/worktree/fuzzy.rs @@ -21,11 +21,10 @@ pub struct PathEntry { pub path_chars: CharBag, pub path: Arc<[char]>, pub lowercase_path: Arc<[char]>, - pub is_ignored: bool, } impl PathEntry { - pub fn new(ino: u64, path: &Path, is_ignored: bool) -> Self { + pub fn new(ino: u64, path: &Path) -> Self { let path = path.to_string_lossy(); let lowercase_path = path.to_lowercase().chars().collect::>().into(); let path: Arc<[char]> = path.chars().collect::>().into(); @@ -36,7 +35,6 @@ impl PathEntry { path_chars, path, lowercase_path, - is_ignored, } } } @@ -84,6 +82,7 @@ where { let lowercase_query = query.to_lowercase().chars().collect::>(); let query = query.chars().collect::>(); + let lowercase_query = &lowercase_query; let query = &query; let query_chars = CharBag::from(&lowercase_query[..]); @@ -139,7 +138,7 @@ where }; match_single_tree_paths( - snapshot.id, + snapshot, skipped_prefix_len, path_entries, query, @@ -176,7 +175,7 @@ where } fn match_single_tree_paths<'a>( - tree_id: usize, + snapshot: &Snapshot, skipped_prefix_len: usize, path_entries: impl Iterator, query: &[char], @@ -193,11 +192,11 @@ fn match_single_tree_paths<'a>( best_position_matrix: &mut Vec, ) { for path_entry in path_entries { - if !include_ignored && path_entry.is_ignored { + if !path_entry.path_chars.is_superset(query_chars.clone()) { continue; } - if !path_entry.path_chars.is_superset(query_chars.clone()) { + if !include_ignored && snapshot.is_inode_ignored(path_entry.ino).unwrap_or(true) { continue; } @@ -232,7 +231,7 @@ fn match_single_tree_paths<'a>( if score > 0.0 { results.push(Reverse(PathMatch { - tree_id, + tree_id: snapshot.id, entry_id: path_entry.ino, path: path_entry.path.iter().skip(skipped_prefix_len).collect(), score, @@ -438,6 +437,7 @@ fn recursive_score_match( #[cfg(test)] mod tests { use super::*; + use std::path::PathBuf; #[test] fn test_match_path_entries() { @@ -500,7 +500,6 @@ mod tests { path_chars, path, lowercase_path, - is_ignored: false, }); } @@ -511,7 +510,13 @@ mod tests { let mut results = BinaryHeap::new(); match_single_tree_paths( - 0, + &Snapshot { + id: 0, + path: PathBuf::new().into(), + root_inode: None, + ignores: Default::default(), + entries: Default::default(), + }, 0, path_entries.iter(), &query[..],