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