1mod char_bag;
2mod fuzzy;
3
4use crate::{
5 editor::{History, Snapshot as BufferSnapshot},
6 sum_tree::{self, Edit, SumTree},
7};
8use anyhow::{anyhow, Result};
9pub use fuzzy::match_paths;
10use fuzzy::PathEntry;
11use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, Task};
12use ignore::dir::{Ignore, IgnoreBuilder};
13use parking_lot::Mutex;
14use smol::{channel::Sender, Timer};
15use std::future::Future;
16use std::{
17 ffi::OsStr,
18 fmt, fs,
19 io::{self, Read, Write},
20 ops::{AddAssign, Deref},
21 os::unix::fs::MetadataExt,
22 path::{Path, PathBuf},
23 sync::Arc,
24 time::Duration,
25};
26
27pub use fuzzy::PathMatch;
28
29#[derive(Debug)]
30enum ScanState {
31 Idle,
32 Scanning,
33 Err(io::Error),
34}
35
36pub struct Worktree {
37 snapshot: Snapshot,
38 scanner: Arc<BackgroundScanner>,
39 scan_state: ScanState,
40 poll_scheduled: bool,
41}
42
43#[derive(Clone)]
44pub struct FileHandle {
45 worktree: ModelHandle<Worktree>,
46 inode: u64,
47}
48
49impl Worktree {
50 pub fn new(path: impl Into<Arc<Path>>, ctx: &mut ModelContext<Self>) -> Self {
51 let scan_state = smol::channel::unbounded();
52 let snapshot = Snapshot {
53 id: ctx.model_id(),
54 path: path.into(),
55 root_inode: None,
56 entries: Default::default(),
57 };
58 let scanner = Arc::new(BackgroundScanner::new(snapshot.clone(), scan_state.0));
59 let tree = Self {
60 snapshot,
61 scanner,
62 scan_state: ScanState::Idle,
63 poll_scheduled: false,
64 };
65
66 let scanner = tree.scanner.clone();
67 std::thread::spawn(move || scanner.run());
68
69 ctx.spawn_stream(scan_state.1, Self::observe_scan_state, |_, _| {})
70 .detach();
71
72 tree
73 }
74
75 fn observe_scan_state(&mut self, scan_state: ScanState, ctx: &mut ModelContext<Self>) {
76 self.scan_state = scan_state;
77 self.poll_entries(ctx);
78 }
79
80 fn poll_entries(&mut self, ctx: &mut ModelContext<Self>) {
81 self.snapshot = self.scanner.snapshot();
82 ctx.notify();
83
84 if self.is_scanning() && !self.poll_scheduled {
85 ctx.spawn(Timer::after(Duration::from_millis(100)), |this, _, ctx| {
86 this.poll_scheduled = false;
87 this.poll_entries(ctx);
88 })
89 .detach();
90 self.poll_scheduled = true;
91 }
92 }
93
94 fn is_scanning(&self) -> bool {
95 if let ScanState::Scanning = self.scan_state {
96 true
97 } else {
98 false
99 }
100 }
101
102 pub fn snapshot(&self) -> Snapshot {
103 self.snapshot.clone()
104 }
105
106 pub fn contains_path(&self, path: &Path) -> bool {
107 path.starts_with(&self.snapshot.path)
108 }
109
110 pub fn has_inode(&self, inode: u64) -> bool {
111 self.snapshot.entries.get(&inode).is_some()
112 }
113
114 pub fn file_count(&self) -> usize {
115 self.snapshot.entries.summary().file_count
116 }
117
118 pub fn abs_path_for_inode(&self, ino: u64) -> Result<PathBuf> {
119 let mut result = self.snapshot.path.to_path_buf();
120 result.push(self.path_for_inode(ino, false)?);
121 Ok(result)
122 }
123
124 pub fn load_history(
125 &self,
126 ino: u64,
127 ctx: &AppContext,
128 ) -> impl Future<Output = Result<History>> {
129 let path = self.abs_path_for_inode(ino);
130 ctx.background_executor().spawn(async move {
131 let mut file = std::fs::File::open(&path?)?;
132 let mut base_text = String::new();
133 file.read_to_string(&mut base_text)?;
134 Ok(History::new(Arc::from(base_text)))
135 })
136 }
137
138 pub fn save<'a>(
139 &self,
140 ino: u64,
141 content: BufferSnapshot,
142 ctx: &AppContext,
143 ) -> Task<Result<()>> {
144 let path = self.abs_path_for_inode(ino);
145 ctx.background_executor().spawn(async move {
146 let buffer_size = content.text_summary().bytes.min(10 * 1024);
147 let file = std::fs::File::create(&path?)?;
148 let mut writer = std::io::BufWriter::with_capacity(buffer_size, file);
149 for chunk in content.fragments() {
150 writer.write(chunk.as_bytes())?;
151 }
152 writer.flush()?;
153 Ok(())
154 })
155 }
156
157 #[cfg(test)]
158 pub fn files<'a>(&'a self) -> impl Iterator<Item = u64> + 'a {
159 self.snapshot
160 .entries
161 .cursor::<(), ()>()
162 .filter_map(|entry| {
163 if let Entry::File { inode, .. } = entry {
164 Some(*inode)
165 } else {
166 None
167 }
168 })
169 }
170}
171
172impl Entity for Worktree {
173 type Event = ();
174}
175
176impl Deref for Worktree {
177 type Target = Snapshot;
178
179 fn deref(&self) -> &Self::Target {
180 &self.snapshot
181 }
182}
183
184impl fmt::Debug for Worktree {
185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186 self.snapshot.fmt(f)
187 }
188}
189
190#[derive(Clone)]
191pub struct Snapshot {
192 id: usize,
193 path: Arc<Path>,
194 root_inode: Option<u64>,
195 entries: SumTree<Entry>,
196}
197
198impl Snapshot {
199 pub fn file_count(&self) -> usize {
200 self.entries.summary().file_count
201 }
202
203 pub fn root_entry(&self) -> Option<&Entry> {
204 self.root_inode.and_then(|inode| self.entries.get(&inode))
205 }
206
207 pub fn root_name(&self) -> Option<&OsStr> {
208 self.path.file_name()
209 }
210
211 fn inode_for_path(&self, path: impl AsRef<Path>) -> Option<u64> {
212 let path = path.as_ref();
213 self.root_inode.and_then(|mut inode| {
214 'components: for path_component in path {
215 if let Some(Entry::Dir { children, .. }) = &self.entries.get(&inode) {
216 for (child_inode, name) in children.as_ref() {
217 if name.as_ref() == path_component {
218 inode = *child_inode;
219 continue 'components;
220 }
221 }
222 }
223 return None;
224 }
225 Some(inode)
226 })
227 }
228
229 fn entry_for_path(&self, path: impl AsRef<Path>) -> Option<&Entry> {
230 self.inode_for_path(path)
231 .and_then(|inode| self.entries.get(&inode))
232 }
233
234 pub fn path_for_inode(&self, mut ino: u64, include_root: bool) -> Result<PathBuf> {
235 let mut components = Vec::new();
236 let mut entry = self
237 .entries
238 .get(&ino)
239 .ok_or_else(|| anyhow!("entry does not exist in worktree"))?;
240 while let Some(parent) = entry.parent() {
241 entry = self.entries.get(&parent).unwrap();
242 if let Entry::Dir { children, .. } = entry {
243 let (_, child_name) = children
244 .iter()
245 .find(|(child_inode, _)| *child_inode == ino)
246 .unwrap();
247 components.push(child_name.as_ref());
248 ino = parent;
249 } else {
250 unreachable!();
251 }
252 }
253 if include_root {
254 components.push(self.path.file_name().unwrap());
255 }
256
257 Ok(components.into_iter().rev().collect())
258 }
259
260 fn remove_path(&mut self, path: &Path) {
261 let mut parent_entry = match path.parent().and_then(|p| self.entry_for_path(p).cloned()) {
262 Some(e) => e,
263 None => return,
264 };
265
266 let mut edits = Vec::new();
267 let parent_inode = parent_entry.inode();
268 let mut entry_inode = None;
269 if let Entry::Dir { children, .. } = &mut parent_entry {
270 let mut new_children = Vec::new();
271 for (child_inode, child_name) in children.as_ref() {
272 if Some(child_name.as_ref()) == path.file_name() {
273 entry_inode = Some(*child_inode);
274 } else {
275 new_children.push((*child_inode, child_name.clone()));
276 }
277 }
278
279 if new_children.iter().any(|c| Some(c.0) == entry_inode) {
280 entry_inode = None;
281 }
282
283 *children = new_children.into();
284 edits.push(Edit::Insert(parent_entry));
285 }
286
287 if let Some(entry_inode) = entry_inode {
288 let mut descendant_stack = Vec::new();
289 descendant_stack.push((entry_inode, parent_inode));
290 while let Some((inode, parent_inode)) = descendant_stack.pop() {
291 if let Some(entry) = self.entries.get(&inode) {
292 if entry.parent() == Some(parent_inode) {
293 edits.push(Edit::Remove(inode));
294 if let Entry::Dir { children, .. } = entry {
295 descendant_stack.extend(children.iter().map(|c| (c.0, entry.inode())));
296 }
297 }
298 }
299 }
300 }
301
302 self.entries.edit(edits);
303 }
304
305 fn fmt_entry(
306 &self,
307 f: &mut fmt::Formatter<'_>,
308 ino: u64,
309 name: &OsStr,
310 indent: usize,
311 ) -> fmt::Result {
312 match self.entries.get(&ino).unwrap() {
313 Entry::Dir { children, .. } => {
314 write!(
315 f,
316 "{}{}/ ({})\n",
317 " ".repeat(indent),
318 name.to_string_lossy(),
319 ino
320 )?;
321 for (child_inode, child_name) in children.iter() {
322 self.fmt_entry(f, *child_inode, child_name, indent + 2)?;
323 }
324 Ok(())
325 }
326 Entry::File { .. } => write!(
327 f,
328 "{}{} ({})\n",
329 " ".repeat(indent),
330 name.to_string_lossy(),
331 ino
332 ),
333 }
334 }
335}
336
337impl fmt::Debug for Snapshot {
338 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
339 if let Some(root_ino) = self.root_inode {
340 self.fmt_entry(f, root_ino, self.path.file_name().unwrap(), 0)
341 } else {
342 write!(f, "Empty tree\n")
343 }
344 }
345}
346
347impl FileHandle {
348 pub fn path(&self, ctx: &AppContext) -> PathBuf {
349 self.worktree
350 .read(ctx)
351 .path_for_inode(self.inode, false)
352 .unwrap()
353 }
354
355 pub fn load_history(&self, ctx: &AppContext) -> impl Future<Output = Result<History>> {
356 self.worktree.read(ctx).load_history(self.inode, ctx)
357 }
358
359 pub fn save<'a>(&self, content: BufferSnapshot, ctx: &AppContext) -> Task<Result<()>> {
360 let worktree = self.worktree.read(ctx);
361 worktree.save(self.inode, content, ctx)
362 }
363
364 pub fn entry_id(&self) -> (usize, u64) {
365 (self.worktree.id(), self.inode)
366 }
367}
368
369#[derive(Clone, Debug)]
370pub enum Entry {
371 Dir {
372 inode: u64,
373 parent: Option<u64>,
374 is_symlink: bool,
375 is_ignored: bool,
376 children: Arc<[(u64, Arc<OsStr>)]>,
377 pending: bool,
378 },
379 File {
380 inode: u64,
381 parent: Option<u64>,
382 is_symlink: bool,
383 is_ignored: bool,
384 path: PathEntry,
385 },
386}
387
388impl Entry {
389 fn inode(&self) -> u64 {
390 match self {
391 Entry::Dir { inode, .. } => *inode,
392 Entry::File { inode, .. } => *inode,
393 }
394 }
395
396 fn is_dir(&self) -> bool {
397 matches!(self, Entry::Dir { .. })
398 }
399
400 fn parent(&self) -> Option<u64> {
401 match self {
402 Entry::Dir { parent, .. } => *parent,
403 Entry::File { parent, .. } => *parent,
404 }
405 }
406}
407
408impl sum_tree::Item for Entry {
409 type Summary = EntrySummary;
410
411 fn summary(&self) -> Self::Summary {
412 EntrySummary {
413 max_ino: self.inode(),
414 file_count: if matches!(self, Self::File { .. }) {
415 1
416 } else {
417 0
418 },
419 }
420 }
421}
422
423impl sum_tree::KeyedItem for Entry {
424 type Key = u64;
425
426 fn key(&self) -> Self::Key {
427 self.inode()
428 }
429}
430
431#[derive(Clone, Debug, Default)]
432pub struct EntrySummary {
433 max_ino: u64,
434 file_count: usize,
435}
436
437impl<'a> AddAssign<&'a EntrySummary> for EntrySummary {
438 fn add_assign(&mut self, rhs: &'a EntrySummary) {
439 self.max_ino = rhs.max_ino;
440 self.file_count += rhs.file_count;
441 }
442}
443
444impl<'a> sum_tree::Dimension<'a, EntrySummary> for u64 {
445 fn add_summary(&mut self, summary: &'a EntrySummary) {
446 *self = summary.max_ino;
447 }
448}
449
450#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd)]
451struct FileCount(usize);
452
453impl<'a> sum_tree::Dimension<'a, EntrySummary> for FileCount {
454 fn add_summary(&mut self, summary: &'a EntrySummary) {
455 self.0 += summary.file_count;
456 }
457}
458
459struct BackgroundScanner {
460 snapshot: Mutex<Snapshot>,
461 notify: Sender<ScanState>,
462 thread_pool: scoped_pool::Pool,
463}
464
465impl BackgroundScanner {
466 fn new(snapshot: Snapshot, notify: Sender<ScanState>) -> Self {
467 Self {
468 snapshot: Mutex::new(snapshot),
469 notify,
470 thread_pool: scoped_pool::Pool::new(16),
471 }
472 }
473
474 fn path(&self) -> Arc<Path> {
475 self.snapshot.lock().path.clone()
476 }
477
478 fn snapshot(&self) -> Snapshot {
479 self.snapshot.lock().clone()
480 }
481
482 fn run(&self) {
483 let path = {
484 let mut snapshot = self.snapshot.lock();
485 let canonical_path = snapshot
486 .path
487 .canonicalize()
488 .map(Arc::from)
489 .unwrap_or_else(|_| snapshot.path.clone());
490 snapshot.path = canonical_path.clone();
491 canonical_path
492 };
493
494 // Create the event stream before we start scanning to ensure we receive events for changes
495 // that occur in the middle of the scan.
496 let event_stream =
497 fsevent::EventStream::new(&[path.as_ref()], Duration::from_millis(100), |events| {
498 if smol::block_on(self.notify.send(ScanState::Scanning)).is_err() {
499 return false;
500 }
501
502 self.process_events(events);
503
504 if smol::block_on(self.notify.send(ScanState::Idle)).is_err() {
505 return false;
506 }
507
508 true
509 });
510
511 if smol::block_on(self.notify.send(ScanState::Scanning)).is_err() {
512 return;
513 }
514
515 if let Err(err) = self.scan_dirs() {
516 if smol::block_on(self.notify.send(ScanState::Err(err))).is_err() {
517 return;
518 }
519 }
520
521 if smol::block_on(self.notify.send(ScanState::Idle)).is_err() {
522 return;
523 }
524
525 event_stream.run();
526 }
527
528 fn scan_dirs(&self) -> io::Result<()> {
529 let path = self.path();
530 let metadata = fs::metadata(&path)?;
531 let inode = metadata.ino();
532 let is_symlink = fs::symlink_metadata(&path)?.file_type().is_symlink();
533 let name = Arc::from(path.file_name().unwrap_or(OsStr::new("/")));
534 let relative_path = PathBuf::from(&name);
535
536 let mut ignore = IgnoreBuilder::new().build().add_parents(&path).unwrap();
537 if metadata.is_dir() {
538 ignore = ignore.add_child(&path).unwrap();
539 }
540 let is_ignored = ignore.matched(&path, metadata.is_dir()).is_ignore();
541
542 if metadata.file_type().is_dir() {
543 let is_ignored = is_ignored || name.as_ref() == ".git";
544 let dir_entry = Entry::Dir {
545 parent: None,
546 inode,
547 is_symlink,
548 is_ignored,
549 children: Arc::from([]),
550 pending: true,
551 };
552 self.insert_entries(Some(dir_entry.clone()));
553 self.snapshot.lock().root_inode = Some(inode);
554
555 let (tx, rx) = crossbeam_channel::unbounded();
556
557 tx.send(Ok(ScanJob {
558 inode,
559 path: path.clone(),
560 relative_path,
561 dir_entry,
562 ignore: Some(ignore),
563 scan_queue: tx.clone(),
564 }))
565 .unwrap();
566 drop(tx);
567
568 let mut results = Vec::new();
569 results.resize_with(self.thread_pool.workers(), || Ok(()));
570 self.thread_pool.scoped(|pool| {
571 for result in &mut results {
572 pool.execute(|| {
573 let result = result;
574 while let Ok(job) = rx.recv() {
575 if let Err(err) = job.and_then(|job| self.scan_dir(job)) {
576 *result = Err(err);
577 break;
578 }
579 }
580 });
581 }
582 });
583 results.into_iter().collect::<io::Result<()>>()?;
584 } else {
585 self.insert_entries(Some(Entry::File {
586 parent: None,
587 path: PathEntry::new(inode, &relative_path, is_ignored),
588 inode,
589 is_symlink,
590 is_ignored,
591 }));
592 self.snapshot.lock().root_inode = Some(inode);
593 }
594
595 Ok(())
596 }
597
598 fn scan_dir(&self, job: ScanJob) -> io::Result<()> {
599 let scan_queue = job.scan_queue;
600 let mut dir_entry = job.dir_entry;
601
602 let mut new_children = Vec::new();
603 let mut new_entries = Vec::new();
604 let mut new_jobs = Vec::new();
605
606 for child_entry in fs::read_dir(&job.path)? {
607 let child_entry = child_entry?;
608 let name: Arc<OsStr> = child_entry.file_name().into();
609 let relative_path = job.relative_path.join(name.as_ref());
610 let metadata = child_entry.metadata()?;
611 let ino = metadata.ino();
612 let is_symlink = metadata.file_type().is_symlink();
613 let path = job.path.join(name.as_ref());
614
615 new_children.push((ino, name.clone()));
616 if metadata.is_dir() {
617 let mut is_ignored = true;
618 let mut ignore = None;
619
620 if let Some(parent_ignore) = job.ignore.as_ref() {
621 let child_ignore = parent_ignore.add_child(&path).unwrap();
622 is_ignored =
623 child_ignore.matched(&path, true).is_ignore() || name.as_ref() == ".git";
624 if !is_ignored {
625 ignore = Some(child_ignore);
626 }
627 }
628
629 let dir_entry = Entry::Dir {
630 parent: Some(job.inode),
631 inode: ino,
632 is_symlink,
633 is_ignored,
634 children: Arc::from([]),
635 pending: true,
636 };
637 new_entries.push(dir_entry.clone());
638 new_jobs.push(ScanJob {
639 inode: ino,
640 path: Arc::from(path),
641 relative_path,
642 dir_entry,
643 ignore,
644 scan_queue: scan_queue.clone(),
645 });
646 } else {
647 let is_ignored = job
648 .ignore
649 .as_ref()
650 .map_or(true, |i| i.matched(&path, false).is_ignore());
651 new_entries.push(Entry::File {
652 parent: Some(job.inode),
653 path: PathEntry::new(ino, &relative_path, is_ignored),
654 inode: ino,
655 is_symlink,
656 is_ignored,
657 });
658 };
659 }
660
661 if let Entry::Dir {
662 children, pending, ..
663 } = &mut dir_entry
664 {
665 *children = Arc::from(new_children);
666 *pending = false;
667 } else {
668 unreachable!()
669 }
670 new_entries.push(dir_entry);
671
672 self.insert_entries(new_entries);
673 for new_job in new_jobs {
674 scan_queue.send(Ok(new_job)).unwrap();
675 }
676
677 Ok(())
678 }
679
680 fn process_events(&self, mut events: Vec<fsevent::Event>) {
681 let mut snapshot = self.snapshot();
682
683 events.sort_unstable_by(|a, b| a.path.cmp(&b.path));
684 let mut paths = events.into_iter().map(|e| e.path).peekable();
685 let (scan_queue_tx, scan_queue_rx) = crossbeam_channel::unbounded();
686 while let Some(path) = paths.next() {
687 let relative_path = match path.strip_prefix(&snapshot.path) {
688 Ok(relative_path) => relative_path.to_path_buf(),
689 Err(e) => {
690 log::error!("Unexpected event {:?}", e);
691 continue;
692 }
693 };
694
695 while paths.peek().map_or(false, |p| p.starts_with(&path)) {
696 paths.next();
697 }
698
699 snapshot.remove_path(&relative_path);
700
701 match self.fs_entry_for_path(&snapshot.path, &path) {
702 Ok(Some((fs_entry, ignore))) => {
703 let mut edits = Vec::new();
704 edits.push(Edit::Insert(fs_entry.clone()));
705 if let Some(parent) = fs_entry.parent() {
706 let mut parent_entry = snapshot.entries.get(&parent).unwrap().clone();
707 if let Entry::Dir { children, .. } = &mut parent_entry {
708 let name = Arc::from(path.file_name().unwrap());
709 *children = children
710 .into_iter()
711 .cloned()
712 .chain(Some((fs_entry.inode(), name)))
713 .collect::<Vec<_>>()
714 .into();
715 edits.push(Edit::Insert(parent_entry));
716 } else {
717 unreachable!();
718 }
719 }
720 snapshot.entries.edit(edits);
721
722 if fs_entry.is_dir() {
723 let relative_path = snapshot
724 .path
725 .parent()
726 .map_or(path.as_path(), |parent| path.strip_prefix(parent).unwrap())
727 .to_path_buf();
728 scan_queue_tx
729 .send(Ok(ScanJob {
730 inode: fs_entry.inode(),
731 path: Arc::from(path),
732 relative_path,
733 dir_entry: fs_entry,
734 ignore: Some(ignore),
735 scan_queue: scan_queue_tx.clone(),
736 }))
737 .unwrap();
738 }
739 }
740 Ok(None) => {}
741 Err(err) => {
742 // TODO - create a special 'error' entry in the entries tree to mark this
743 log::error!("Error reading file on event {:?}", err);
744 }
745 }
746 }
747
748 *self.snapshot.lock() = snapshot;
749
750 // Scan any directories that were moved into this worktree as part of this event batch.
751 drop(scan_queue_tx);
752 self.thread_pool.scoped(|pool| {
753 for _ in 0..self.thread_pool.workers() {
754 pool.execute(|| {
755 while let Ok(job) = scan_queue_rx.recv() {
756 if let Err(err) = job.and_then(|job| self.scan_dir(job)) {
757 log::error!("Error scanning {:?}", err);
758 }
759 }
760 });
761 }
762 });
763 }
764
765 fn fs_entry_for_path(&self, root_path: &Path, path: &Path) -> Result<Option<(Entry, Ignore)>> {
766 match fs::metadata(&path) {
767 Ok(metadata) => {
768 let mut ignore = IgnoreBuilder::new().build().add_parents(&path).unwrap();
769 if metadata.is_dir() {
770 ignore = ignore.add_child(&path).unwrap();
771 }
772 let is_ignored = ignore.matched(&path, metadata.is_dir()).is_ignore();
773
774 let inode = metadata.ino();
775 let is_symlink = fs::symlink_metadata(&path)?.file_type().is_symlink();
776 let parent = if path == root_path {
777 None
778 } else {
779 Some(fs::metadata(path.parent().unwrap())?.ino())
780 };
781 if metadata.file_type().is_dir() {
782 Ok(Some((
783 Entry::Dir {
784 parent,
785 inode,
786 is_symlink,
787 is_ignored,
788 children: Arc::from([]),
789 pending: true,
790 },
791 ignore,
792 )))
793 } else {
794 Ok(Some((
795 Entry::File {
796 parent,
797 path: PathEntry::new(
798 inode,
799 root_path
800 .parent()
801 .map_or(path, |parent| path.strip_prefix(parent).unwrap()),
802 is_ignored,
803 ),
804 inode,
805 is_symlink,
806 is_ignored,
807 },
808 ignore,
809 )))
810 }
811 }
812 Err(err) => {
813 if err.kind() == io::ErrorKind::NotFound {
814 Ok(None)
815 } else {
816 Err(anyhow::Error::new(err))
817 }
818 }
819 }
820 }
821
822 fn insert_entries(&self, entries: impl IntoIterator<Item = Entry>) {
823 self.snapshot
824 .lock()
825 .entries
826 .edit(entries.into_iter().map(Edit::Insert).collect::<Vec<_>>());
827 }
828}
829
830impl Drop for BackgroundScanner {
831 fn drop(&mut self) {
832 self.thread_pool.shutdown();
833 }
834}
835
836struct ScanJob {
837 inode: u64,
838 path: Arc<Path>,
839 relative_path: PathBuf,
840 dir_entry: Entry,
841 ignore: Option<Ignore>,
842 scan_queue: crossbeam_channel::Sender<io::Result<ScanJob>>,
843}
844
845pub trait WorktreeHandle {
846 fn file(&self, entry_id: u64, app: &AppContext) -> Result<FileHandle>;
847}
848
849impl WorktreeHandle for ModelHandle<Worktree> {
850 fn file(&self, inode: u64, app: &AppContext) -> Result<FileHandle> {
851 if self.read(app).has_inode(inode) {
852 Ok(FileHandle {
853 worktree: self.clone(),
854 inode,
855 })
856 } else {
857 Err(anyhow!("entry does not exist in tree"))
858 }
859 }
860}
861
862trait UnwrapIgnoreTuple {
863 fn unwrap(self) -> Ignore;
864}
865
866impl UnwrapIgnoreTuple for (Ignore, Option<ignore::Error>) {
867 fn unwrap(self) -> Ignore {
868 if let Some(error) = self.1 {
869 log::error!("error loading gitignore data: {}", error);
870 }
871 self.0
872 }
873}
874
875#[cfg(test)]
876mod tests {
877 use super::*;
878 use crate::editor::Buffer;
879 use crate::test::*;
880 use anyhow::Result;
881 use gpui::App;
882 use log::LevelFilter;
883 use rand::prelude::*;
884 use serde_json::json;
885 use simplelog::SimpleLogger;
886 use std::env;
887 use std::os::unix;
888 use std::time::{SystemTime, UNIX_EPOCH};
889
890 #[test]
891 fn test_populate_and_search() {
892 App::test_async((), |mut app| async move {
893 let dir = temp_tree(json!({
894 "root": {
895 "apple": "",
896 "banana": {
897 "carrot": {
898 "date": "",
899 "endive": "",
900 }
901 },
902 "fennel": {
903 "grape": "",
904 }
905 }
906 }));
907
908 let root_link_path = dir.path().join("root_link");
909 unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
910
911 let tree = app.add_model(|ctx| Worktree::new(root_link_path, ctx));
912 assert_condition(1, 300, || app.read(|ctx| tree.read(ctx).file_count() == 4)).await;
913 app.read(|ctx| {
914 let tree = tree.read(ctx);
915 let results = match_paths(
916 Some(tree.snapshot()).iter(),
917 "bna",
918 false,
919 false,
920 false,
921 10,
922 ctx.thread_pool().clone(),
923 )
924 .iter()
925 .map(|result| tree.path_for_inode(result.entry_id, true))
926 .collect::<Result<Vec<PathBuf>, _>>()
927 .unwrap();
928 assert_eq!(
929 results,
930 vec![
931 PathBuf::from("root_link/banana/carrot/date"),
932 PathBuf::from("root_link/banana/carrot/endive"),
933 ]
934 );
935 })
936 });
937 }
938
939 #[test]
940 fn test_save_file() {
941 App::test_async((), |mut app| async move {
942 let dir = temp_tree(json!({
943 "file1": "the old contents",
944 }));
945
946 let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
947 assert_condition(1, 300, || app.read(|ctx| tree.read(ctx).file_count() == 1)).await;
948
949 let buffer = Buffer::new(1, "a line of text.\n".repeat(10 * 1024));
950
951 let file_inode = app.read(|ctx| {
952 let tree = tree.read(ctx);
953 let inode = tree.files().next().unwrap();
954 assert_eq!(
955 tree.path_for_inode(inode, false)
956 .unwrap()
957 .file_name()
958 .unwrap(),
959 "file1"
960 );
961 inode
962 });
963
964 tree.update(&mut app, |tree, ctx| {
965 smol::block_on(tree.save(file_inode, buffer.snapshot(), ctx.as_ref())).unwrap()
966 });
967
968 let loaded_history = app
969 .read(|ctx| tree.read(ctx).load_history(file_inode, ctx))
970 .await
971 .unwrap();
972 assert_eq!(loaded_history.base_text.as_ref(), buffer.text());
973 });
974 }
975
976 #[test]
977 fn test_rescan() {
978 App::test_async((), |mut app| async move {
979 let dir2 = temp_tree(json!({
980 "dir1": {
981 "dir3": {
982 "file": "contents",
983 }
984 },
985 "dir2": {
986 }
987 }));
988 let dir = temp_tree(json!({
989 "dir1": {
990 "dir3": {
991 "file": "contents",
992 }
993 },
994 "dir2": {
995 }
996 }));
997
998 let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
999 assert_condition(1, 300, || app.read(|ctx| tree.read(ctx).file_count() == 1)).await;
1000
1001 let dir_inode = app.read(|ctx| {
1002 tree.read(ctx)
1003 .snapshot()
1004 .inode_for_path("dir1/dir3")
1005 .unwrap()
1006 });
1007 app.read(|ctx| {
1008 let tree = tree.read(ctx);
1009 assert_eq!(
1010 tree.path_for_inode(dir_inode, false)
1011 .unwrap()
1012 .to_str()
1013 .unwrap(),
1014 "dir1/dir3"
1015 );
1016 });
1017
1018 std::fs::rename(dir2.path(), dir.path().join("foo")).unwrap();
1019 assert_condition(1, 300, || {
1020 app.read(|ctx| {
1021 let tree = tree.read(ctx);
1022 tree.path_for_inode(dir_inode, false)
1023 .unwrap()
1024 .to_str()
1025 .unwrap()
1026 == "dir2/dir3"
1027 })
1028 })
1029 .await;
1030 app.read(|ctx| {
1031 let tree = tree.read(ctx);
1032 assert_eq!(tree.snapshot().inode_for_path("dir2/dir3"), Some(dir_inode));
1033 });
1034 });
1035 }
1036
1037 #[test]
1038 fn test_random() {
1039 let iterations = env::var("ITERATIONS")
1040 .map(|i| i.parse().unwrap())
1041 .unwrap_or(100);
1042 let operations = env::var("OPERATIONS")
1043 .map(|o| o.parse().unwrap())
1044 .unwrap_or(40);
1045 let initial_entries = env::var("INITIAL_ENTRIES")
1046 .map(|o| o.parse().unwrap())
1047 .unwrap_or(20);
1048 let seeds = if let Ok(seed) = env::var("SEED").map(|s| s.parse().unwrap()) {
1049 // Init logging so that we can debug the operations for this seed.
1050 SimpleLogger::init(LevelFilter::Info, Default::default()).unwrap();
1051 seed..seed + 1
1052 } else {
1053 0..iterations
1054 };
1055
1056 for seed in seeds {
1057 dbg!(seed);
1058 let mut rng = StdRng::seed_from_u64(seed);
1059
1060 let root_dir = tempdir::TempDir::new(&format!("test-{}", seed)).unwrap();
1061 for _ in 0..initial_entries {
1062 randomly_mutate_tree(root_dir.path(), 1.0, &mut rng).unwrap();
1063 }
1064 log::info!("Generated initial tree");
1065
1066 let (notify_tx, _notify_rx) = smol::channel::unbounded();
1067 let scanner = BackgroundScanner::new(
1068 Snapshot {
1069 id: 0,
1070 path: root_dir.path().into(),
1071 root_inode: None,
1072 entries: Default::default(),
1073 },
1074 notify_tx,
1075 );
1076 scanner.scan_dirs().unwrap();
1077
1078 let mut events = Vec::new();
1079 let mut mutations_len = operations;
1080 while mutations_len > 1 {
1081 if !events.is_empty() && rng.gen_bool(0.4) {
1082 let len = rng.gen_range(0..=events.len());
1083 let to_deliver = events.drain(0..len).collect::<Vec<_>>();
1084 log::info!("Delivering events: {:#?}", to_deliver);
1085 scanner.process_events(to_deliver);
1086 } else {
1087 events.extend(randomly_mutate_tree(root_dir.path(), 0.6, &mut rng).unwrap());
1088 mutations_len -= 1;
1089 }
1090 }
1091 log::info!("Quiescing: {:#?}", events);
1092 scanner.process_events(events);
1093
1094 let (notify_tx, _notify_rx) = smol::channel::unbounded();
1095 let new_scanner = BackgroundScanner::new(
1096 Snapshot {
1097 id: 0,
1098 path: root_dir.path().into(),
1099 root_inode: None,
1100 entries: Default::default(),
1101 },
1102 notify_tx,
1103 );
1104 new_scanner.scan_dirs().unwrap();
1105 assert_eq!(scanner.snapshot().to_vec(), new_scanner.snapshot().to_vec());
1106 }
1107 }
1108
1109 fn randomly_mutate_tree(
1110 root_path: &Path,
1111 insertion_probability: f64,
1112 rng: &mut impl Rng,
1113 ) -> Result<Vec<fsevent::Event>> {
1114 let (dirs, files) = read_dir_recursive(root_path.to_path_buf());
1115
1116 let mut events = Vec::new();
1117 let mut record_event = |path: PathBuf| {
1118 events.push(fsevent::Event {
1119 event_id: SystemTime::now()
1120 .duration_since(UNIX_EPOCH)
1121 .unwrap()
1122 .as_secs(),
1123 flags: fsevent::StreamFlags::empty(),
1124 path,
1125 });
1126 };
1127
1128 if (files.is_empty() && dirs.len() == 1) || rng.gen_bool(insertion_probability) {
1129 let path = dirs.choose(rng).unwrap();
1130 let new_path = path.join(gen_name(rng));
1131
1132 if rng.gen() {
1133 log::info!("Creating dir {:?}", new_path.strip_prefix(root_path)?);
1134 fs::create_dir(&new_path)?;
1135 } else {
1136 log::info!("Creating file {:?}", new_path.strip_prefix(root_path)?);
1137 fs::write(&new_path, "")?;
1138 }
1139 record_event(new_path);
1140 } else {
1141 let old_path = {
1142 let file_path = files.choose(rng);
1143 let dir_path = dirs[1..].choose(rng);
1144 file_path.into_iter().chain(dir_path).choose(rng).unwrap()
1145 };
1146
1147 let is_rename = rng.gen();
1148 if is_rename {
1149 let new_path_parent = dirs
1150 .iter()
1151 .filter(|d| !d.starts_with(old_path))
1152 .choose(rng)
1153 .unwrap();
1154
1155 let overwrite_existing_dir =
1156 !old_path.starts_with(&new_path_parent) && rng.gen_bool(0.3);
1157 let new_path = if overwrite_existing_dir {
1158 fs::remove_dir_all(&new_path_parent).ok();
1159 new_path_parent.to_path_buf()
1160 } else {
1161 new_path_parent.join(gen_name(rng))
1162 };
1163
1164 log::info!(
1165 "Renaming {:?} to {}{:?}",
1166 old_path.strip_prefix(&root_path)?,
1167 if overwrite_existing_dir {
1168 "overwrite "
1169 } else {
1170 ""
1171 },
1172 new_path.strip_prefix(&root_path)?
1173 );
1174 fs::rename(&old_path, &new_path)?;
1175 record_event(old_path.clone());
1176 record_event(new_path);
1177 } else if old_path.is_dir() {
1178 let (dirs, files) = read_dir_recursive(old_path.clone());
1179
1180 log::info!("Deleting dir {:?}", old_path.strip_prefix(&root_path)?);
1181 fs::remove_dir_all(&old_path).unwrap();
1182 for file in files {
1183 record_event(file);
1184 }
1185 for dir in dirs {
1186 record_event(dir);
1187 }
1188 } else {
1189 log::info!("Deleting file {:?}", old_path.strip_prefix(&root_path)?);
1190 fs::remove_file(old_path).unwrap();
1191 record_event(old_path.clone());
1192 }
1193 }
1194
1195 Ok(events)
1196 }
1197
1198 fn read_dir_recursive(path: PathBuf) -> (Vec<PathBuf>, Vec<PathBuf>) {
1199 let child_entries = fs::read_dir(&path).unwrap();
1200 let mut dirs = vec![path];
1201 let mut files = Vec::new();
1202 for child_entry in child_entries {
1203 let child_path = child_entry.unwrap().path();
1204 if child_path.is_dir() {
1205 let (child_dirs, child_files) = read_dir_recursive(child_path);
1206 dirs.extend(child_dirs);
1207 files.extend(child_files);
1208 } else {
1209 files.push(child_path);
1210 }
1211 }
1212 (dirs, files)
1213 }
1214
1215 fn gen_name(rng: &mut impl Rng) -> String {
1216 (0..6)
1217 .map(|_| rng.sample(rand::distributions::Alphanumeric))
1218 .map(char::from)
1219 .collect()
1220 }
1221
1222 impl Snapshot {
1223 fn to_vec(&self) -> Vec<(PathBuf, u64)> {
1224 use std::iter::FromIterator;
1225
1226 let mut paths = Vec::new();
1227
1228 let mut stack = Vec::new();
1229 stack.extend(self.root_inode);
1230 while let Some(inode) = stack.pop() {
1231 let computed_path = self.path_for_inode(inode, true).unwrap();
1232 match self.entries.get(&inode).unwrap() {
1233 Entry::Dir { children, .. } => {
1234 stack.extend(children.iter().map(|c| c.0));
1235 }
1236 Entry::File { path, .. } => {
1237 assert_eq!(
1238 String::from_iter(path.path.iter()),
1239 computed_path.to_str().unwrap()
1240 );
1241 }
1242 }
1243 paths.push((computed_path, inode));
1244 }
1245
1246 paths.sort_by(|a, b| a.0.cmp(&b.0));
1247 paths
1248 }
1249 }
1250}