From bd7bf82d18cdf08a6c651743d60c86f8180029b4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 28 Sep 2021 13:21:19 -0600 Subject: [PATCH] Load .gitignore files from the Fs object so we can test this with a FakeFs Co-Authored-By: Max Brunsfeld --- zed/src/worktree.rs | 133 +++++++++++++++++++++++++++++++++----------- 1 file changed, 102 insertions(+), 31 deletions(-) diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index 8859e9bf7fcc6670c3bf867e7688947450729b20..7a412d34f7d7228ad3e0ac715fc765e033cc1d1d 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -11,7 +11,7 @@ use crate::{ time::{self, ReplicaId}, util::{Bias, TryFutureExt}, }; -use ::ignore::gitignore::Gitignore; +use ::ignore::gitignore::{Gitignore, GitignoreBuilder}; use anyhow::{anyhow, Result}; use futures::{Stream, StreamExt}; pub use fuzzy::{match_paths, PathMatch}; @@ -732,12 +732,15 @@ impl LocalWorktree { next_entry_id: Arc::new(next_entry_id), }; if let Some(metadata) = metadata { - snapshot.insert_entry(Entry::new( - path.into(), - &metadata, - &snapshot.next_entry_id, - snapshot.root_char_bag, - )); + snapshot.insert_entry( + Entry::new( + path.into(), + &metadata, + &snapshot.next_entry_id, + snapshot.root_char_bag, + ), + fs.as_ref(), + ); } let (mut remote_id_tx, remote_id_rx) = watch::channel(); @@ -1156,6 +1159,16 @@ impl LocalWorktree { } } +fn build_gitignore(abs_path: &Path, fs: &dyn Fs) -> Result { + let contents = smol::block_on(fs.load(&abs_path))?; + let parent = abs_path.parent().unwrap_or(Path::new("/")); + let mut builder = GitignoreBuilder::new(parent); + for line in contents.lines() { + builder.add_line(Some(abs_path.into()), line)?; + } + Ok(builder.build()?) +} + pub fn refresh_buffer(abs_path: PathBuf, fs: &Arc, cx: &mut ModelContext) { let fs = fs.clone(); cx.spawn(|buffer, mut cx| async move { @@ -1578,16 +1591,24 @@ impl Snapshot { self.entry_for_path(path.as_ref()).map(|e| e.inode) } - fn insert_entry(&mut self, mut entry: Entry) -> Entry { + fn insert_entry(&mut self, mut entry: Entry, fs: &dyn Fs) -> Entry { + println!("insert entry {:?}", entry.path); if !entry.is_dir() && entry.path.file_name() == Some(&GITIGNORE) { - let (ignore, err) = Gitignore::new(self.abs_path.join(&entry.path)); - if let Some(err) = err { - log::error!("error in ignore file {:?} - {:?}", &entry.path, err); + let abs_path = self.abs_path.join(&entry.path); + match build_gitignore(&abs_path, fs) { + Ok(ignore) => { + let ignore_dir_path = entry.path.parent().unwrap(); + self.ignores + .insert(ignore_dir_path.into(), (Arc::new(ignore), self.scan_id)); + } + Err(error) => { + log::error!( + "error loading .gitignore file {:?} - {:?}", + &entry.path, + error + ); + } } - - let ignore_dir_path = entry.path.parent().unwrap(); - self.ignores - .insert(ignore_dir_path.into(), (Arc::new(ignore), self.scan_id)); } self.reuse_entry_id(&mut entry); @@ -2206,13 +2227,16 @@ impl BackgroundScanner { // If we find a .gitignore, add it to the stack of ignores used to determine which paths are ignored if child_name == *GITIGNORE { - let (ignore, err) = Gitignore::new(&child_abs_path); - if let Some(err) = err { - log::error!("error in ignore file {:?} - {:?}", child_name, err); + match build_gitignore(&child_abs_path, self.fs.as_ref()) { + Ok(ignore) => { + let ignore = Arc::new(ignore); + ignore_stack = ignore_stack.append(job.path.clone(), ignore.clone()); + new_ignore = Some(ignore); + } + Err(error) => { + log::error!("error loading .gitignore file {:?} - {:?}", child_name, error); + } } - let ignore = Arc::new(ignore); - ignore_stack = ignore_stack.append(job.path.clone(), ignore.clone()); - new_ignore = Some(ignore); // Update ignore status of any child entries we've already processed to reflect the // ignore file in the current directory. Because `.gitignore` starts with a `.`, @@ -2321,7 +2345,7 @@ impl BackgroundScanner { snapshot.root_char_bag, ); fs_entry.is_ignored = ignore_stack.is_all(); - snapshot.insert_entry(fs_entry); + snapshot.insert_entry(fs_entry, self.fs.as_ref()); if metadata.is_dir { scan_queue_tx .send(ScanJob { @@ -2490,7 +2514,7 @@ async fn refresh_entry( &next_entry_id, root_char_bag, ); - Ok(snapshot.lock().insert_entry(entry)) + Ok(snapshot.lock().insert_entry(entry, fs)) } fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag { @@ -2774,6 +2798,50 @@ mod tests { use std::time::UNIX_EPOCH; use std::{env, fmt::Write, os::unix, time::SystemTime}; + #[gpui::test] + async fn test_traversal(cx: gpui::TestAppContext) { + let fs = FakeFs::new(); + fs.insert_tree( + "/root", + json!({ + ".gitignore": "a/b\n", + "a": { + "b": "", + "c": "", + } + }), + ) + .await; + + let tree = Worktree::open_local( + rpc::Client::new(), + Arc::from(Path::new("/root")), + Arc::new(fs), + 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, cx| { + dbg!(tree.entries_by_path.items(&())); + + assert_eq!( + tree.entries(false) + .map(|entry| entry.path.as_ref()) + .collect::>(), + vec![ + Path::new(""), + Path::new(".gitignore"), + Path::new("a"), + Path::new("a/c"), + ] + ); + }) + } + #[gpui::test] async fn test_populate_and_search(cx: gpui::TestAppContext) { let dir = temp_tree(json!({ @@ -3260,14 +3328,17 @@ mod tests { root_char_bag: Default::default(), next_entry_id: next_entry_id.clone(), }; - initial_snapshot.insert_entry(Entry::new( - Path::new("").into(), - &smol::block_on(fs.metadata(root_dir.path())) - .unwrap() - .unwrap(), - &next_entry_id, - Default::default(), - )); + initial_snapshot.insert_entry( + Entry::new( + Path::new("").into(), + &smol::block_on(fs.metadata(root_dir.path())) + .unwrap() + .unwrap(), + &next_entry_id, + Default::default(), + ), + fs.as_ref(), + ); let mut scanner = BackgroundScanner::new( Arc::new(Mutex::new(initial_snapshot.clone())), notify_tx,