aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBernardo <[email protected]>2019-01-19 21:28:51 +0000
committerAleksey Kladov <[email protected]>2019-01-26 08:46:27 +0000
commiteacf7aeb42d7ba54c305664773e77eb592b51b99 (patch)
treedb4cec298738212beeb43f1cf428fa955df35b9b
parentfb1d748a2c49597934337432a78be2a5a098ca0e (diff)
ignore check event dir for ignore, cleanup tests
-rw-r--r--crates/ra_vfs/src/watcher.rs61
-rw-r--r--crates/ra_vfs/tests/vfs.rs131
2 files changed, 129 insertions, 63 deletions
diff --git a/crates/ra_vfs/src/watcher.rs b/crates/ra_vfs/src/watcher.rs
index 9d552f886..f0ef9bc0e 100644
--- a/crates/ra_vfs/src/watcher.rs
+++ b/crates/ra_vfs/src/watcher.rs
@@ -1,7 +1,7 @@
1use crate::io; 1use crate::io;
2use crossbeam_channel::Sender; 2use crossbeam_channel::Sender;
3use drop_bomb::DropBomb; 3use drop_bomb::DropBomb;
4use ignore; 4use ignore::{gitignore::Gitignore, Walk};
5use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as NotifyWatcher}; 5use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as NotifyWatcher};
6use parking_lot::Mutex; 6use parking_lot::Mutex;
7use std::{ 7use std::{
@@ -40,8 +40,11 @@ fn handle_change_event(
40 sender.send(io::Task::HandleChange(WatcherChange::Rescan))?; 40 sender.send(io::Task::HandleChange(WatcherChange::Rescan))?;
41 } 41 }
42 DebouncedEvent::Create(path) => { 42 DebouncedEvent::Create(path) => {
43 if path.is_dir() { 43 // we have to check if `path` is ignored because Walk iterator doesn't check it
44 watch_recursive(watcher, &path); 44 // also childs are only ignored if they match a pattern
45 // (see `matched` vs `matched_path_or_any_parents` in `Gitignore`)
46 if path.is_dir() && !should_ignore_dir(&path) {
47 watch_recursive(watcher, &path, Some(sender));
45 } 48 }
46 sender.send(io::Task::HandleChange(WatcherChange::Create(path)))?; 49 sender.send(io::Task::HandleChange(WatcherChange::Create(path)))?;
47 } 50 }
@@ -63,15 +66,18 @@ fn handle_change_event(
63 Ok(()) 66 Ok(())
64} 67}
65 68
66fn watch_one(watcher: &mut RecommendedWatcher, path: &Path) { 69fn watch_one(watcher: &mut RecommendedWatcher, dir: &Path) {
67 match watcher.watch(path, RecursiveMode::NonRecursive) { 70 match watcher.watch(dir, RecursiveMode::NonRecursive) {
68 Ok(()) => log::debug!("watching \"{}\"", path.display()), 71 Ok(()) => log::debug!("watching \"{}\"", dir.display()),
69 Err(e) => log::warn!("could not watch \"{}\": {}", path.display(), e), 72 Err(e) => log::warn!("could not watch \"{}\": {}", dir.display(), e),
70 } 73 }
71} 74}
72 75
73fn watch_recursive(watcher: &Arc<Mutex<Option<RecommendedWatcher>>>, path: &Path) { 76fn watch_recursive(
74 log::debug!("watch_recursive \"{}\"", path.display()); 77 watcher: &Arc<Mutex<Option<RecommendedWatcher>>>,
78 dir: &Path,
79 sender: Option<&Sender<io::Task>>,
80) {
75 let mut watcher = watcher.lock(); 81 let mut watcher = watcher.lock();
76 let mut watcher = match *watcher { 82 let mut watcher = match *watcher {
77 Some(ref mut watcher) => watcher, 83 Some(ref mut watcher) => watcher,
@@ -80,20 +86,47 @@ fn watch_recursive(watcher: &Arc<Mutex<Option<RecommendedWatcher>>>, path: &Path
80 return; 86 return;
81 } 87 }
82 }; 88 };
83 // TODO it seems path itself isn't checked against ignores 89 for res in Walk::new(dir) {
84 // check if path should be ignored before walking it
85 for res in ignore::Walk::new(path) {
86 match res { 90 match res {
87 Ok(entry) => { 91 Ok(entry) => {
88 if entry.path().is_dir() { 92 if entry.path().is_dir() {
89 watch_one(&mut watcher, entry.path()); 93 watch_one(&mut watcher, entry.path());
90 } 94 }
95 if let Some(sender) = sender {
96 // emit as create because we haven't seen it yet
97 if let Err(e) = sender.send(io::Task::HandleChange(WatcherChange::Create(
98 entry.path().to_path_buf(),
99 ))) {
100 log::warn!("watcher error: {}", e)
101 }
102 }
91 } 103 }
92 Err(e) => log::warn!("watcher error: {}", e), 104 Err(e) => log::warn!("watcher error: {}", e),
93 } 105 }
94 } 106 }
95} 107}
96 108
109fn should_ignore_dir(dir: &Path) -> bool {
110 let mut parent = dir;
111 loop {
112 parent = match parent.parent() {
113 Some(p) => p,
114 None => break,
115 };
116 let gitignore = parent.join(".gitignore");
117 if gitignore.exists() {
118 let gitignore = Gitignore::new(gitignore).0;
119 if gitignore.matched_path_or_any_parents(dir, true).is_ignore() {
120 log::debug!("ignored {}", dir.display());
121 return true;
122 }
123 }
124 }
125 false
126}
127
128const WATCHER_DELAY: Duration = Duration::from_millis(250);
129
97impl Watcher { 130impl Watcher {
98 pub(crate) fn start( 131 pub(crate) fn start(
99 output_sender: Sender<io::Task>, 132 output_sender: Sender<io::Task>,
@@ -101,7 +134,7 @@ impl Watcher {
101 let (input_sender, input_receiver) = mpsc::channel(); 134 let (input_sender, input_receiver) = mpsc::channel();
102 let watcher = Arc::new(Mutex::new(Some(notify::watcher( 135 let watcher = Arc::new(Mutex::new(Some(notify::watcher(
103 input_sender, 136 input_sender,
104 Duration::from_millis(250), 137 WATCHER_DELAY,
105 )?))); 138 )?)));
106 let w = watcher.clone(); 139 let w = watcher.clone();
107 let thread = thread::spawn(move || { 140 let thread = thread::spawn(move || {
@@ -119,7 +152,7 @@ impl Watcher {
119 } 152 }
120 153
121 pub fn watch(&mut self, root: impl AsRef<Path>) { 154 pub fn watch(&mut self, root: impl AsRef<Path>) {
122 watch_recursive(&self.watcher, root.as_ref()); 155 watch_recursive(&self.watcher, root.as_ref(), None);
123 } 156 }
124 157
125 pub fn shutdown(mut self) -> thread::Result<()> { 158 pub fn shutdown(mut self) -> thread::Result<()> {
diff --git a/crates/ra_vfs/tests/vfs.rs b/crates/ra_vfs/tests/vfs.rs
index 8266a0bd5..71b25a5c9 100644
--- a/crates/ra_vfs/tests/vfs.rs
+++ b/crates/ra_vfs/tests/vfs.rs
@@ -11,9 +11,21 @@ fn process_tasks(vfs: &mut Vfs, num_tasks: u32) {
11 } 11 }
12} 12}
13 13
14macro_rules! assert_match {
15 ($x:expr, $pat:pat) => {
16 assert_match!($x, $pat, assert!(true))
17 };
18 ($x:expr, $pat:pat, $assert:expr) => {
19 match $x {
20 $pat => $assert,
21 x => assert!(false, "Expected {}, got {:?}", stringify!($pat), x),
22 };
23 };
24}
25
14#[test] 26#[test]
15fn test_vfs_works() -> std::io::Result<()> { 27fn test_vfs_works() -> std::io::Result<()> {
16 // Logger::with_str("debug").start().unwrap(); 28 // Logger::with_str("vfs=debug,ra_vfs=debug").start().unwrap();
17 29
18 let files = [ 30 let files = [
19 ("a/foo.rs", "hello"), 31 ("a/foo.rs", "hello"),
@@ -21,13 +33,16 @@ fn test_vfs_works() -> std::io::Result<()> {
21 ("a/b/baz.rs", "nested hello"), 33 ("a/b/baz.rs", "nested hello"),
22 ]; 34 ];
23 35
24 let dir = tempdir()?; 36 let dir = tempdir().unwrap();
25 for (path, text) in files.iter() { 37 for (path, text) in files.iter() {
26 let file_path = dir.path().join(path); 38 let file_path = dir.path().join(path);
27 fs::create_dir_all(file_path.parent().unwrap())?; 39 fs::create_dir_all(file_path.parent().unwrap()).unwrap();
28 fs::write(file_path, text)? 40 fs::write(file_path, text)?
29 } 41 }
30 42
43 let gitignore = dir.path().join("a/.gitignore");
44 fs::write(gitignore, "/target").unwrap();
45
31 let a_root = dir.path().join("a"); 46 let a_root = dir.path().join("a");
32 let b_root = dir.path().join("a/b"); 47 let b_root = dir.path().join("a/b");
33 48
@@ -62,79 +77,97 @@ fn test_vfs_works() -> std::io::Result<()> {
62 } 77 }
63 78
64 fs::write(&dir.path().join("a/b/baz.rs"), "quux").unwrap(); 79 fs::write(&dir.path().join("a/b/baz.rs"), "quux").unwrap();
65 // 2 tasks per watcher change, first for HandleChange then for LoadChange 80 // 2 tasks per change, HandleChange and then LoadChange
66 process_tasks(&mut vfs, 2); 81 process_tasks(&mut vfs, 2);
67 match vfs.commit_changes().as_slice() { 82 assert_match!(
68 [VfsChange::ChangeFile { text, .. }] => assert_eq!(text.as_str(), "quux"), 83 vfs.commit_changes().as_slice(),
69 xs => panic!("unexpected changes {:?}", xs), 84 [VfsChange::ChangeFile { text, .. }],
70 } 85 assert_eq!(text.as_str(), "quux")
86 );
71 87
72 vfs.change_file_overlay(&dir.path().join("a/b/baz.rs"), "m".to_string()); 88 vfs.change_file_overlay(&dir.path().join("a/b/baz.rs"), "m".to_string());
73 match vfs.commit_changes().as_slice() { 89 assert_match!(
74 [VfsChange::ChangeFile { text, .. }] => assert_eq!(text.as_str(), "m"), 90 vfs.commit_changes().as_slice(),
75 xs => panic!("unexpected changes {:?}", xs), 91 [VfsChange::ChangeFile { text, .. }],
76 } 92 assert_eq!(text.as_str(), "m")
93 );
77 94
78 // removing overlay restores data on disk 95 // removing overlay restores data on disk
79 vfs.remove_file_overlay(&dir.path().join("a/b/baz.rs")); 96 vfs.remove_file_overlay(&dir.path().join("a/b/baz.rs"));
80 match vfs.commit_changes().as_slice() { 97 assert_match!(
81 [VfsChange::ChangeFile { text, .. }] => assert_eq!(text.as_str(), "quux"), 98 vfs.commit_changes().as_slice(),
82 xs => panic!("unexpected changes {:?}", xs), 99 [VfsChange::ChangeFile { text, .. }],
83 } 100 assert_eq!(text.as_str(), "quux")
101 );
84 102
85 vfs.add_file_overlay(&dir.path().join("a/b/spam.rs"), "spam".to_string()); 103 vfs.add_file_overlay(&dir.path().join("a/b/spam.rs"), "spam".to_string());
86 match vfs.commit_changes().as_slice() { 104 assert_match!(
87 [VfsChange::AddFile { text, path, .. }] => { 105 vfs.commit_changes().as_slice(),
106 [VfsChange::AddFile { text, path, .. }],
107 {
88 assert_eq!(text.as_str(), "spam"); 108 assert_eq!(text.as_str(), "spam");
89 assert_eq!(path, "spam.rs"); 109 assert_eq!(path, "spam.rs");
90 } 110 }
91 xs => panic!("unexpected changes {:?}", xs), 111 );
92 }
93 112
94 vfs.remove_file_overlay(&dir.path().join("a/b/spam.rs")); 113 vfs.remove_file_overlay(&dir.path().join("a/b/spam.rs"));
95 match vfs.commit_changes().as_slice() { 114 assert_match!(
96 [VfsChange::RemoveFile { path, .. }] => assert_eq!(path, "spam.rs"), 115 vfs.commit_changes().as_slice(),
97 xs => panic!("unexpected changes {:?}", xs), 116 [VfsChange::RemoveFile { path, .. }],
98 } 117 assert_eq!(path, "spam.rs")
99 118 );
100 fs::write(&dir.path().join("a/new.rs"), "new hello").unwrap(); 119
101 process_tasks(&mut vfs, 2); 120 fs::create_dir_all(dir.path().join("a/c")).unwrap();
102 match vfs.commit_changes().as_slice() { 121 fs::write(dir.path().join("a/c/new.rs"), "new hello").unwrap();
103 [VfsChange::AddFile { text, path, .. }] => { 122 process_tasks(&mut vfs, 4);
123 assert_match!(
124 vfs.commit_changes().as_slice(),
125 [VfsChange::AddFile { text, path, .. }],
126 {
104 assert_eq!(text.as_str(), "new hello"); 127 assert_eq!(text.as_str(), "new hello");
105 assert_eq!(path, "new.rs"); 128 assert_eq!(path, "c/new.rs");
106 } 129 }
107 xs => panic!("unexpected changes {:?}", xs), 130 );
108 }
109 131
110 fs::rename(&dir.path().join("a/new.rs"), &dir.path().join("a/new1.rs")).unwrap(); 132 fs::rename(
133 &dir.path().join("a/c/new.rs"),
134 &dir.path().join("a/c/new1.rs"),
135 )
136 .unwrap();
111 process_tasks(&mut vfs, 4); 137 process_tasks(&mut vfs, 4);
112 match vfs.commit_changes().as_slice() { 138 assert_match!(
139 vfs.commit_changes().as_slice(),
113 [VfsChange::RemoveFile { 140 [VfsChange::RemoveFile {
114 path: removed_path, .. 141 path: removed_path, ..
115 }, VfsChange::AddFile { 142 }, VfsChange::AddFile {
116 text, 143 text,
117 path: added_path, 144 path: added_path,
118 .. 145 ..
119 }] => { 146 }],
120 assert_eq!(removed_path, "new.rs"); 147 {
121 assert_eq!(added_path, "new1.rs"); 148 assert_eq!(removed_path, "c/new.rs");
149 assert_eq!(added_path, "c/new1.rs");
122 assert_eq!(text.as_str(), "new hello"); 150 assert_eq!(text.as_str(), "new hello");
123 } 151 }
124 xs => panic!("unexpected changes {:?}", xs), 152 );
125 }
126 153
127 fs::remove_file(&dir.path().join("a/new1.rs")).unwrap(); 154 fs::remove_file(&dir.path().join("a/c/new1.rs")).unwrap();
128 process_tasks(&mut vfs, 2); 155 process_tasks(&mut vfs, 2);
129 match vfs.commit_changes().as_slice() { 156 assert_match!(
130 [VfsChange::RemoveFile { path, .. }] => assert_eq!(path, "new1.rs"), 157 vfs.commit_changes().as_slice(),
131 xs => panic!("unexpected changes {:?}", xs), 158 [VfsChange::RemoveFile { path, .. }],
132 } 159 assert_eq!(path, "c/new1.rs")
133 160 );
134 match vfs.task_receiver().try_recv() { 161
135 Err(crossbeam_channel::TryRecvError::Empty) => (), 162 fs::create_dir_all(dir.path().join("a/target")).unwrap();
136 res => panic!("unexpected {:?}", res), 163 // should be ignored
137 } 164 fs::write(&dir.path().join("a/target/new.rs"), "ignore me").unwrap();
165 process_tasks(&mut vfs, 1); // 1 task because no LoadChange will happen, just HandleChange for dir creation
166
167 assert_match!(
168 vfs.task_receiver().try_recv(),
169 Err(crossbeam_channel::TryRecvError::Empty)
170 );
138 171
139 vfs.shutdown().unwrap(); 172 vfs.shutdown().unwrap();
140 Ok(()) 173 Ok(())