1mod char_bag;
2mod fuzzy;
3mod ignore;
4
5use crate::{
6 editor::{History, Snapshot as BufferSnapshot},
7 sum_tree::{self, Cursor, Edit, SeekBias, SumTree},
8};
9use ::ignore::gitignore::Gitignore;
10use anyhow::{Context, Result};
11pub use fuzzy::{match_paths, PathMatch};
12use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task};
13use lazy_static::lazy_static;
14use parking_lot::Mutex;
15use postage::{
16 prelude::{Sink, Stream},
17 watch,
18};
19use smol::channel::Sender;
20use std::{
21 cmp,
22 collections::{HashMap, HashSet},
23 ffi::{CStr, OsStr, OsString},
24 fmt, fs,
25 future::Future,
26 io::{self, Read, Write},
27 ops::Deref,
28 os::unix::{ffi::OsStrExt, fs::MetadataExt},
29 path::{Path, PathBuf},
30 sync::{Arc, Weak},
31 time::{Duration, SystemTime, UNIX_EPOCH},
32};
33
34use self::{char_bag::CharBag, ignore::IgnoreStack};
35
36lazy_static! {
37 static ref GITIGNORE: &'static OsStr = OsStr::new(".gitignore");
38}
39
40#[derive(Clone, Debug)]
41enum ScanState {
42 Idle,
43 Scanning,
44 Err(Arc<io::Error>),
45}
46
47pub struct Worktree {
48 snapshot: Snapshot,
49 background_snapshot: Arc<Mutex<Snapshot>>,
50 handles: Arc<Mutex<HashMap<Arc<Path>, Weak<Mutex<FileHandleState>>>>>,
51 scan_state: (watch::Sender<ScanState>, watch::Receiver<ScanState>),
52 _event_stream_handle: fsevent::Handle,
53 poll_scheduled: bool,
54}
55
56#[derive(Clone, Debug)]
57pub struct FileHandle {
58 worktree: ModelHandle<Worktree>,
59 state: Arc<Mutex<FileHandleState>>,
60}
61
62#[derive(Clone, Debug, PartialEq, Eq)]
63struct FileHandleState {
64 path: Arc<Path>,
65 is_deleted: bool,
66 mtime: SystemTime,
67}
68
69impl Worktree {
70 pub fn new(path: impl Into<Arc<Path>>, ctx: &mut ModelContext<Self>) -> Self {
71 let abs_path = path.into();
72 let (scan_state_tx, scan_state_rx) = smol::channel::unbounded();
73 let id = ctx.model_id();
74 let snapshot = Snapshot {
75 id,
76 scan_id: 0,
77 abs_path,
78 root_name: Default::default(),
79 ignores: Default::default(),
80 entries: Default::default(),
81 };
82 let (event_stream, event_stream_handle) =
83 fsevent::EventStream::new(&[snapshot.abs_path.as_ref()], Duration::from_millis(100));
84
85 let background_snapshot = Arc::new(Mutex::new(snapshot.clone()));
86 let handles = Arc::new(Mutex::new(Default::default()));
87
88 let tree = Self {
89 snapshot,
90 background_snapshot: background_snapshot.clone(),
91 handles: handles.clone(),
92 scan_state: watch::channel_with(ScanState::Scanning),
93 _event_stream_handle: event_stream_handle,
94 poll_scheduled: false,
95 };
96
97 std::thread::spawn(move || {
98 let scanner = BackgroundScanner::new(background_snapshot, handles, scan_state_tx, id);
99 scanner.run(event_stream)
100 });
101
102 ctx.spawn(|this, mut ctx| {
103 let this = this.downgrade();
104 async move {
105 while let Ok(scan_state) = scan_state_rx.recv().await {
106 let alive = ctx.update(|ctx| {
107 if let Some(handle) = this.upgrade(&ctx) {
108 handle
109 .update(ctx, |this, ctx| this.observe_scan_state(scan_state, ctx));
110 true
111 } else {
112 false
113 }
114 });
115
116 if !alive {
117 break;
118 }
119 }
120 }
121 })
122 .detach();
123
124 tree
125 }
126
127 pub fn scan_complete(&self) -> impl Future<Output = ()> {
128 let mut scan_state_rx = self.scan_state.1.clone();
129 async move {
130 let mut scan_state = Some(scan_state_rx.borrow().clone());
131 while let Some(ScanState::Scanning) = scan_state {
132 scan_state = scan_state_rx.recv().await;
133 }
134 }
135 }
136
137 pub fn next_scan_complete(&self, ctx: &mut ModelContext<Self>) -> impl Future<Output = ()> {
138 let scan_id = self.snapshot.scan_id;
139 let mut scan_state = self.scan_state.1.clone();
140 ctx.spawn(|this, ctx| async move {
141 while let Some(scan_state) = scan_state.recv().await {
142 if this.read_with(&ctx, |this, _| {
143 matches!(scan_state, ScanState::Idle) && this.snapshot.scan_id > scan_id
144 }) {
145 break;
146 }
147 }
148 })
149 }
150
151 fn observe_scan_state(&mut self, scan_state: ScanState, ctx: &mut ModelContext<Self>) {
152 let _ = self.scan_state.0.blocking_send(scan_state);
153 self.poll_entries(ctx);
154 }
155
156 fn poll_entries(&mut self, ctx: &mut ModelContext<Self>) {
157 self.snapshot = self.background_snapshot.lock().clone();
158 ctx.notify();
159
160 if self.is_scanning() && !self.poll_scheduled {
161 ctx.spawn(|this, mut ctx| async move {
162 this.update(&mut ctx, |this, ctx| {
163 this.poll_scheduled = false;
164 this.poll_entries(ctx);
165 })
166 })
167 .detach();
168 self.poll_scheduled = true;
169 }
170 }
171
172 fn is_scanning(&self) -> bool {
173 if let ScanState::Scanning = *self.scan_state.1.borrow() {
174 true
175 } else {
176 false
177 }
178 }
179
180 pub fn snapshot(&self) -> Snapshot {
181 self.snapshot.clone()
182 }
183
184 pub fn abs_path(&self) -> &Path {
185 self.snapshot.abs_path.as_ref()
186 }
187
188 pub fn contains_abs_path(&self, path: &Path) -> bool {
189 path.starts_with(&self.snapshot.abs_path)
190 }
191
192 fn absolutize(&self, path: &Path) -> PathBuf {
193 if path.file_name().is_some() {
194 self.snapshot.abs_path.join(path)
195 } else {
196 self.snapshot.abs_path.to_path_buf()
197 }
198 }
199
200 pub fn load_history(
201 &self,
202 path: &Path,
203 ctx: &AppContext,
204 ) -> impl Future<Output = Result<History>> {
205 let path = path.to_path_buf();
206 let abs_path = self.absolutize(&path);
207 ctx.background_executor().spawn(async move {
208 let mut file = fs::File::open(&abs_path)?;
209 let mut base_text = String::new();
210 file.read_to_string(&mut base_text)?;
211 Ok(History::new(Arc::from(base_text)))
212 })
213 }
214
215 pub fn save<'a>(
216 &self,
217 path: &Path,
218 content: BufferSnapshot,
219 ctx: &AppContext,
220 ) -> Task<Result<()>> {
221 let handles = self.handles.clone();
222 let path = path.to_path_buf();
223 let abs_path = self.absolutize(&path);
224 ctx.background_executor().spawn(async move {
225 let buffer_size = content.text_summary().bytes.min(10 * 1024);
226 let file = fs::File::create(&abs_path)?;
227 let mut writer = io::BufWriter::with_capacity(buffer_size, &file);
228 for chunk in content.fragments() {
229 writer.write(chunk.as_bytes())?;
230 }
231 writer.flush()?;
232
233 if let Some(handle) = handles.lock().get(&*path).and_then(Weak::upgrade) {
234 let mut handle = handle.lock();
235 handle.mtime = file.metadata()?.modified()?;
236 handle.is_deleted = false;
237 }
238
239 Ok(())
240 })
241 }
242}
243
244impl Entity for Worktree {
245 type Event = ();
246}
247
248impl Deref for Worktree {
249 type Target = Snapshot;
250
251 fn deref(&self) -> &Self::Target {
252 &self.snapshot
253 }
254}
255
256impl fmt::Debug for Worktree {
257 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258 self.snapshot.fmt(f)
259 }
260}
261
262#[derive(Clone)]
263pub struct Snapshot {
264 id: usize,
265 scan_id: usize,
266 abs_path: Arc<Path>,
267 root_name: String,
268 ignores: HashMap<Arc<Path>, (Arc<Gitignore>, usize)>,
269 entries: SumTree<Entry>,
270}
271
272impl Snapshot {
273 pub fn file_count(&self) -> usize {
274 self.entries.summary().file_count
275 }
276
277 pub fn visible_file_count(&self) -> usize {
278 self.entries.summary().visible_file_count
279 }
280
281 pub fn files(&self, start: usize) -> FileIter {
282 FileIter::all(self, start)
283 }
284
285 #[cfg(test)]
286 pub fn paths(&self) -> impl Iterator<Item = &Arc<Path>> {
287 self.entries
288 .cursor::<(), ()>()
289 .skip(1)
290 .map(|entry| entry.path())
291 }
292
293 pub fn visible_files(&self, start: usize) -> FileIter {
294 FileIter::visible(self, start)
295 }
296
297 fn child_entries<'a>(&'a self, path: &'a Path) -> ChildEntriesIter<'a> {
298 ChildEntriesIter::new(path, self)
299 }
300
301 pub fn root_entry(&self) -> &Entry {
302 self.entry_for_path("").unwrap()
303 }
304
305 /// Returns the filename of the snapshot's root, plus a trailing slash if the snapshot's root is
306 /// a directory.
307 pub fn root_name(&self) -> &str {
308 &self.root_name
309 }
310
311 fn path_is_pending(&self, path: impl AsRef<Path>) -> bool {
312 if self.entries.is_empty() {
313 return true;
314 }
315 let path = path.as_ref();
316 let mut cursor = self.entries.cursor::<_, ()>();
317 if cursor.seek(&PathSearch::Exact(path), SeekBias::Left, &()) {
318 let entry = cursor.item().unwrap();
319 if entry.path.as_ref() == path {
320 return matches!(entry.kind, EntryKind::PendingDir);
321 }
322 }
323 if let Some(entry) = cursor.prev_item() {
324 matches!(entry.kind, EntryKind::PendingDir) && path.starts_with(entry.path.as_ref())
325 } else {
326 false
327 }
328 }
329
330 fn entry_for_path(&self, path: impl AsRef<Path>) -> Option<&Entry> {
331 let mut cursor = self.entries.cursor::<_, ()>();
332 if cursor.seek(&PathSearch::Exact(path.as_ref()), SeekBias::Left, &()) {
333 cursor.item()
334 } else {
335 None
336 }
337 }
338
339 pub fn inode_for_path(&self, path: impl AsRef<Path>) -> Option<u64> {
340 self.entry_for_path(path.as_ref()).map(|e| e.inode())
341 }
342
343 fn insert_entry(&mut self, entry: Entry) {
344 if !entry.is_dir() && entry.path().file_name() == Some(&GITIGNORE) {
345 let (ignore, err) = Gitignore::new(self.abs_path.join(entry.path()));
346 if let Some(err) = err {
347 log::error!("error in ignore file {:?} - {:?}", entry.path(), err);
348 }
349
350 let ignore_dir_path = entry.path().parent().unwrap();
351 self.ignores
352 .insert(ignore_dir_path.into(), (Arc::new(ignore), self.scan_id));
353 }
354 self.entries.insert(entry, &());
355 }
356
357 fn populate_dir(
358 &mut self,
359 parent_path: Arc<Path>,
360 entries: impl IntoIterator<Item = Entry>,
361 ignore: Option<Arc<Gitignore>>,
362 ) {
363 let mut edits = Vec::new();
364
365 let mut parent_entry = self
366 .entries
367 .get(&PathKey(parent_path.clone()), &())
368 .unwrap()
369 .clone();
370 if let Some(ignore) = ignore {
371 self.ignores.insert(parent_path, (ignore, self.scan_id));
372 }
373 if matches!(parent_entry.kind, EntryKind::PendingDir) {
374 parent_entry.kind = EntryKind::Dir;
375 } else {
376 unreachable!();
377 }
378 edits.push(Edit::Insert(parent_entry));
379
380 for entry in entries {
381 edits.push(Edit::Insert(entry));
382 }
383 self.entries.edit(edits, &());
384 }
385
386 fn remove_path(&mut self, path: &Path) {
387 let new_entries = {
388 let mut cursor = self.entries.cursor::<_, ()>();
389 let mut new_entries = cursor.slice(&PathSearch::Exact(path), SeekBias::Left, &());
390 cursor.seek_forward(&PathSearch::Successor(path), SeekBias::Left, &());
391 new_entries.push_tree(cursor.suffix(&()), &());
392 new_entries
393 };
394 self.entries = new_entries;
395
396 if path.file_name() == Some(&GITIGNORE) {
397 if let Some((_, scan_id)) = self.ignores.get_mut(path.parent().unwrap()) {
398 *scan_id = self.scan_id;
399 }
400 }
401 }
402
403 fn ignore_stack_for_path(&self, path: &Path, is_dir: bool) -> Arc<IgnoreStack> {
404 let mut new_ignores = Vec::new();
405 for ancestor in path.ancestors().skip(1) {
406 if let Some((ignore, _)) = self.ignores.get(ancestor) {
407 new_ignores.push((ancestor, Some(ignore.clone())));
408 } else {
409 new_ignores.push((ancestor, None));
410 }
411 }
412
413 let mut ignore_stack = IgnoreStack::none();
414 for (parent_path, ignore) in new_ignores.into_iter().rev() {
415 if ignore_stack.is_path_ignored(&parent_path, true) {
416 ignore_stack = IgnoreStack::all();
417 break;
418 } else if let Some(ignore) = ignore {
419 ignore_stack = ignore_stack.append(Arc::from(parent_path), ignore);
420 }
421 }
422
423 if ignore_stack.is_path_ignored(path, is_dir) {
424 ignore_stack = IgnoreStack::all();
425 }
426
427 ignore_stack
428 }
429}
430
431impl fmt::Debug for Snapshot {
432 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
433 for entry in self.entries.cursor::<(), ()>() {
434 for _ in entry.path().ancestors().skip(1) {
435 write!(f, " ")?;
436 }
437 writeln!(f, "{:?} (inode: {})", entry.path(), entry.inode())?;
438 }
439 Ok(())
440 }
441}
442
443impl FileHandle {
444 /// Returns this file's path relative to the root of its worktree.
445 pub fn path(&self) -> Arc<Path> {
446 self.state.lock().path.clone()
447 }
448
449 /// Returns the last component of this handle's absolute path. If this handle refers to the root
450 /// of its worktree, then this method will return the name of the worktree itself.
451 pub fn file_name<'a>(&'a self, ctx: &'a AppContext) -> Option<OsString> {
452 self.state
453 .lock()
454 .path
455 .file_name()
456 .or_else(|| self.worktree.read(ctx).abs_path().file_name())
457 .map(Into::into)
458 }
459
460 pub fn is_deleted(&self) -> bool {
461 self.state.lock().is_deleted
462 }
463
464 pub fn mtime(&self) -> SystemTime {
465 self.state.lock().mtime
466 }
467
468 pub fn exists(&self) -> bool {
469 !self.is_deleted()
470 }
471
472 pub fn load_history(&self, ctx: &AppContext) -> impl Future<Output = Result<History>> {
473 self.worktree.read(ctx).load_history(&self.path(), ctx)
474 }
475
476 pub fn save<'a>(&self, content: BufferSnapshot, ctx: &AppContext) -> Task<Result<()>> {
477 let worktree = self.worktree.read(ctx);
478 worktree.save(&self.path(), content, ctx)
479 }
480
481 pub fn worktree_id(&self) -> usize {
482 self.worktree.id()
483 }
484
485 pub fn entry_id(&self) -> (usize, Arc<Path>) {
486 (self.worktree.id(), self.path())
487 }
488
489 pub fn observe_from_model<T: Entity>(
490 &self,
491 ctx: &mut ModelContext<T>,
492 mut callback: impl FnMut(&mut T, FileHandle, &mut ModelContext<T>) + 'static,
493 ) {
494 let mut prev_state = self.state.lock().clone();
495 let cur_state = Arc::downgrade(&self.state);
496 ctx.observe(&self.worktree, move |observer, worktree, ctx| {
497 if let Some(cur_state) = cur_state.upgrade() {
498 let cur_state_unlocked = cur_state.lock();
499 if *cur_state_unlocked != prev_state {
500 prev_state = cur_state_unlocked.clone();
501 drop(cur_state_unlocked);
502 callback(
503 observer,
504 FileHandle {
505 worktree,
506 state: cur_state,
507 },
508 ctx,
509 );
510 }
511 }
512 });
513 }
514}
515
516#[derive(Clone, Debug)]
517pub struct Entry {
518 kind: EntryKind,
519 path: Arc<Path>,
520 inode: u64,
521 is_symlink: bool,
522 is_ignored: bool,
523}
524
525#[derive(Clone, Debug)]
526pub enum EntryKind {
527 PendingDir,
528 Dir,
529 File(CharBag),
530}
531
532impl Entry {
533 pub fn path(&self) -> &Arc<Path> {
534 &self.path
535 }
536
537 pub fn inode(&self) -> u64 {
538 self.inode
539 }
540
541 pub fn is_ignored(&self) -> bool {
542 self.is_ignored
543 }
544
545 fn is_dir(&self) -> bool {
546 matches!(self.kind, EntryKind::Dir | EntryKind::PendingDir)
547 }
548
549 fn is_file(&self) -> bool {
550 matches!(self.kind, EntryKind::File(_))
551 }
552}
553
554impl sum_tree::Item for Entry {
555 type Summary = EntrySummary;
556
557 fn summary(&self) -> Self::Summary {
558 let file_count;
559 let visible_file_count;
560 if self.is_file() {
561 file_count = 1;
562 if self.is_ignored {
563 visible_file_count = 0;
564 } else {
565 visible_file_count = 1;
566 }
567 } else {
568 file_count = 0;
569 visible_file_count = 0;
570 }
571
572 EntrySummary {
573 max_path: self.path().clone(),
574 file_count,
575 visible_file_count,
576 }
577 }
578}
579
580impl sum_tree::KeyedItem for Entry {
581 type Key = PathKey;
582
583 fn key(&self) -> Self::Key {
584 PathKey(self.path().clone())
585 }
586}
587
588#[derive(Clone, Debug)]
589pub struct EntrySummary {
590 max_path: Arc<Path>,
591 file_count: usize,
592 visible_file_count: usize,
593}
594
595impl Default for EntrySummary {
596 fn default() -> Self {
597 Self {
598 max_path: Arc::from(Path::new("")),
599 file_count: 0,
600 visible_file_count: 0,
601 }
602 }
603}
604
605impl sum_tree::Summary for EntrySummary {
606 type Context = ();
607
608 fn add_summary(&mut self, rhs: &Self, _: &()) {
609 self.max_path = rhs.max_path.clone();
610 self.file_count += rhs.file_count;
611 self.visible_file_count += rhs.visible_file_count;
612 }
613}
614
615#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
616pub struct PathKey(Arc<Path>);
617
618impl Default for PathKey {
619 fn default() -> Self {
620 Self(Path::new("").into())
621 }
622}
623
624impl<'a> sum_tree::Dimension<'a, EntrySummary> for PathKey {
625 fn add_summary(&mut self, summary: &'a EntrySummary) {
626 self.0 = summary.max_path.clone();
627 }
628}
629
630#[derive(Copy, Clone, Debug, PartialEq, Eq)]
631enum PathSearch<'a> {
632 Exact(&'a Path),
633 Successor(&'a Path),
634}
635
636impl<'a> Ord for PathSearch<'a> {
637 fn cmp(&self, other: &Self) -> cmp::Ordering {
638 match (self, other) {
639 (Self::Exact(a), Self::Exact(b)) => a.cmp(b),
640 (Self::Successor(a), Self::Exact(b)) => {
641 if b.starts_with(a) {
642 cmp::Ordering::Greater
643 } else {
644 a.cmp(b)
645 }
646 }
647 _ => todo!("not sure we need the other two cases"),
648 }
649 }
650}
651
652impl<'a> PartialOrd for PathSearch<'a> {
653 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
654 Some(self.cmp(other))
655 }
656}
657
658impl<'a> Default for PathSearch<'a> {
659 fn default() -> Self {
660 Self::Exact(Path::new("").into())
661 }
662}
663
664impl<'a: 'b, 'b> sum_tree::Dimension<'a, EntrySummary> for PathSearch<'b> {
665 fn add_summary(&mut self, summary: &'a EntrySummary) {
666 *self = Self::Exact(summary.max_path.as_ref());
667 }
668}
669
670#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd)]
671pub struct FileCount(usize);
672
673impl<'a> sum_tree::Dimension<'a, EntrySummary> for FileCount {
674 fn add_summary(&mut self, summary: &'a EntrySummary) {
675 self.0 += summary.file_count;
676 }
677}
678
679#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd)]
680pub struct VisibleFileCount(usize);
681
682impl<'a> sum_tree::Dimension<'a, EntrySummary> for VisibleFileCount {
683 fn add_summary(&mut self, summary: &'a EntrySummary) {
684 self.0 += summary.visible_file_count;
685 }
686}
687
688struct BackgroundScanner {
689 snapshot: Arc<Mutex<Snapshot>>,
690 notify: Sender<ScanState>,
691 handles: Arc<Mutex<HashMap<Arc<Path>, Weak<Mutex<FileHandleState>>>>>,
692 other_mount_paths: HashSet<PathBuf>,
693 thread_pool: scoped_pool::Pool,
694 root_char_bag: CharBag,
695}
696
697impl BackgroundScanner {
698 fn new(
699 snapshot: Arc<Mutex<Snapshot>>,
700 handles: Arc<Mutex<HashMap<Arc<Path>, Weak<Mutex<FileHandleState>>>>>,
701 notify: Sender<ScanState>,
702 worktree_id: usize,
703 ) -> Self {
704 let mut scanner = Self {
705 root_char_bag: Default::default(),
706 snapshot,
707 notify,
708 handles,
709 other_mount_paths: Default::default(),
710 thread_pool: scoped_pool::Pool::new(16, format!("worktree-{}-scanner", worktree_id)),
711 };
712 scanner.update_other_mount_paths();
713 scanner
714 }
715
716 fn update_other_mount_paths(&mut self) {
717 let path = self.snapshot.lock().abs_path.clone();
718 self.other_mount_paths.clear();
719 self.other_mount_paths.extend(
720 mounted_volume_paths()
721 .into_iter()
722 .filter(|mount_path| !path.starts_with(mount_path)),
723 );
724 }
725
726 fn abs_path(&self) -> Arc<Path> {
727 self.snapshot.lock().abs_path.clone()
728 }
729
730 fn snapshot(&self) -> Snapshot {
731 self.snapshot.lock().clone()
732 }
733
734 fn run(mut self, event_stream: fsevent::EventStream) {
735 if smol::block_on(self.notify.send(ScanState::Scanning)).is_err() {
736 return;
737 }
738
739 if let Err(err) = self.scan_dirs() {
740 if smol::block_on(self.notify.send(ScanState::Err(Arc::new(err)))).is_err() {
741 return;
742 }
743 }
744
745 if smol::block_on(self.notify.send(ScanState::Idle)).is_err() {
746 return;
747 }
748
749 event_stream.run(move |events| {
750 if smol::block_on(self.notify.send(ScanState::Scanning)).is_err() {
751 return false;
752 }
753
754 if !self.process_events(events) {
755 return false;
756 }
757
758 if smol::block_on(self.notify.send(ScanState::Idle)).is_err() {
759 return false;
760 }
761
762 true
763 });
764 }
765
766 fn scan_dirs(&mut self) -> io::Result<()> {
767 self.snapshot.lock().scan_id += 1;
768
769 let path: Arc<Path> = Arc::from(Path::new(""));
770 let abs_path = self.abs_path();
771 let metadata = fs::metadata(&abs_path)?;
772 let inode = metadata.ino();
773 let is_symlink = fs::symlink_metadata(&abs_path)?.file_type().is_symlink();
774 let is_dir = metadata.file_type().is_dir();
775
776 // After determining whether the root entry is a file or a directory, populate the
777 // snapshot's "root name", which will be used for the purpose of fuzzy matching.
778 let mut root_name = abs_path
779 .file_name()
780 .map_or(String::new(), |f| f.to_string_lossy().to_string());
781 if is_dir {
782 root_name.push('/');
783 }
784 self.root_char_bag = root_name.chars().map(|c| c.to_ascii_lowercase()).collect();
785 self.snapshot.lock().root_name = root_name;
786
787 if is_dir {
788 self.snapshot.lock().insert_entry(Entry {
789 kind: EntryKind::PendingDir,
790 path: path.clone(),
791 inode,
792 is_symlink,
793 is_ignored: false,
794 });
795
796 let (tx, rx) = crossbeam_channel::unbounded();
797 tx.send(ScanJob {
798 abs_path: abs_path.to_path_buf(),
799 path,
800 ignore_stack: IgnoreStack::none(),
801 scan_queue: tx.clone(),
802 })
803 .unwrap();
804 drop(tx);
805
806 self.thread_pool.scoped(|pool| {
807 for _ in 0..self.thread_pool.thread_count() {
808 pool.execute(|| {
809 while let Ok(job) = rx.recv() {
810 if let Err(err) = self.scan_dir(&job) {
811 log::error!("error scanning {:?}: {}", job.abs_path, err);
812 }
813 }
814 });
815 }
816 });
817 } else {
818 self.snapshot.lock().insert_entry(Entry {
819 kind: EntryKind::File(self.char_bag(&path)),
820 path,
821 inode,
822 is_symlink,
823 is_ignored: false,
824 });
825 }
826
827 self.mark_deleted_file_handles();
828 Ok(())
829 }
830
831 fn scan_dir(&self, job: &ScanJob) -> io::Result<()> {
832 let mut new_entries: Vec<Entry> = Vec::new();
833 let mut new_jobs: Vec<ScanJob> = Vec::new();
834 let mut ignore_stack = job.ignore_stack.clone();
835 let mut new_ignore = None;
836
837 for child_entry in fs::read_dir(&job.abs_path)? {
838 let child_entry = child_entry?;
839 let child_name = child_entry.file_name();
840 let child_abs_path = job.abs_path.join(&child_name);
841 let child_path: Arc<Path> = job.path.join(&child_name).into();
842 let child_is_symlink = child_entry.metadata()?.file_type().is_symlink();
843 let child_metadata = if let Ok(metadata) = fs::metadata(&child_abs_path) {
844 metadata
845 } else {
846 log::error!("could not get metadata for path {:?}", child_abs_path);
847 continue;
848 };
849
850 let child_inode = child_metadata.ino();
851
852 // Disallow mount points outside the file system containing the root of this worktree
853 if self.other_mount_paths.contains(&child_abs_path) {
854 continue;
855 }
856
857 // If we find a .gitignore, add it to the stack of ignores used to determine which paths are ignored
858 if child_name == *GITIGNORE {
859 let (ignore, err) = Gitignore::new(&child_abs_path);
860 if let Some(err) = err {
861 log::error!("error in ignore file {:?} - {:?}", child_path, err);
862 }
863 let ignore = Arc::new(ignore);
864 ignore_stack = ignore_stack.append(job.path.clone(), ignore.clone());
865 new_ignore = Some(ignore);
866
867 // Update ignore status of any child entries we've already processed to reflect the
868 // ignore file in the current directory. Because `.gitignore` starts with a `.`,
869 // there should rarely be too numerous. Update the ignore stack associated with any
870 // new jobs as well.
871 let mut new_jobs = new_jobs.iter_mut();
872 for entry in &mut new_entries {
873 entry.is_ignored = ignore_stack.is_path_ignored(&entry.path, entry.is_dir());
874 if entry.is_dir() {
875 new_jobs.next().unwrap().ignore_stack = if entry.is_ignored {
876 IgnoreStack::all()
877 } else {
878 ignore_stack.clone()
879 };
880 }
881 }
882 }
883
884 if child_metadata.is_dir() {
885 let is_ignored = ignore_stack.is_path_ignored(&child_path, true);
886 new_entries.push(Entry {
887 kind: EntryKind::PendingDir,
888 path: child_path.clone(),
889 inode: child_inode,
890 is_symlink: child_is_symlink,
891 is_ignored,
892 });
893 new_jobs.push(ScanJob {
894 abs_path: child_abs_path,
895 path: child_path,
896 ignore_stack: if is_ignored {
897 IgnoreStack::all()
898 } else {
899 ignore_stack.clone()
900 },
901 scan_queue: job.scan_queue.clone(),
902 });
903 } else {
904 let is_ignored = ignore_stack.is_path_ignored(&child_path, false);
905 new_entries.push(Entry {
906 kind: EntryKind::File(self.char_bag(&child_path)),
907 path: child_path,
908 inode: child_inode,
909 is_symlink: child_is_symlink,
910 is_ignored,
911 });
912 };
913 }
914
915 self.snapshot
916 .lock()
917 .populate_dir(job.path.clone(), new_entries, new_ignore);
918 for new_job in new_jobs {
919 job.scan_queue.send(new_job).unwrap();
920 }
921
922 Ok(())
923 }
924
925 fn process_events(&mut self, mut events: Vec<fsevent::Event>) -> bool {
926 self.update_other_mount_paths();
927
928 let mut snapshot = self.snapshot();
929 snapshot.scan_id += 1;
930
931 let root_abs_path = if let Ok(abs_path) = snapshot.abs_path.canonicalize() {
932 abs_path
933 } else {
934 return false;
935 };
936
937 let mut renamed_paths: HashMap<u64, PathBuf> = HashMap::new();
938 let mut handles = self.handles.lock();
939 let mut updated_handles = HashMap::new();
940 for event in &events {
941 let path = if let Ok(path) = event.path.strip_prefix(&root_abs_path) {
942 path
943 } else {
944 continue;
945 };
946
947 let metadata = fs::metadata(&event.path);
948 if event.flags.contains(fsevent::StreamFlags::ITEM_RENAMED) {
949 if let Some(inode) = snapshot.inode_for_path(path) {
950 renamed_paths.insert(inode, path.to_path_buf());
951 } else if let Ok(metadata) = &metadata {
952 let new_path = path;
953 if let Some(old_path) = renamed_paths.get(&metadata.ino()) {
954 handles.retain(|handle_path, handle_state| {
955 if let Ok(path_suffix) = handle_path.strip_prefix(&old_path) {
956 let new_handle_path: Arc<Path> =
957 if path_suffix.file_name().is_some() {
958 new_path.join(path_suffix)
959 } else {
960 new_path.to_path_buf()
961 }
962 .into();
963 if let Some(handle_state) = Weak::upgrade(&handle_state) {
964 let mut state = handle_state.lock();
965 state.path = new_handle_path.clone();
966 updated_handles
967 .insert(new_handle_path, Arc::downgrade(&handle_state));
968 }
969 false
970 } else {
971 true
972 }
973 });
974 handles.extend(updated_handles.drain());
975 }
976 }
977 }
978
979 for state in handles.values_mut() {
980 if let Some(state) = Weak::upgrade(&state) {
981 let mut state = state.lock();
982 if state.path.as_ref() == path {
983 if let Ok(metadata) = &metadata {
984 state.mtime = metadata.modified().unwrap();
985 }
986 } else if state.path.starts_with(path) {
987 if let Ok(metadata) = fs::metadata(state.path.as_ref()) {
988 state.mtime = metadata.modified().unwrap();
989 }
990 }
991 }
992 }
993 }
994 drop(handles);
995
996 events.sort_unstable_by(|a, b| a.path.cmp(&b.path));
997 let mut abs_paths = events.into_iter().map(|e| e.path).peekable();
998 let (scan_queue_tx, scan_queue_rx) = crossbeam_channel::unbounded();
999
1000 while let Some(abs_path) = abs_paths.next() {
1001 let path = match abs_path.strip_prefix(&root_abs_path) {
1002 Ok(path) => Arc::from(path.to_path_buf()),
1003 Err(_) => {
1004 log::error!(
1005 "unexpected event {:?} for root path {:?}",
1006 abs_path,
1007 root_abs_path
1008 );
1009 continue;
1010 }
1011 };
1012
1013 while abs_paths.peek().map_or(false, |p| p.starts_with(&abs_path)) {
1014 abs_paths.next();
1015 }
1016
1017 snapshot.remove_path(&path);
1018
1019 match self.fs_entry_for_path(path.clone(), &abs_path) {
1020 Ok(Some(mut fs_entry)) => {
1021 let is_dir = fs_entry.is_dir();
1022 let ignore_stack = snapshot.ignore_stack_for_path(&path, is_dir);
1023 fs_entry.is_ignored = ignore_stack.is_all();
1024 snapshot.insert_entry(fs_entry);
1025 if is_dir {
1026 scan_queue_tx
1027 .send(ScanJob {
1028 abs_path,
1029 path,
1030 ignore_stack,
1031 scan_queue: scan_queue_tx.clone(),
1032 })
1033 .unwrap();
1034 }
1035 }
1036 Ok(None) => {}
1037 Err(err) => {
1038 // TODO - create a special 'error' entry in the entries tree to mark this
1039 log::error!("error reading file on event {:?}", err);
1040 }
1041 }
1042 }
1043
1044 *self.snapshot.lock() = snapshot;
1045
1046 // Scan any directories that were created as part of this event batch.
1047 drop(scan_queue_tx);
1048 self.thread_pool.scoped(|pool| {
1049 for _ in 0..self.thread_pool.thread_count() {
1050 pool.execute(|| {
1051 while let Ok(job) = scan_queue_rx.recv() {
1052 if let Err(err) = self.scan_dir(&job) {
1053 log::error!("error scanning {:?}: {}", job.abs_path, err);
1054 }
1055 }
1056 });
1057 }
1058 });
1059
1060 self.update_ignore_statuses();
1061 self.mark_deleted_file_handles();
1062 true
1063 }
1064
1065 fn update_ignore_statuses(&self) {
1066 let mut snapshot = self.snapshot();
1067
1068 let mut ignores_to_update = Vec::new();
1069 let mut ignores_to_delete = Vec::new();
1070 for (parent_path, (_, scan_id)) in &snapshot.ignores {
1071 if *scan_id == snapshot.scan_id && snapshot.entry_for_path(parent_path).is_some() {
1072 ignores_to_update.push(parent_path.clone());
1073 }
1074
1075 let ignore_path = parent_path.join(&*GITIGNORE);
1076 if snapshot.entry_for_path(ignore_path).is_none() {
1077 ignores_to_delete.push(parent_path.clone());
1078 }
1079 }
1080
1081 for parent_path in ignores_to_delete {
1082 snapshot.ignores.remove(&parent_path);
1083 self.snapshot.lock().ignores.remove(&parent_path);
1084 }
1085
1086 let (ignore_queue_tx, ignore_queue_rx) = crossbeam_channel::unbounded();
1087 ignores_to_update.sort_unstable();
1088 let mut ignores_to_update = ignores_to_update.into_iter().peekable();
1089 while let Some(parent_path) = ignores_to_update.next() {
1090 while ignores_to_update
1091 .peek()
1092 .map_or(false, |p| p.starts_with(&parent_path))
1093 {
1094 ignores_to_update.next().unwrap();
1095 }
1096
1097 let ignore_stack = snapshot.ignore_stack_for_path(&parent_path, true);
1098 ignore_queue_tx
1099 .send(UpdateIgnoreStatusJob {
1100 path: parent_path,
1101 ignore_stack,
1102 ignore_queue: ignore_queue_tx.clone(),
1103 })
1104 .unwrap();
1105 }
1106 drop(ignore_queue_tx);
1107
1108 self.thread_pool.scoped(|scope| {
1109 for _ in 0..self.thread_pool.thread_count() {
1110 scope.execute(|| {
1111 while let Ok(job) = ignore_queue_rx.recv() {
1112 self.update_ignore_status(job, &snapshot);
1113 }
1114 });
1115 }
1116 });
1117 }
1118
1119 fn update_ignore_status(&self, job: UpdateIgnoreStatusJob, snapshot: &Snapshot) {
1120 let mut ignore_stack = job.ignore_stack;
1121 if let Some((ignore, _)) = snapshot.ignores.get(&job.path) {
1122 ignore_stack = ignore_stack.append(job.path.clone(), ignore.clone());
1123 }
1124
1125 let mut edits = Vec::new();
1126 for mut entry in snapshot.child_entries(&job.path).cloned() {
1127 let was_ignored = entry.is_ignored;
1128 entry.is_ignored = ignore_stack.is_path_ignored(entry.path(), entry.is_dir());
1129 if entry.is_dir() {
1130 let child_ignore_stack = if entry.is_ignored {
1131 IgnoreStack::all()
1132 } else {
1133 ignore_stack.clone()
1134 };
1135 job.ignore_queue
1136 .send(UpdateIgnoreStatusJob {
1137 path: entry.path().clone(),
1138 ignore_stack: child_ignore_stack,
1139 ignore_queue: job.ignore_queue.clone(),
1140 })
1141 .unwrap();
1142 }
1143
1144 if entry.is_ignored != was_ignored {
1145 edits.push(Edit::Insert(entry));
1146 }
1147 }
1148 self.snapshot.lock().entries.edit(edits, &());
1149 }
1150
1151 fn mark_deleted_file_handles(&self) {
1152 let mut handles = self.handles.lock();
1153 let snapshot = self.snapshot.lock();
1154 handles.retain(|path, handle_state| {
1155 if let Some(handle_state) = Weak::upgrade(&handle_state) {
1156 let mut handle_state = handle_state.lock();
1157 handle_state.is_deleted = snapshot.entry_for_path(&path).is_none();
1158 true
1159 } else {
1160 false
1161 }
1162 });
1163 }
1164
1165 fn fs_entry_for_path(&self, path: Arc<Path>, abs_path: &Path) -> Result<Option<Entry>> {
1166 let metadata = match fs::metadata(&abs_path) {
1167 Err(err) => {
1168 return match (err.kind(), err.raw_os_error()) {
1169 (io::ErrorKind::NotFound, _) => Ok(None),
1170 (io::ErrorKind::Other, Some(libc::ENOTDIR)) => Ok(None),
1171 _ => Err(anyhow::Error::new(err)),
1172 }
1173 }
1174 Ok(metadata) => metadata,
1175 };
1176 let inode = metadata.ino();
1177 let is_symlink = fs::symlink_metadata(&abs_path)
1178 .context("failed to read symlink metadata")?
1179 .file_type()
1180 .is_symlink();
1181
1182 let entry = Entry {
1183 kind: if metadata.file_type().is_dir() {
1184 EntryKind::PendingDir
1185 } else {
1186 EntryKind::File(self.char_bag(&path))
1187 },
1188 path,
1189 inode,
1190 is_symlink,
1191 is_ignored: false,
1192 };
1193
1194 Ok(Some(entry))
1195 }
1196
1197 fn char_bag(&self, path: &Path) -> CharBag {
1198 let mut result = self.root_char_bag;
1199 result.extend(
1200 path.to_string_lossy()
1201 .chars()
1202 .map(|c| c.to_ascii_lowercase()),
1203 );
1204 result
1205 }
1206}
1207
1208struct ScanJob {
1209 abs_path: PathBuf,
1210 path: Arc<Path>,
1211 ignore_stack: Arc<IgnoreStack>,
1212 scan_queue: crossbeam_channel::Sender<ScanJob>,
1213}
1214
1215struct UpdateIgnoreStatusJob {
1216 path: Arc<Path>,
1217 ignore_stack: Arc<IgnoreStack>,
1218 ignore_queue: crossbeam_channel::Sender<UpdateIgnoreStatusJob>,
1219}
1220
1221pub trait WorktreeHandle {
1222 fn file(&self, path: impl AsRef<Path>, app: &mut MutableAppContext) -> Task<FileHandle>;
1223
1224 #[cfg(test)]
1225 fn flush_fs_events<'a>(
1226 &self,
1227 app: &'a gpui::TestAppContext,
1228 ) -> futures_core::future::LocalBoxFuture<'a, ()>;
1229}
1230
1231impl WorktreeHandle for ModelHandle<Worktree> {
1232 fn file(&self, path: impl AsRef<Path>, app: &mut MutableAppContext) -> Task<FileHandle> {
1233 let path = Arc::from(path.as_ref());
1234 let handle = self.clone();
1235 let tree = self.read(app);
1236 let abs_path = tree.absolutize(&path);
1237 app.spawn(|ctx| async move {
1238 let mtime = ctx
1239 .background_executor()
1240 .spawn(async move {
1241 if let Ok(metadata) = fs::metadata(&abs_path) {
1242 metadata.modified().unwrap()
1243 } else {
1244 UNIX_EPOCH
1245 }
1246 })
1247 .await;
1248 let state = handle.read_with(&ctx, |tree, _| {
1249 let mut handles = tree.handles.lock();
1250 if let Some(state) = handles.get(&path).and_then(Weak::upgrade) {
1251 state
1252 } else {
1253 let handle_state = if let Some(entry) = tree.entry_for_path(&path) {
1254 FileHandleState {
1255 path: entry.path().clone(),
1256 is_deleted: false,
1257 mtime,
1258 }
1259 } else {
1260 FileHandleState {
1261 path: path.clone(),
1262 is_deleted: !tree.path_is_pending(path),
1263 mtime,
1264 }
1265 };
1266
1267 let state = Arc::new(Mutex::new(handle_state.clone()));
1268 handles.insert(handle_state.path, Arc::downgrade(&state));
1269 state
1270 }
1271 });
1272 FileHandle {
1273 worktree: handle.clone(),
1274 state,
1275 }
1276 })
1277 }
1278
1279 // When the worktree's FS event stream sometimes delivers "redundant" events for FS changes that
1280 // occurred before the worktree was constructed. These events can cause the worktree to perfrom
1281 // extra directory scans, and emit extra scan-state notifications.
1282 //
1283 // This function mutates the worktree's directory and waits for those mutations to be picked up,
1284 // to ensure that all redundant FS events have already been processed.
1285 #[cfg(test)]
1286 fn flush_fs_events<'a>(
1287 &self,
1288 app: &'a gpui::TestAppContext,
1289 ) -> futures_core::future::LocalBoxFuture<'a, ()> {
1290 use smol::future::FutureExt;
1291
1292 let filename = "fs-event-sentinel";
1293 let root_path = app.read(|ctx| self.read(ctx).abs_path.clone());
1294 let tree = self.clone();
1295 async move {
1296 fs::write(root_path.join(filename), "").unwrap();
1297 tree.condition_with_duration(Duration::from_secs(5), &app, |tree, _| {
1298 tree.entry_for_path(filename).is_some()
1299 })
1300 .await;
1301
1302 fs::remove_file(root_path.join(filename)).unwrap();
1303 tree.condition_with_duration(Duration::from_secs(5), &app, |tree, _| {
1304 tree.entry_for_path(filename).is_none()
1305 })
1306 .await;
1307
1308 app.read(|ctx| tree.read(ctx).scan_complete()).await;
1309 }
1310 .boxed_local()
1311 }
1312}
1313
1314pub enum FileIter<'a> {
1315 All(Cursor<'a, Entry, FileCount, FileCount>),
1316 Visible(Cursor<'a, Entry, VisibleFileCount, VisibleFileCount>),
1317}
1318
1319impl<'a> FileIter<'a> {
1320 fn all(snapshot: &'a Snapshot, start: usize) -> Self {
1321 let mut cursor = snapshot.entries.cursor();
1322 cursor.seek(&FileCount(start), SeekBias::Right, &());
1323 Self::All(cursor)
1324 }
1325
1326 fn visible(snapshot: &'a Snapshot, start: usize) -> Self {
1327 let mut cursor = snapshot.entries.cursor();
1328 cursor.seek(&VisibleFileCount(start), SeekBias::Right, &());
1329 Self::Visible(cursor)
1330 }
1331
1332 fn next_internal(&mut self) {
1333 match self {
1334 Self::All(cursor) => {
1335 let ix = *cursor.start();
1336 cursor.seek_forward(&FileCount(ix.0 + 1), SeekBias::Right, &());
1337 }
1338 Self::Visible(cursor) => {
1339 let ix = *cursor.start();
1340 cursor.seek_forward(&VisibleFileCount(ix.0 + 1), SeekBias::Right, &());
1341 }
1342 }
1343 }
1344
1345 fn item(&self) -> Option<&'a Entry> {
1346 match self {
1347 Self::All(cursor) => cursor.item(),
1348 Self::Visible(cursor) => cursor.item(),
1349 }
1350 }
1351}
1352
1353impl<'a> Iterator for FileIter<'a> {
1354 type Item = &'a Entry;
1355
1356 fn next(&mut self) -> Option<Self::Item> {
1357 if let Some(entry) = self.item() {
1358 self.next_internal();
1359 Some(entry)
1360 } else {
1361 None
1362 }
1363 }
1364}
1365
1366struct ChildEntriesIter<'a> {
1367 parent_path: &'a Path,
1368 cursor: Cursor<'a, Entry, PathSearch<'a>, ()>,
1369}
1370
1371impl<'a> ChildEntriesIter<'a> {
1372 fn new(parent_path: &'a Path, snapshot: &'a Snapshot) -> Self {
1373 let mut cursor = snapshot.entries.cursor();
1374 cursor.seek(&PathSearch::Exact(parent_path), SeekBias::Right, &());
1375 Self {
1376 parent_path,
1377 cursor,
1378 }
1379 }
1380}
1381
1382impl<'a> Iterator for ChildEntriesIter<'a> {
1383 type Item = &'a Entry;
1384
1385 fn next(&mut self) -> Option<Self::Item> {
1386 if let Some(item) = self.cursor.item() {
1387 if item.path().starts_with(self.parent_path) {
1388 self.cursor
1389 .seek_forward(&PathSearch::Successor(item.path()), SeekBias::Left, &());
1390 Some(item)
1391 } else {
1392 None
1393 }
1394 } else {
1395 None
1396 }
1397 }
1398}
1399
1400fn mounted_volume_paths() -> Vec<PathBuf> {
1401 unsafe {
1402 let mut stat_ptr: *mut libc::statfs = std::ptr::null_mut();
1403 let count = libc::getmntinfo(&mut stat_ptr as *mut _, libc::MNT_WAIT);
1404 if count >= 0 {
1405 std::slice::from_raw_parts(stat_ptr, count as usize)
1406 .iter()
1407 .map(|stat| {
1408 PathBuf::from(OsStr::from_bytes(
1409 CStr::from_ptr(&stat.f_mntonname[0]).to_bytes(),
1410 ))
1411 })
1412 .collect()
1413 } else {
1414 panic!("failed to run getmntinfo");
1415 }
1416 }
1417}
1418
1419#[cfg(test)]
1420mod tests {
1421 use super::*;
1422 use crate::editor::Buffer;
1423 use crate::test::*;
1424 use anyhow::Result;
1425 use rand::prelude::*;
1426 use serde_json::json;
1427 use std::env;
1428 use std::fmt::Write;
1429 use std::os::unix;
1430 use std::time::{SystemTime, UNIX_EPOCH};
1431
1432 #[gpui::test]
1433 async fn test_populate_and_search(mut app: gpui::TestAppContext) {
1434 let dir = temp_tree(json!({
1435 "root": {
1436 "apple": "",
1437 "banana": {
1438 "carrot": {
1439 "date": "",
1440 "endive": "",
1441 }
1442 },
1443 "fennel": {
1444 "grape": "",
1445 }
1446 }
1447 }));
1448
1449 let root_link_path = dir.path().join("root_link");
1450 unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
1451 unix::fs::symlink(
1452 &dir.path().join("root/fennel"),
1453 &dir.path().join("root/finnochio"),
1454 )
1455 .unwrap();
1456
1457 let tree = app.add_model(|ctx| Worktree::new(root_link_path, ctx));
1458
1459 app.read(|ctx| tree.read(ctx).scan_complete()).await;
1460 app.read(|ctx| {
1461 let tree = tree.read(ctx);
1462 assert_eq!(tree.file_count(), 5);
1463
1464 assert_eq!(
1465 tree.inode_for_path("fennel/grape"),
1466 tree.inode_for_path("finnochio/grape")
1467 );
1468
1469 let results = match_paths(
1470 Some(tree.snapshot()).iter(),
1471 "bna",
1472 false,
1473 false,
1474 false,
1475 10,
1476 Default::default(),
1477 ctx.thread_pool().clone(),
1478 )
1479 .into_iter()
1480 .map(|result| result.path)
1481 .collect::<Vec<Arc<Path>>>();
1482 assert_eq!(
1483 results,
1484 vec![
1485 PathBuf::from("banana/carrot/date").into(),
1486 PathBuf::from("banana/carrot/endive").into(),
1487 ]
1488 );
1489 })
1490 }
1491
1492 #[gpui::test]
1493 async fn test_save_file(mut app: gpui::TestAppContext) {
1494 let dir = temp_tree(json!({
1495 "file1": "the old contents",
1496 }));
1497
1498 let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
1499 app.read(|ctx| tree.read(ctx).scan_complete()).await;
1500 app.read(|ctx| assert_eq!(tree.read(ctx).file_count(), 1));
1501
1502 let buffer =
1503 app.add_model(|ctx| Buffer::new(1, "a line of text.\n".repeat(10 * 1024), ctx));
1504
1505 let path = tree.update(&mut app, |tree, ctx| {
1506 let path = tree.files(0).next().unwrap().path().clone();
1507 assert_eq!(path.file_name().unwrap(), "file1");
1508 smol::block_on(tree.save(&path, buffer.read(ctx).snapshot(), ctx.as_ref())).unwrap();
1509 path
1510 });
1511
1512 let history = app
1513 .read(|ctx| tree.read(ctx).load_history(&path, ctx))
1514 .await
1515 .unwrap();
1516 app.read(|ctx| {
1517 assert_eq!(history.base_text.as_ref(), buffer.read(ctx).text());
1518 });
1519 }
1520
1521 #[gpui::test]
1522 async fn test_save_in_single_file_worktree(mut app: gpui::TestAppContext) {
1523 let dir = temp_tree(json!({
1524 "file1": "the old contents",
1525 }));
1526
1527 let tree = app.add_model(|ctx| Worktree::new(dir.path().join("file1"), ctx));
1528 app.read(|ctx| tree.read(ctx).scan_complete()).await;
1529 app.read(|ctx| assert_eq!(tree.read(ctx).file_count(), 1));
1530
1531 let buffer =
1532 app.add_model(|ctx| Buffer::new(1, "a line of text.\n".repeat(10 * 1024), ctx));
1533
1534 let file = app.update(|ctx| tree.file("", ctx)).await;
1535 app.update(|ctx| {
1536 assert_eq!(file.path().file_name(), None);
1537 smol::block_on(file.save(buffer.read(ctx).snapshot(), ctx.as_ref())).unwrap();
1538 });
1539
1540 let history = app.read(|ctx| file.load_history(ctx)).await.unwrap();
1541 app.read(|ctx| assert_eq!(history.base_text.as_ref(), buffer.read(ctx).text()));
1542 }
1543
1544 #[gpui::test]
1545 async fn test_rescan_simple(mut app: gpui::TestAppContext) {
1546 let dir = temp_tree(json!({
1547 "a": {
1548 "file1": "",
1549 "file2": "",
1550 "file3": "",
1551 },
1552 "b": {
1553 "c": {
1554 "file4": "",
1555 "file5": "",
1556 }
1557 }
1558 }));
1559
1560 let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
1561 let file2 = app.update(|ctx| tree.file("a/file2", ctx)).await;
1562 let file3 = app.update(|ctx| tree.file("a/file3", ctx)).await;
1563 let file4 = app.update(|ctx| tree.file("b/c/file4", ctx)).await;
1564 let file5 = app.update(|ctx| tree.file("b/c/file5", ctx)).await;
1565 let non_existent_file = app.update(|ctx| tree.file("a/file_x", ctx)).await;
1566
1567 // After scanning, the worktree knows which files exist and which don't.
1568 app.read(|ctx| tree.read(ctx).scan_complete()).await;
1569 assert!(!file2.is_deleted());
1570 assert!(!file3.is_deleted());
1571 assert!(!file4.is_deleted());
1572 assert!(!file5.is_deleted());
1573 assert!(non_existent_file.is_deleted());
1574
1575 tree.flush_fs_events(&app).await;
1576 std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap();
1577 std::fs::remove_file(dir.path().join("b/c/file5")).unwrap();
1578 std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
1579 std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap();
1580 tree.flush_fs_events(&app).await;
1581
1582 app.read(|ctx| {
1583 assert_eq!(
1584 tree.read(ctx)
1585 .paths()
1586 .map(|p| p.to_str().unwrap())
1587 .collect::<Vec<_>>(),
1588 vec![
1589 "a",
1590 "a/file1",
1591 "a/file2.new",
1592 "b",
1593 "d",
1594 "d/file3",
1595 "d/file4"
1596 ]
1597 );
1598
1599 assert_eq!(file2.path().to_str().unwrap(), "a/file2.new");
1600 assert_eq!(file4.path().as_ref(), Path::new("d/file4"));
1601 assert_eq!(file5.path().as_ref(), Path::new("d/file5"));
1602 assert!(!file2.is_deleted());
1603 assert!(!file4.is_deleted());
1604 assert!(file5.is_deleted());
1605
1606 // Right now, this rename isn't detected because the target path
1607 // no longer exists on the file system by the time we process the
1608 // rename event.
1609 assert_eq!(file3.path().as_ref(), Path::new("a/file3"));
1610 assert!(file3.is_deleted());
1611 });
1612 }
1613
1614 #[gpui::test]
1615 async fn test_rescan_with_gitignore(mut app: gpui::TestAppContext) {
1616 let dir = temp_tree(json!({
1617 ".git": {},
1618 ".gitignore": "ignored-dir\n",
1619 "tracked-dir": {
1620 "tracked-file1": "tracked contents",
1621 },
1622 "ignored-dir": {
1623 "ignored-file1": "ignored contents",
1624 }
1625 }));
1626
1627 let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
1628 app.read(|ctx| tree.read(ctx).scan_complete()).await;
1629 tree.flush_fs_events(&app).await;
1630 app.read(|ctx| {
1631 let tree = tree.read(ctx);
1632 let tracked = tree.entry_for_path("tracked-dir/tracked-file1").unwrap();
1633 let ignored = tree.entry_for_path("ignored-dir/ignored-file1").unwrap();
1634 assert_eq!(tracked.is_ignored(), false);
1635 assert_eq!(ignored.is_ignored(), true);
1636 });
1637
1638 fs::write(dir.path().join("tracked-dir/tracked-file2"), "").unwrap();
1639 fs::write(dir.path().join("ignored-dir/ignored-file2"), "").unwrap();
1640 tree.flush_fs_events(&app).await;
1641 app.read(|ctx| {
1642 let tree = tree.read(ctx);
1643 let dot_git = tree.entry_for_path(".git").unwrap();
1644 let tracked = tree.entry_for_path("tracked-dir/tracked-file2").unwrap();
1645 let ignored = tree.entry_for_path("ignored-dir/ignored-file2").unwrap();
1646 assert_eq!(tracked.is_ignored(), false);
1647 assert_eq!(ignored.is_ignored(), true);
1648 assert_eq!(dot_git.is_ignored(), true);
1649 });
1650 }
1651
1652 #[test]
1653 fn test_path_is_pending() {
1654 let mut snapshot = Snapshot {
1655 id: 0,
1656 scan_id: 0,
1657 abs_path: Path::new("").into(),
1658 entries: Default::default(),
1659 ignores: Default::default(),
1660 root_name: Default::default(),
1661 };
1662
1663 snapshot.entries.edit(
1664 vec![
1665 Edit::Insert(Entry {
1666 path: Path::new("b").into(),
1667 kind: EntryKind::Dir,
1668 inode: 0,
1669 is_ignored: false,
1670 is_symlink: false,
1671 }),
1672 Edit::Insert(Entry {
1673 path: Path::new("b/a").into(),
1674 kind: EntryKind::Dir,
1675 inode: 0,
1676 is_ignored: false,
1677 is_symlink: false,
1678 }),
1679 Edit::Insert(Entry {
1680 path: Path::new("b/c").into(),
1681 kind: EntryKind::PendingDir,
1682 inode: 0,
1683 is_ignored: false,
1684 is_symlink: false,
1685 }),
1686 Edit::Insert(Entry {
1687 path: Path::new("b/e").into(),
1688 kind: EntryKind::Dir,
1689 inode: 0,
1690 is_ignored: false,
1691 is_symlink: false,
1692 }),
1693 ],
1694 &(),
1695 );
1696
1697 assert!(!snapshot.path_is_pending("b/a"));
1698 assert!(!snapshot.path_is_pending("b/b"));
1699 assert!(snapshot.path_is_pending("b/c"));
1700 assert!(snapshot.path_is_pending("b/c/x"));
1701 assert!(!snapshot.path_is_pending("b/d"));
1702 assert!(!snapshot.path_is_pending("b/e"));
1703 }
1704
1705 #[test]
1706 fn test_mounted_volume_paths() {
1707 let paths = mounted_volume_paths();
1708 assert!(paths.contains(&"/".into()));
1709 }
1710
1711 #[test]
1712 fn test_random() {
1713 let iterations = env::var("ITERATIONS")
1714 .map(|i| i.parse().unwrap())
1715 .unwrap_or(100);
1716 let operations = env::var("OPERATIONS")
1717 .map(|o| o.parse().unwrap())
1718 .unwrap_or(40);
1719 let initial_entries = env::var("INITIAL_ENTRIES")
1720 .map(|o| o.parse().unwrap())
1721 .unwrap_or(20);
1722 let seeds = if let Ok(seed) = env::var("SEED").map(|s| s.parse().unwrap()) {
1723 seed..seed + 1
1724 } else {
1725 0..iterations
1726 };
1727
1728 for seed in seeds {
1729 dbg!(seed);
1730 let mut rng = StdRng::seed_from_u64(seed);
1731
1732 let root_dir = tempdir::TempDir::new(&format!("test-{}", seed)).unwrap();
1733 for _ in 0..initial_entries {
1734 randomly_mutate_tree(root_dir.path(), 1.0, &mut rng).unwrap();
1735 }
1736 log::info!("Generated initial tree");
1737
1738 let (notify_tx, _notify_rx) = smol::channel::unbounded();
1739 let mut scanner = BackgroundScanner::new(
1740 Arc::new(Mutex::new(Snapshot {
1741 id: 0,
1742 scan_id: 0,
1743 abs_path: root_dir.path().into(),
1744 entries: Default::default(),
1745 ignores: Default::default(),
1746 root_name: Default::default(),
1747 })),
1748 Arc::new(Mutex::new(Default::default())),
1749 notify_tx,
1750 0,
1751 );
1752 scanner.scan_dirs().unwrap();
1753 scanner.snapshot().check_invariants();
1754
1755 let mut events = Vec::new();
1756 let mut mutations_len = operations;
1757 while mutations_len > 1 {
1758 if !events.is_empty() && rng.gen_bool(0.4) {
1759 let len = rng.gen_range(0..=events.len());
1760 let to_deliver = events.drain(0..len).collect::<Vec<_>>();
1761 log::info!("Delivering events: {:#?}", to_deliver);
1762 scanner.process_events(to_deliver);
1763 scanner.snapshot().check_invariants();
1764 } else {
1765 events.extend(randomly_mutate_tree(root_dir.path(), 0.6, &mut rng).unwrap());
1766 mutations_len -= 1;
1767 }
1768 }
1769 log::info!("Quiescing: {:#?}", events);
1770 scanner.process_events(events);
1771 scanner.snapshot().check_invariants();
1772
1773 let (notify_tx, _notify_rx) = smol::channel::unbounded();
1774 let mut new_scanner = BackgroundScanner::new(
1775 Arc::new(Mutex::new(Snapshot {
1776 id: 0,
1777 scan_id: 0,
1778 abs_path: root_dir.path().into(),
1779 entries: Default::default(),
1780 ignores: Default::default(),
1781 root_name: Default::default(),
1782 })),
1783 Arc::new(Mutex::new(Default::default())),
1784 notify_tx,
1785 1,
1786 );
1787 new_scanner.scan_dirs().unwrap();
1788 assert_eq!(scanner.snapshot().to_vec(), new_scanner.snapshot().to_vec());
1789 }
1790 }
1791
1792 fn randomly_mutate_tree(
1793 root_path: &Path,
1794 insertion_probability: f64,
1795 rng: &mut impl Rng,
1796 ) -> Result<Vec<fsevent::Event>> {
1797 let root_path = root_path.canonicalize().unwrap();
1798 let (dirs, files) = read_dir_recursive(root_path.clone());
1799
1800 let mut events = Vec::new();
1801 let mut record_event = |path: PathBuf| {
1802 events.push(fsevent::Event {
1803 event_id: SystemTime::now()
1804 .duration_since(UNIX_EPOCH)
1805 .unwrap()
1806 .as_secs(),
1807 flags: fsevent::StreamFlags::empty(),
1808 path,
1809 });
1810 };
1811
1812 if (files.is_empty() && dirs.len() == 1) || rng.gen_bool(insertion_probability) {
1813 let path = dirs.choose(rng).unwrap();
1814 let new_path = path.join(gen_name(rng));
1815
1816 if rng.gen() {
1817 log::info!("Creating dir {:?}", new_path.strip_prefix(root_path)?);
1818 fs::create_dir(&new_path)?;
1819 } else {
1820 log::info!("Creating file {:?}", new_path.strip_prefix(root_path)?);
1821 fs::write(&new_path, "")?;
1822 }
1823 record_event(new_path);
1824 } else if rng.gen_bool(0.05) {
1825 let ignore_dir_path = dirs.choose(rng).unwrap();
1826 let ignore_path = ignore_dir_path.join(&*GITIGNORE);
1827
1828 let (subdirs, subfiles) = read_dir_recursive(ignore_dir_path.clone());
1829 let files_to_ignore = {
1830 let len = rng.gen_range(0..=subfiles.len());
1831 subfiles.choose_multiple(rng, len)
1832 };
1833 let dirs_to_ignore = {
1834 let len = rng.gen_range(0..subdirs.len());
1835 subdirs.choose_multiple(rng, len)
1836 };
1837
1838 let mut ignore_contents = String::new();
1839 for path_to_ignore in files_to_ignore.chain(dirs_to_ignore) {
1840 write!(
1841 ignore_contents,
1842 "{}\n",
1843 path_to_ignore
1844 .strip_prefix(&ignore_dir_path)?
1845 .to_str()
1846 .unwrap()
1847 )
1848 .unwrap();
1849 }
1850 log::info!(
1851 "Creating {:?} with contents:\n{}",
1852 ignore_path.strip_prefix(&root_path)?,
1853 ignore_contents
1854 );
1855 fs::write(&ignore_path, ignore_contents).unwrap();
1856 record_event(ignore_path);
1857 } else {
1858 let old_path = {
1859 let file_path = files.choose(rng);
1860 let dir_path = dirs[1..].choose(rng);
1861 file_path.into_iter().chain(dir_path).choose(rng).unwrap()
1862 };
1863
1864 let is_rename = rng.gen();
1865 if is_rename {
1866 let new_path_parent = dirs
1867 .iter()
1868 .filter(|d| !d.starts_with(old_path))
1869 .choose(rng)
1870 .unwrap();
1871
1872 let overwrite_existing_dir =
1873 !old_path.starts_with(&new_path_parent) && rng.gen_bool(0.3);
1874 let new_path = if overwrite_existing_dir {
1875 fs::remove_dir_all(&new_path_parent).ok();
1876 new_path_parent.to_path_buf()
1877 } else {
1878 new_path_parent.join(gen_name(rng))
1879 };
1880
1881 log::info!(
1882 "Renaming {:?} to {}{:?}",
1883 old_path.strip_prefix(&root_path)?,
1884 if overwrite_existing_dir {
1885 "overwrite "
1886 } else {
1887 ""
1888 },
1889 new_path.strip_prefix(&root_path)?
1890 );
1891 fs::rename(&old_path, &new_path)?;
1892 record_event(old_path.clone());
1893 record_event(new_path);
1894 } else if old_path.is_dir() {
1895 let (dirs, files) = read_dir_recursive(old_path.clone());
1896
1897 log::info!("Deleting dir {:?}", old_path.strip_prefix(&root_path)?);
1898 fs::remove_dir_all(&old_path).unwrap();
1899 for file in files {
1900 record_event(file);
1901 }
1902 for dir in dirs {
1903 record_event(dir);
1904 }
1905 } else {
1906 log::info!("Deleting file {:?}", old_path.strip_prefix(&root_path)?);
1907 fs::remove_file(old_path).unwrap();
1908 record_event(old_path.clone());
1909 }
1910 }
1911
1912 Ok(events)
1913 }
1914
1915 fn read_dir_recursive(path: PathBuf) -> (Vec<PathBuf>, Vec<PathBuf>) {
1916 let child_entries = fs::read_dir(&path).unwrap();
1917 let mut dirs = vec![path];
1918 let mut files = Vec::new();
1919 for child_entry in child_entries {
1920 let child_path = child_entry.unwrap().path();
1921 if child_path.is_dir() {
1922 let (child_dirs, child_files) = read_dir_recursive(child_path);
1923 dirs.extend(child_dirs);
1924 files.extend(child_files);
1925 } else {
1926 files.push(child_path);
1927 }
1928 }
1929 (dirs, files)
1930 }
1931
1932 fn gen_name(rng: &mut impl Rng) -> String {
1933 (0..6)
1934 .map(|_| rng.sample(rand::distributions::Alphanumeric))
1935 .map(char::from)
1936 .collect()
1937 }
1938
1939 impl Snapshot {
1940 fn check_invariants(&self) {
1941 let mut files = self.files(0);
1942 let mut visible_files = self.visible_files(0);
1943 for entry in self.entries.cursor::<(), ()>() {
1944 if entry.is_file() {
1945 assert_eq!(files.next().unwrap().inode(), entry.inode);
1946 if !entry.is_ignored {
1947 assert_eq!(visible_files.next().unwrap().inode(), entry.inode);
1948 }
1949 }
1950 }
1951 assert!(files.next().is_none());
1952 assert!(visible_files.next().is_none());
1953
1954 let mut bfs_paths = Vec::new();
1955 let mut stack = vec![Path::new("")];
1956 while let Some(path) = stack.pop() {
1957 bfs_paths.push(path);
1958 let ix = stack.len();
1959 for child_entry in self.child_entries(path) {
1960 stack.insert(ix, child_entry.path());
1961 }
1962 }
1963
1964 let dfs_paths = self
1965 .entries
1966 .cursor::<(), ()>()
1967 .map(|e| e.path().as_ref())
1968 .collect::<Vec<_>>();
1969 assert_eq!(bfs_paths, dfs_paths);
1970
1971 for (ignore_parent_path, _) in &self.ignores {
1972 assert!(self.entry_for_path(ignore_parent_path).is_some());
1973 assert!(self
1974 .entry_for_path(ignore_parent_path.join(&*GITIGNORE))
1975 .is_some());
1976 }
1977 }
1978
1979 fn to_vec(&self) -> Vec<(&Path, u64, bool)> {
1980 let mut paths = Vec::new();
1981 for entry in self.entries.cursor::<(), ()>() {
1982 paths.push((entry.path().as_ref(), entry.inode(), entry.is_ignored()));
1983 }
1984 paths.sort_by(|a, b| a.0.cmp(&b.0));
1985 paths
1986 }
1987 }
1988}