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::{anyhow, Context, Result};
11pub use fuzzy::{match_paths, PathMatch};
12use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, Task};
13use lazy_static::lazy_static;
14use parking_lot::Mutex;
15use postage::{
16 prelude::{Sink, Stream},
17 watch,
18};
19use smol::{channel::Sender, Timer};
20use std::{
21 cmp,
22 collections::{BTreeMap, HashSet},
23 ffi::{CStr, OsStr},
24 fmt, fs,
25 future::Future,
26 io::{self, Read, Write},
27 ops::{AddAssign, Deref},
28 os::unix::{ffi::OsStrExt, fs::MetadataExt},
29 path::{Path, PathBuf},
30 sync::Arc,
31 time::Duration,
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 scan_state: (watch::Sender<ScanState>, watch::Receiver<ScanState>),
51 _event_stream_handle: fsevent::Handle,
52 poll_scheduled: bool,
53}
54
55#[derive(Clone)]
56pub struct FileHandle {
57 worktree: ModelHandle<Worktree>,
58 path: Arc<Path>,
59}
60
61impl Worktree {
62 pub fn new(path: impl Into<Arc<Path>>, ctx: &mut ModelContext<Self>) -> Self {
63 let abs_path = path.into();
64 let root_name_chars = abs_path.file_name().map_or(Vec::new(), |n| {
65 n.to_string_lossy().chars().chain(Some('/')).collect()
66 });
67 let (scan_state_tx, scan_state_rx) = smol::channel::unbounded();
68 let id = ctx.model_id();
69 let snapshot = Snapshot {
70 id,
71 scan_id: 0,
72 abs_path,
73 root_name_chars,
74 ignores: Default::default(),
75 entries: Default::default(),
76 };
77 let (event_stream, event_stream_handle) =
78 fsevent::EventStream::new(&[snapshot.abs_path.as_ref()], Duration::from_millis(100));
79
80 let background_snapshot = Arc::new(Mutex::new(snapshot.clone()));
81
82 let tree = Self {
83 snapshot,
84 background_snapshot: background_snapshot.clone(),
85 scan_state: watch::channel_with(ScanState::Scanning),
86 _event_stream_handle: event_stream_handle,
87 poll_scheduled: false,
88 };
89
90 std::thread::spawn(move || {
91 let scanner = BackgroundScanner::new(background_snapshot, scan_state_tx, id);
92 scanner.run(event_stream)
93 });
94
95 ctx.spawn_stream(scan_state_rx, Self::observe_scan_state, |_, _| {})
96 .detach();
97
98 tree
99 }
100
101 pub fn scan_complete(&self) -> impl Future<Output = ()> {
102 let mut scan_state_rx = self.scan_state.1.clone();
103 async move {
104 let mut scan_state = Some(scan_state_rx.borrow().clone());
105 while let Some(ScanState::Scanning) = scan_state {
106 scan_state = scan_state_rx.recv().await;
107 }
108 }
109 }
110
111 pub fn next_scan_complete(&self) -> impl Future<Output = ()> {
112 let mut scan_state_rx = self.scan_state.1.clone();
113 let mut did_scan = matches!(*scan_state_rx.borrow(), ScanState::Scanning);
114 async move {
115 loop {
116 if let ScanState::Scanning = *scan_state_rx.borrow() {
117 did_scan = true;
118 } else if did_scan {
119 break;
120 }
121 scan_state_rx.recv().await;
122 }
123 }
124 }
125
126 fn observe_scan_state(&mut self, scan_state: ScanState, ctx: &mut ModelContext<Self>) {
127 let _ = self.scan_state.0.blocking_send(scan_state);
128 self.poll_entries(ctx);
129 }
130
131 fn poll_entries(&mut self, ctx: &mut ModelContext<Self>) {
132 self.snapshot = self.background_snapshot.lock().clone();
133 ctx.notify();
134
135 if self.is_scanning() && !self.poll_scheduled {
136 ctx.spawn(Timer::after(Duration::from_millis(100)), |this, _, ctx| {
137 this.poll_scheduled = false;
138 this.poll_entries(ctx);
139 })
140 .detach();
141 self.poll_scheduled = true;
142 }
143 }
144
145 fn is_scanning(&self) -> bool {
146 if let ScanState::Scanning = *self.scan_state.1.borrow() {
147 true
148 } else {
149 false
150 }
151 }
152
153 pub fn snapshot(&self) -> Snapshot {
154 self.snapshot.clone()
155 }
156
157 pub fn contains_abs_path(&self, path: &Path) -> bool {
158 path.starts_with(&self.snapshot.abs_path)
159 }
160
161 pub fn load_history(
162 &self,
163 path: &Path,
164 ctx: &AppContext,
165 ) -> impl Future<Output = Result<History>> {
166 let abs_path = self.snapshot.abs_path.join(path);
167 ctx.background_executor().spawn(async move {
168 let mut file = std::fs::File::open(&abs_path)?;
169 let mut base_text = String::new();
170 file.read_to_string(&mut base_text)?;
171 Ok(History::new(Arc::from(base_text)))
172 })
173 }
174
175 pub fn save<'a>(
176 &self,
177 path: &Path,
178 content: BufferSnapshot,
179 ctx: &AppContext,
180 ) -> Task<Result<()>> {
181 let abs_path = self.snapshot.abs_path.join(path);
182 ctx.background_executor().spawn(async move {
183 let buffer_size = content.text_summary().bytes.min(10 * 1024);
184 let file = std::fs::File::create(&abs_path)?;
185 let mut writer = std::io::BufWriter::with_capacity(buffer_size, file);
186 for chunk in content.fragments() {
187 writer.write(chunk.as_bytes())?;
188 }
189 writer.flush()?;
190 Ok(())
191 })
192 }
193}
194
195impl Entity for Worktree {
196 type Event = ();
197}
198
199impl Deref for Worktree {
200 type Target = Snapshot;
201
202 fn deref(&self) -> &Self::Target {
203 &self.snapshot
204 }
205}
206
207impl fmt::Debug for Worktree {
208 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209 self.snapshot.fmt(f)
210 }
211}
212
213#[derive(Clone)]
214pub struct Snapshot {
215 id: usize,
216 scan_id: usize,
217 abs_path: Arc<Path>,
218 root_name_chars: Vec<char>,
219 ignores: BTreeMap<u64, Arc<Gitignore>>,
220 entries: SumTree<Entry>,
221}
222
223impl Snapshot {
224 pub fn file_count(&self) -> usize {
225 self.entries.summary().file_count
226 }
227
228 pub fn visible_file_count(&self) -> usize {
229 self.entries.summary().visible_file_count
230 }
231
232 pub fn files(&self, start: usize) -> FileIter {
233 FileIter::all(self, start)
234 }
235
236 #[cfg(test)]
237 pub fn paths(&self) -> impl Iterator<Item = &Arc<Path>> {
238 let mut cursor = self.entries.cursor::<(), ()>();
239 cursor.next();
240 cursor.map(|entry| entry.path())
241 }
242
243 pub fn visible_files(&self, start: usize) -> FileIter {
244 FileIter::visible(self, start)
245 }
246
247 pub fn root_entry(&self) -> &Entry {
248 self.entry_for_path("").unwrap()
249 }
250
251 pub fn root_name(&self) -> Option<&OsStr> {
252 self.abs_path.file_name()
253 }
254
255 pub fn root_name_chars(&self) -> &[char] {
256 &self.root_name_chars
257 }
258
259 fn entry_for_path(&self, path: impl AsRef<Path>) -> Option<&Entry> {
260 let mut cursor = self.entries.cursor::<_, ()>();
261 if cursor.seek(&PathSearch::Exact(path.as_ref()), SeekBias::Left) {
262 cursor.item()
263 } else {
264 None
265 }
266 }
267
268 pub fn inode_for_path(&self, path: impl AsRef<Path>) -> Option<u64> {
269 self.entry_for_path(path.as_ref()).map(|e| e.inode())
270 }
271
272 fn is_path_ignored(&self, path: &Path) -> Result<bool> {
273 todo!();
274 Ok(false)
275 // let mut entry = self
276 // .entry_for_path(path)
277 // .ok_or_else(|| anyhow!("entry does not exist in worktree"))?;
278
279 // if path.starts_with(".git") {
280 // Ok(true)
281 // } else {
282 // while let Some(parent_entry) =
283 // entry.path().parent().and_then(|p| self.entry_for_path(p))
284 // {
285 // let parent_path = parent_entry.path();
286 // if let Some((ignore, _)) = self.ignores.get(parent_path) {
287 // let relative_path = path.strip_prefix(parent_path).unwrap();
288 // match ignore.matched_path_or_any_parents(relative_path, entry.is_dir()) {
289 // ::ignore::Match::Whitelist(_) => return Ok(false),
290 // ::ignore::Match::Ignore(_) => return Ok(true),
291 // ::ignore::Match::None => {}
292 // }
293 // }
294 // entry = parent_entry;
295 // }
296 // Ok(false)
297 // }
298 }
299
300 fn insert_entry(&mut self, entry: Entry) {
301 if !entry.is_dir() && entry.path().file_name() == Some(&GITIGNORE) {
302 self.insert_ignore_file(entry.path());
303 }
304 self.entries.insert(entry);
305 }
306
307 fn populate_dir(
308 &mut self,
309 parent_path: Arc<Path>,
310 entries: impl IntoIterator<Item = Entry>,
311 ignore: Option<Arc<Gitignore>>,
312 ) {
313 let mut edits = Vec::new();
314
315 let mut parent_entry = self.entries.get(&PathKey(parent_path)).unwrap().clone();
316 if let Some(ignore) = ignore {
317 self.ignores.insert(parent_entry.inode, ignore);
318 }
319 if matches!(parent_entry.kind, EntryKind::PendingDir) {
320 parent_entry.kind = EntryKind::Dir;
321 } else {
322 unreachable!();
323 }
324 edits.push(Edit::Insert(parent_entry));
325
326 for entry in entries {
327 if !entry.is_dir() && entry.path().file_name() == Some(&GITIGNORE) {
328 self.insert_ignore_file(entry.path());
329 }
330 edits.push(Edit::Insert(entry));
331 }
332 self.entries.edit(edits);
333 }
334
335 fn remove_path(&mut self, path: &Path) {
336 let new_entries = {
337 let mut cursor = self.entries.cursor::<_, ()>();
338 let mut new_entries = cursor.slice(&PathSearch::Exact(path), SeekBias::Left);
339 cursor.seek_forward(&PathSearch::Successor(path), SeekBias::Left);
340 new_entries.push_tree(cursor.suffix());
341 new_entries
342 };
343 self.entries = new_entries;
344
345 // if path.file_name() == Some(&GITIGNORE) {
346 // if let Some((_, scan_id)) = self.ignores.get_mut(path.parent().unwrap()) {
347 // *scan_id = self.scan_id;
348 // }
349 // }
350 }
351
352 fn insert_ignore_file(&mut self, path: &Path) -> Arc<Gitignore> {
353 let (ignore, err) = Gitignore::new(self.abs_path.join(path));
354 if let Some(err) = err {
355 log::error!("error in ignore file {:?} - {:?}", path, err);
356 }
357 let ignore = Arc::new(ignore);
358 let ignore_parent_inode = self.entry_for_path(path.parent().unwrap()).unwrap().inode;
359 self.ignores.insert(ignore_parent_inode, ignore.clone());
360 ignore
361 }
362}
363
364impl fmt::Debug for Snapshot {
365 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
366 for entry in self.entries.cursor::<(), ()>() {
367 for _ in entry.path().ancestors().skip(1) {
368 write!(f, " ")?;
369 }
370 writeln!(f, "{:?} (inode: {})", entry.path(), entry.inode())?;
371 }
372 Ok(())
373 }
374}
375
376impl FileHandle {
377 pub fn path(&self) -> &Arc<Path> {
378 &self.path
379 }
380
381 pub fn load_history(&self, ctx: &AppContext) -> impl Future<Output = Result<History>> {
382 self.worktree.read(ctx).load_history(&self.path, ctx)
383 }
384
385 pub fn save<'a>(&self, content: BufferSnapshot, ctx: &AppContext) -> Task<Result<()>> {
386 let worktree = self.worktree.read(ctx);
387 worktree.save(&self.path, content, ctx)
388 }
389
390 pub fn entry_id(&self) -> (usize, Arc<Path>) {
391 (self.worktree.id(), self.path.clone())
392 }
393}
394
395#[derive(Clone, Debug)]
396pub struct Entry {
397 kind: EntryKind,
398 path: Arc<Path>,
399 inode: u64,
400 is_symlink: bool,
401 is_ignored: bool,
402}
403
404#[derive(Clone, Debug)]
405pub enum EntryKind {
406 PendingDir,
407 Dir,
408 File(CharBag),
409}
410
411impl Entry {
412 pub fn path(&self) -> &Arc<Path> {
413 &self.path
414 }
415
416 pub fn inode(&self) -> u64 {
417 self.inode
418 }
419
420 fn is_ignored(&self) -> bool {
421 self.is_ignored
422 }
423
424 fn set_ignored(&mut self, ignored: bool) {
425 self.is_ignored = ignored;
426 }
427
428 fn is_dir(&self) -> bool {
429 matches!(self.kind, EntryKind::Dir | EntryKind::PendingDir)
430 }
431}
432
433impl sum_tree::Item for Entry {
434 type Summary = EntrySummary;
435
436 fn summary(&self) -> Self::Summary {
437 let file_count;
438 let visible_file_count;
439 if matches!(self.kind, EntryKind::File(_)) {
440 file_count = 1;
441 if self.is_ignored {
442 visible_file_count = 0;
443 } else {
444 visible_file_count = 1;
445 }
446 } else {
447 file_count = 0;
448 visible_file_count = 0;
449 }
450
451 EntrySummary {
452 max_path: self.path().clone(),
453 file_count,
454 visible_file_count,
455 }
456 }
457}
458
459impl sum_tree::KeyedItem for Entry {
460 type Key = PathKey;
461
462 fn key(&self) -> Self::Key {
463 PathKey(self.path().clone())
464 }
465}
466
467#[derive(Clone, Debug)]
468pub struct EntrySummary {
469 max_path: Arc<Path>,
470 file_count: usize,
471 visible_file_count: usize,
472}
473
474impl Default for EntrySummary {
475 fn default() -> Self {
476 Self {
477 max_path: Arc::from(Path::new("")),
478 file_count: 0,
479 visible_file_count: 0,
480 }
481 }
482}
483
484impl<'a> AddAssign<&'a EntrySummary> for EntrySummary {
485 fn add_assign(&mut self, rhs: &'a EntrySummary) {
486 self.max_path = rhs.max_path.clone();
487 self.file_count += rhs.file_count;
488 self.visible_file_count += rhs.visible_file_count;
489 }
490}
491
492#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
493pub struct PathKey(Arc<Path>);
494
495impl Default for PathKey {
496 fn default() -> Self {
497 Self(Path::new("").into())
498 }
499}
500
501impl<'a> sum_tree::Dimension<'a, EntrySummary> for PathKey {
502 fn add_summary(&mut self, summary: &'a EntrySummary) {
503 self.0 = summary.max_path.clone();
504 }
505}
506
507#[derive(Copy, Clone, Debug, PartialEq, Eq)]
508enum PathSearch<'a> {
509 Exact(&'a Path),
510 Successor(&'a Path),
511}
512
513impl<'a> Ord for PathSearch<'a> {
514 fn cmp(&self, other: &Self) -> cmp::Ordering {
515 match (self, other) {
516 (Self::Exact(a), Self::Exact(b)) => a.cmp(b),
517 (Self::Successor(a), Self::Exact(b)) => {
518 if b.starts_with(a) {
519 cmp::Ordering::Greater
520 } else {
521 a.cmp(b)
522 }
523 }
524 _ => todo!("not sure we need the other two cases"),
525 }
526 }
527}
528
529impl<'a> PartialOrd for PathSearch<'a> {
530 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
531 Some(self.cmp(other))
532 }
533}
534
535impl<'a> Default for PathSearch<'a> {
536 fn default() -> Self {
537 Self::Exact(Path::new("").into())
538 }
539}
540
541impl<'a: 'b, 'b> sum_tree::Dimension<'a, EntrySummary> for PathSearch<'b> {
542 fn add_summary(&mut self, summary: &'a EntrySummary) {
543 *self = Self::Exact(summary.max_path.as_ref());
544 }
545}
546
547#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd)]
548pub struct FileCount(usize);
549
550impl<'a> sum_tree::Dimension<'a, EntrySummary> for FileCount {
551 fn add_summary(&mut self, summary: &'a EntrySummary) {
552 self.0 += summary.file_count;
553 }
554}
555
556#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd)]
557pub struct VisibleFileCount(usize);
558
559impl<'a> sum_tree::Dimension<'a, EntrySummary> for VisibleFileCount {
560 fn add_summary(&mut self, summary: &'a EntrySummary) {
561 self.0 += summary.visible_file_count;
562 }
563}
564
565struct BackgroundScanner {
566 snapshot: Arc<Mutex<Snapshot>>,
567 notify: Sender<ScanState>,
568 other_mount_paths: HashSet<PathBuf>,
569 thread_pool: scoped_pool::Pool,
570 root_char_bag: CharBag,
571}
572
573impl BackgroundScanner {
574 fn new(snapshot: Arc<Mutex<Snapshot>>, notify: Sender<ScanState>, worktree_id: usize) -> Self {
575 let root_char_bag = CharBag::from(snapshot.lock().root_name_chars.as_slice());
576 let mut scanner = Self {
577 root_char_bag,
578 snapshot,
579 notify,
580 other_mount_paths: Default::default(),
581 thread_pool: scoped_pool::Pool::new(16, format!("worktree-{}-scanner", worktree_id)),
582 };
583 scanner.update_other_mount_paths();
584 scanner
585 }
586
587 fn update_other_mount_paths(&mut self) {
588 let path = self.snapshot.lock().abs_path.clone();
589 self.other_mount_paths.clear();
590 self.other_mount_paths.extend(
591 mounted_volume_paths()
592 .into_iter()
593 .filter(|mount_path| !path.starts_with(mount_path)),
594 );
595 }
596
597 fn abs_path(&self) -> Arc<Path> {
598 self.snapshot.lock().abs_path.clone()
599 }
600
601 fn snapshot(&self) -> Snapshot {
602 self.snapshot.lock().clone()
603 }
604
605 fn run(mut self, event_stream: fsevent::EventStream) {
606 if smol::block_on(self.notify.send(ScanState::Scanning)).is_err() {
607 return;
608 }
609
610 if let Err(err) = self.scan_dirs() {
611 if smol::block_on(self.notify.send(ScanState::Err(Arc::new(err)))).is_err() {
612 return;
613 }
614 }
615
616 if smol::block_on(self.notify.send(ScanState::Idle)).is_err() {
617 return;
618 }
619
620 return;
621
622 event_stream.run(move |events| {
623 if smol::block_on(self.notify.send(ScanState::Scanning)).is_err() {
624 return false;
625 }
626
627 if !self.process_events(events) {
628 return false;
629 }
630
631 if smol::block_on(self.notify.send(ScanState::Idle)).is_err() {
632 return false;
633 }
634
635 true
636 });
637 }
638
639 fn scan_dirs(&self) -> io::Result<()> {
640 self.snapshot.lock().scan_id += 1;
641
642 let path: Arc<Path> = Arc::from(Path::new(""));
643 let abs_path = self.abs_path();
644 let metadata = fs::metadata(&abs_path)?;
645 let inode = metadata.ino();
646 let is_symlink = fs::symlink_metadata(&abs_path)?.file_type().is_symlink();
647
648 if metadata.file_type().is_dir() {
649 let dir_entry = Entry {
650 kind: EntryKind::PendingDir,
651 path: path.clone(),
652 inode,
653 is_symlink,
654 is_ignored: false,
655 };
656 self.snapshot.lock().insert_entry(dir_entry);
657
658 let (tx, rx) = crossbeam_channel::unbounded();
659
660 tx.send(ScanJob {
661 abs_path: abs_path.to_path_buf(),
662 path,
663 ignore_stack: IgnoreStack::none(),
664 scan_queue: tx.clone(),
665 })
666 .unwrap();
667 drop(tx);
668
669 self.thread_pool.scoped(|pool| {
670 for _ in 0..self.thread_pool.thread_count() {
671 pool.execute(|| {
672 while let Ok(job) = rx.recv() {
673 if let Err(err) = self.scan_dir(&job) {
674 log::error!("error scanning {:?}: {}", job.abs_path, err);
675 }
676 }
677 });
678 }
679 });
680 } else {
681 self.snapshot.lock().insert_entry(Entry {
682 kind: EntryKind::File(self.char_bag(&path)),
683 path,
684 inode,
685 is_symlink,
686 is_ignored: false,
687 });
688 }
689
690 self.recompute_ignore_statuses();
691
692 Ok(())
693 }
694
695 fn scan_dir(&self, job: &ScanJob) -> io::Result<()> {
696 let mut new_entries: Vec<Entry> = Vec::new();
697 let mut new_jobs: Vec<ScanJob> = Vec::new();
698 let mut ignore_stack = job.ignore_stack.clone();
699 let mut new_ignore = None;
700
701 for child_entry in fs::read_dir(&job.abs_path)? {
702 let child_entry = child_entry?;
703 let child_name = child_entry.file_name();
704 let child_abs_path = job.abs_path.join(&child_name);
705 let child_path: Arc<Path> = job.path.join(&child_name).into();
706 let child_metadata = child_entry.metadata()?;
707 let child_inode = child_metadata.ino();
708 let child_is_symlink = child_metadata.file_type().is_symlink();
709
710 // Disallow mount points outside the file system containing the root of this worktree
711 if self.other_mount_paths.contains(&child_abs_path) {
712 continue;
713 }
714
715 // If we find a .gitignore, add it to the stack of ignores used to determine which paths are ignored
716 if child_name == *GITIGNORE {
717 let (ignore, err) = Gitignore::new(&child_abs_path);
718 if let Some(err) = err {
719 log::error!("error in ignore file {:?} - {:?}", child_path, err);
720 }
721 let ignore = Arc::new(ignore);
722 ignore_stack = ignore_stack.append(job.path.clone(), ignore.clone());
723 new_ignore = Some(ignore);
724
725 // Update ignore status of any child entries we've already processed to reflect the
726 // ignore file in the current directory. Because `.gitignore` starts with a `.`,
727 // there should rarely be too numerous. Update the ignore stack associated with any
728 // new jobs as well.
729 let mut new_jobs = new_jobs.iter_mut();
730 for entry in &mut new_entries {
731 entry.is_ignored = ignore_stack.is_path_ignored(&entry.path, entry.is_dir());
732 if entry.is_dir() {
733 new_jobs.next().unwrap().ignore_stack = if entry.is_ignored {
734 IgnoreStack::all()
735 } else {
736 ignore_stack.clone()
737 };
738 }
739 }
740 }
741
742 if child_metadata.is_dir() {
743 let is_ignored = ignore_stack.is_path_ignored(&child_path, true);
744 new_entries.push(Entry {
745 kind: EntryKind::PendingDir,
746 path: child_path.clone(),
747 inode: child_inode,
748 is_symlink: child_is_symlink,
749 is_ignored,
750 });
751 new_jobs.push(ScanJob {
752 abs_path: child_abs_path,
753 path: child_path,
754 ignore_stack: if is_ignored {
755 IgnoreStack::all()
756 } else {
757 ignore_stack.clone()
758 },
759 scan_queue: job.scan_queue.clone(),
760 });
761 } else {
762 let is_ignored = ignore_stack.is_path_ignored(&child_path, false);
763 new_entries.push(Entry {
764 kind: EntryKind::File(self.char_bag(&child_path)),
765 path: child_path,
766 inode: child_inode,
767 is_symlink: child_is_symlink,
768 is_ignored,
769 });
770 };
771 }
772
773 self.snapshot
774 .lock()
775 .populate_dir(job.path.clone(), new_entries, new_ignore);
776 for new_job in new_jobs {
777 job.scan_queue.send(new_job).unwrap();
778 }
779
780 Ok(())
781 }
782
783 fn process_events(&mut self, mut events: Vec<fsevent::Event>) -> bool {
784 self.update_other_mount_paths();
785
786 let mut snapshot = self.snapshot();
787 snapshot.scan_id += 1;
788
789 let root_abs_path = if let Ok(abs_path) = snapshot.abs_path.canonicalize() {
790 abs_path
791 } else {
792 return false;
793 };
794
795 events.sort_unstable_by(|a, b| a.path.cmp(&b.path));
796 let mut abs_paths = events.into_iter().map(|e| e.path).peekable();
797 let (scan_queue_tx, scan_queue_rx) = crossbeam_channel::unbounded();
798
799 while let Some(abs_path) = abs_paths.next() {
800 let path = match abs_path.strip_prefix(&root_abs_path) {
801 Ok(path) => Arc::from(path.to_path_buf()),
802 Err(_) => {
803 log::error!(
804 "unexpected event {:?} for root path {:?}",
805 abs_path,
806 root_abs_path
807 );
808 continue;
809 }
810 };
811
812 while abs_paths.peek().map_or(false, |p| p.starts_with(&abs_path)) {
813 abs_paths.next();
814 }
815
816 snapshot.remove_path(&path);
817
818 match self.fs_entry_for_path(path.clone(), &abs_path) {
819 Ok(Some(fs_entry)) => {
820 let is_dir = fs_entry.is_dir();
821 snapshot.insert_entry(fs_entry);
822 if is_dir {
823 scan_queue_tx
824 .send(ScanJob {
825 abs_path,
826 path,
827 ignore_stack: todo!(),
828 scan_queue: scan_queue_tx.clone(),
829 })
830 .unwrap();
831 }
832 }
833 Ok(None) => {}
834 Err(err) => {
835 // TODO - create a special 'error' entry in the entries tree to mark this
836 log::error!("error reading file on event {:?}", err);
837 }
838 }
839 }
840
841 *self.snapshot.lock() = snapshot;
842
843 // Scan any directories that were created as part of this event batch.
844 drop(scan_queue_tx);
845 self.thread_pool.scoped(|pool| {
846 for _ in 0..self.thread_pool.thread_count() {
847 pool.execute(|| {
848 while let Ok(job) = scan_queue_rx.recv() {
849 if let Err(err) = self.scan_dir(&job) {
850 log::error!("error scanning {:?}: {}", job.abs_path, err);
851 }
852 }
853 });
854 }
855 });
856
857 self.recompute_ignore_statuses();
858
859 true
860 }
861
862 fn recompute_ignore_statuses(&self) {
863 self.compute_ignore_status_for_new_ignores();
864 self.compute_ignore_status_for_new_entries();
865 }
866
867 fn compute_ignore_status_for_new_ignores(&self) {
868 // let mut snapshot = self.snapshot();
869
870 // let mut ignores_to_delete = Vec::new();
871 // let mut changed_ignore_parents = Vec::new();
872 // for (parent_path, (_, scan_id)) in &snapshot.ignores {
873 // let prev_ignore_parent = changed_ignore_parents.last();
874 // if *scan_id == snapshot.scan_id
875 // && prev_ignore_parent.map_or(true, |l| !parent_path.starts_with(l))
876 // {
877 // changed_ignore_parents.push(parent_path.clone());
878 // }
879
880 // let ignore_parent_exists = snapshot.entry_for_path(parent_path).is_some();
881 // let ignore_exists = snapshot
882 // .entry_for_path(parent_path.join(&*GITIGNORE))
883 // .is_some();
884 // if !ignore_parent_exists || !ignore_exists {
885 // ignores_to_delete.push(parent_path.clone());
886 // }
887 // }
888
889 // for parent_path in ignores_to_delete {
890 // snapshot.ignores.remove(&parent_path);
891 // self.snapshot.lock().ignores.remove(&parent_path);
892 // }
893
894 // let (entries_tx, entries_rx) = crossbeam_channel::unbounded();
895 // self.thread_pool.scoped(|scope| {
896 // let (edits_tx, edits_rx) = crossbeam_channel::unbounded();
897 // scope.execute(move || {
898 // let mut edits = Vec::new();
899 // while let Ok(edit) = edits_rx.recv() {
900 // edits.push(edit);
901 // while let Ok(edit) = edits_rx.try_recv() {
902 // edits.push(edit);
903 // }
904 // self.snapshot.lock().entries.edit(mem::take(&mut edits));
905 // }
906 // });
907
908 // scope.execute(|| {
909 // let entries_tx = entries_tx;
910 // let mut cursor = snapshot.entries.cursor::<_, ()>();
911 // for ignore_parent_path in &changed_ignore_parents {
912 // cursor.seek(&PathSearch::Exact(ignore_parent_path), SeekBias::Right);
913 // while let Some(entry) = cursor.item() {
914 // if entry.path().starts_with(ignore_parent_path) {
915 // entries_tx.send(entry.clone()).unwrap();
916 // cursor.next();
917 // } else {
918 // break;
919 // }
920 // }
921 // }
922 // });
923
924 // for _ in 0..self.thread_pool.thread_count() - 2 {
925 // let edits_tx = edits_tx.clone();
926 // scope.execute(|| {
927 // let edits_tx = edits_tx;
928 // while let Ok(mut entry) = entries_rx.recv() {
929 // entry.set_ignored(snapshot.is_path_ignored(entry.path()).unwrap());
930 // edits_tx.send(Edit::Insert(entry)).unwrap();
931 // }
932 // });
933 // }
934 // });
935 }
936
937 fn compute_ignore_status_for_new_entries(&self) {
938 // let snapshot = self.snapshot.lock().clone();
939
940 // let (entries_tx, entries_rx) = crossbeam_channel::unbounded();
941 // self.thread_pool.scoped(|scope| {
942 // let (edits_tx, edits_rx) = crossbeam_channel::unbounded();
943 // scope.execute(move || {
944 // let mut edits = Vec::new();
945 // while let Ok(edit) = edits_rx.recv() {
946 // edits.push(edit);
947 // while let Ok(edit) = edits_rx.try_recv() {
948 // edits.push(edit);
949 // }
950 // self.snapshot.lock().entries.edit(mem::take(&mut edits));
951 // }
952 // });
953
954 // scope.execute(|| {
955 // let entries_tx = entries_tx;
956 // for entry in snapshot
957 // .entries
958 // .filter::<_, ()>(|e| e.recompute_ignore_status)
959 // {
960 // entries_tx.send(entry.clone()).unwrap();
961 // }
962 // });
963
964 // for _ in 0..self.thread_pool.thread_count() - 2 {
965 // let edits_tx = edits_tx.clone();
966 // scope.execute(|| {
967 // let edits_tx = edits_tx;
968 // while let Ok(mut entry) = entries_rx.recv() {
969 // entry.set_ignored(snapshot.is_path_ignored(entry.path()).unwrap());
970 // edits_tx.send(Edit::Insert(entry)).unwrap();
971 // }
972 // });
973 // }
974 // });
975 }
976
977 fn fs_entry_for_path(&self, path: Arc<Path>, abs_path: &Path) -> Result<Option<Entry>> {
978 let metadata = match fs::metadata(&abs_path) {
979 Err(err) => {
980 return match (err.kind(), err.raw_os_error()) {
981 (io::ErrorKind::NotFound, _) => Ok(None),
982 (io::ErrorKind::Other, Some(libc::ENOTDIR)) => Ok(None),
983 _ => Err(anyhow::Error::new(err)),
984 }
985 }
986 Ok(metadata) => metadata,
987 };
988 let inode = metadata.ino();
989 let is_symlink = fs::symlink_metadata(&abs_path)
990 .context("failed to read symlink metadata")?
991 .file_type()
992 .is_symlink();
993
994 let entry = Entry {
995 kind: if metadata.file_type().is_dir() {
996 EntryKind::PendingDir
997 } else {
998 EntryKind::File(self.char_bag(&path))
999 },
1000 path,
1001 inode,
1002 is_symlink,
1003 is_ignored: false,
1004 };
1005
1006 Ok(Some(entry))
1007 }
1008
1009 fn char_bag(&self, path: &Path) -> CharBag {
1010 let mut result = self.root_char_bag;
1011 result.extend(path.to_string_lossy().chars());
1012 result
1013 }
1014}
1015
1016struct ScanJob {
1017 abs_path: PathBuf,
1018 path: Arc<Path>,
1019 ignore_stack: Arc<IgnoreStack>,
1020 scan_queue: crossbeam_channel::Sender<ScanJob>,
1021}
1022
1023pub trait WorktreeHandle {
1024 fn file(&self, path: impl AsRef<Path>, app: &AppContext) -> Result<FileHandle>;
1025}
1026
1027impl WorktreeHandle for ModelHandle<Worktree> {
1028 fn file(&self, path: impl AsRef<Path>, app: &AppContext) -> Result<FileHandle> {
1029 self.read(app)
1030 .entry_for_path(&path)
1031 .map(|entry| FileHandle {
1032 worktree: self.clone(),
1033 path: entry.path().clone(),
1034 })
1035 .ok_or_else(|| anyhow!("path does not exist in tree"))
1036 }
1037}
1038
1039pub enum FileIter<'a> {
1040 All(Cursor<'a, Entry, FileCount, FileCount>),
1041 Visible(Cursor<'a, Entry, VisibleFileCount, VisibleFileCount>),
1042}
1043
1044impl<'a> FileIter<'a> {
1045 fn all(snapshot: &'a Snapshot, start: usize) -> Self {
1046 let mut cursor = snapshot.entries.cursor();
1047 cursor.seek(&FileCount(start), SeekBias::Right);
1048 Self::All(cursor)
1049 }
1050
1051 fn visible(snapshot: &'a Snapshot, start: usize) -> Self {
1052 let mut cursor = snapshot.entries.cursor();
1053 cursor.seek(&VisibleFileCount(start), SeekBias::Right);
1054 Self::Visible(cursor)
1055 }
1056
1057 fn next_internal(&mut self) {
1058 match self {
1059 Self::All(cursor) => {
1060 let ix = *cursor.start();
1061 cursor.seek_forward(&FileCount(ix.0 + 1), SeekBias::Right);
1062 }
1063 Self::Visible(cursor) => {
1064 let ix = *cursor.start();
1065 cursor.seek_forward(&VisibleFileCount(ix.0 + 1), SeekBias::Right);
1066 }
1067 }
1068 }
1069
1070 fn item(&self) -> Option<&'a Entry> {
1071 match self {
1072 Self::All(cursor) => cursor.item(),
1073 Self::Visible(cursor) => cursor.item(),
1074 }
1075 }
1076}
1077
1078impl<'a> Iterator for FileIter<'a> {
1079 type Item = &'a Entry;
1080
1081 fn next(&mut self) -> Option<Self::Item> {
1082 if let Some(entry) = self.item() {
1083 self.next_internal();
1084 Some(entry)
1085 } else {
1086 None
1087 }
1088 }
1089}
1090
1091fn mounted_volume_paths() -> Vec<PathBuf> {
1092 unsafe {
1093 let mut stat_ptr: *mut libc::statfs = std::ptr::null_mut();
1094 let count = libc::getmntinfo(&mut stat_ptr as *mut _, libc::MNT_WAIT);
1095 if count >= 0 {
1096 std::slice::from_raw_parts(stat_ptr, count as usize)
1097 .iter()
1098 .map(|stat| {
1099 PathBuf::from(OsStr::from_bytes(
1100 CStr::from_ptr(&stat.f_mntonname[0]).to_bytes(),
1101 ))
1102 })
1103 .collect()
1104 } else {
1105 panic!("failed to run getmntinfo");
1106 }
1107 }
1108}
1109
1110#[cfg(test)]
1111mod tests {
1112 use super::*;
1113 use crate::editor::Buffer;
1114 use crate::test::*;
1115 use anyhow::Result;
1116 use gpui::App;
1117 use rand::prelude::*;
1118 use serde_json::json;
1119 use std::env;
1120 use std::fmt::Write;
1121 use std::os::unix;
1122 use std::time::{SystemTime, UNIX_EPOCH};
1123
1124 #[test]
1125 fn test_populate_and_search() {
1126 App::test_async((), |mut app| async move {
1127 let dir = temp_tree(json!({
1128 "root": {
1129 "apple": "",
1130 "banana": {
1131 "carrot": {
1132 "date": "",
1133 "endive": "",
1134 }
1135 },
1136 "fennel": {
1137 "grape": "",
1138 }
1139 }
1140 }));
1141
1142 let root_link_path = dir.path().join("root_link");
1143 unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
1144
1145 let tree = app.add_model(|ctx| Worktree::new(root_link_path, ctx));
1146
1147 app.read(|ctx| tree.read(ctx).scan_complete()).await;
1148 app.read(|ctx| {
1149 let tree = tree.read(ctx);
1150 assert_eq!(tree.file_count(), 4);
1151 let results = match_paths(
1152 Some(tree.snapshot()).iter(),
1153 "bna",
1154 false,
1155 false,
1156 false,
1157 10,
1158 ctx.thread_pool().clone(),
1159 )
1160 .into_iter()
1161 .map(|result| result.path)
1162 .collect::<Vec<Arc<Path>>>();
1163 assert_eq!(
1164 results,
1165 vec![
1166 PathBuf::from("banana/carrot/date").into(),
1167 PathBuf::from("banana/carrot/endive").into(),
1168 ]
1169 );
1170 })
1171 });
1172 }
1173
1174 #[test]
1175 fn test_save_file() {
1176 App::test_async((), |mut app| async move {
1177 let dir = temp_tree(json!({
1178 "file1": "the old contents",
1179 }));
1180
1181 let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
1182 app.read(|ctx| tree.read(ctx).scan_complete()).await;
1183 app.read(|ctx| assert_eq!(tree.read(ctx).file_count(), 1));
1184
1185 let buffer = Buffer::new(1, "a line of text.\n".repeat(10 * 1024));
1186
1187 let path = tree.update(&mut app, |tree, ctx| {
1188 let path = tree.files(0).next().unwrap().path().clone();
1189 assert_eq!(path.file_name().unwrap(), "file1");
1190 smol::block_on(tree.save(&path, buffer.snapshot(), ctx.as_ref())).unwrap();
1191 path
1192 });
1193
1194 let loaded_history = app
1195 .read(|ctx| tree.read(ctx).load_history(&path, ctx))
1196 .await
1197 .unwrap();
1198 assert_eq!(loaded_history.base_text.as_ref(), buffer.text());
1199 });
1200 }
1201
1202 #[test]
1203 fn test_rescan_simple() {
1204 App::test_async((), |mut app| async move {
1205 let dir = temp_tree(json!({
1206 "a": {
1207 "file1": "",
1208 },
1209 "b": {
1210 "c": {
1211 "file2": "",
1212 }
1213 }
1214 }));
1215
1216 let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
1217 app.read(|ctx| tree.read(ctx).scan_complete()).await;
1218 app.read(|ctx| assert_eq!(tree.read(ctx).file_count(), 2));
1219
1220 let file2 = app.read(|ctx| {
1221 let file2 = tree.file("b/c/file2", ctx).unwrap();
1222 assert_eq!(file2.path().as_ref(), Path::new("b/c/file2"));
1223 file2
1224 });
1225
1226 std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
1227
1228 app.read(|ctx| tree.read(ctx).next_scan_complete()).await;
1229
1230 app.read(|ctx| {
1231 assert_eq!(
1232 tree.read(ctx)
1233 .paths()
1234 .map(|p| p.to_str().unwrap())
1235 .collect::<Vec<_>>(),
1236 vec!["a", "a/file1", "b", "d", "d/file2"]
1237 )
1238 });
1239
1240 // tree.condition(&app, move |_, _| {
1241 // file2.path().as_ref() == Path::new("d/file2")
1242 // })
1243 // .await;
1244 });
1245 }
1246
1247 #[test]
1248 fn test_rescan_with_gitignore() {
1249 App::test_async((), |mut app| async move {
1250 let dir = temp_tree(json!({
1251 ".git": {},
1252 ".gitignore": "ignored-dir\n",
1253 "tracked-dir": {
1254 "tracked-file1": "tracked contents",
1255 },
1256 "ignored-dir": {
1257 "ignored-file1": "ignored contents",
1258 }
1259 }));
1260
1261 let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
1262 app.read(|ctx| tree.read(ctx).scan_complete()).await;
1263
1264 app.read(|ctx| {
1265 let paths = tree
1266 .read(ctx)
1267 .paths()
1268 .map(|p| p.to_str().unwrap())
1269 .collect::<Vec<_>>();
1270 println!("paths {:?}", paths);
1271 });
1272
1273 app.read(|ctx| {
1274 let tree = tree.read(ctx);
1275 let tracked = tree.entry_for_path("tracked-dir/tracked-file1").unwrap();
1276 let ignored = tree.entry_for_path("ignored-dir/ignored-file1").unwrap();
1277 assert_eq!(tracked.is_ignored(), false);
1278 assert_eq!(ignored.is_ignored(), true);
1279 });
1280
1281 return;
1282
1283 fs::write(dir.path().join("tracked-dir/tracked-file2"), "").unwrap();
1284 fs::write(dir.path().join("ignored-dir/ignored-file2"), "").unwrap();
1285 app.read(|ctx| tree.read(ctx).next_scan_complete()).await;
1286 app.read(|ctx| {
1287 let tree = tree.read(ctx);
1288 let tracked = tree.entry_for_path("tracked-dir/tracked-file2").unwrap();
1289 let ignored = tree.entry_for_path("ignored-dir/ignored-file2").unwrap();
1290 assert_eq!(tracked.is_ignored(), false);
1291 assert_eq!(ignored.is_ignored(), true);
1292 });
1293 });
1294 }
1295
1296 #[test]
1297 fn test_mounted_volume_paths() {
1298 let paths = mounted_volume_paths();
1299 assert!(paths.contains(&"/".into()));
1300 }
1301
1302 #[test]
1303 fn test_random() {
1304 let iterations = env::var("ITERATIONS")
1305 .map(|i| i.parse().unwrap())
1306 .unwrap_or(100);
1307 let operations = env::var("OPERATIONS")
1308 .map(|o| o.parse().unwrap())
1309 .unwrap_or(40);
1310 let initial_entries = env::var("INITIAL_ENTRIES")
1311 .map(|o| o.parse().unwrap())
1312 .unwrap_or(20);
1313 let seeds = if let Ok(seed) = env::var("SEED").map(|s| s.parse().unwrap()) {
1314 seed..seed + 1
1315 } else {
1316 0..iterations
1317 };
1318
1319 for seed in seeds {
1320 dbg!(seed);
1321 let mut rng = StdRng::seed_from_u64(seed);
1322
1323 let root_dir = tempdir::TempDir::new(&format!("test-{}", seed)).unwrap();
1324 for _ in 0..initial_entries {
1325 randomly_mutate_tree(root_dir.path(), 1.0, &mut rng).unwrap();
1326 }
1327 log::info!("Generated initial tree");
1328
1329 let (notify_tx, _notify_rx) = smol::channel::unbounded();
1330 let mut scanner = BackgroundScanner::new(
1331 Arc::new(Mutex::new(Snapshot {
1332 id: 0,
1333 scan_id: 0,
1334 abs_path: root_dir.path().into(),
1335 entries: Default::default(),
1336 ignores: Default::default(),
1337 root_name_chars: Default::default(),
1338 })),
1339 notify_tx,
1340 0,
1341 );
1342 scanner.scan_dirs().unwrap();
1343 scanner.snapshot().check_invariants();
1344
1345 let mut events = Vec::new();
1346 let mut mutations_len = operations;
1347 while mutations_len > 1 {
1348 if !events.is_empty() && rng.gen_bool(0.4) {
1349 let len = rng.gen_range(0..=events.len());
1350 let to_deliver = events.drain(0..len).collect::<Vec<_>>();
1351 log::info!("Delivering events: {:#?}", to_deliver);
1352 scanner.process_events(to_deliver);
1353 scanner.snapshot().check_invariants();
1354 } else {
1355 events.extend(randomly_mutate_tree(root_dir.path(), 0.6, &mut rng).unwrap());
1356 mutations_len -= 1;
1357 }
1358 }
1359 log::info!("Quiescing: {:#?}", events);
1360 scanner.process_events(events);
1361 scanner.snapshot().check_invariants();
1362
1363 let (notify_tx, _notify_rx) = smol::channel::unbounded();
1364 let new_scanner = BackgroundScanner::new(
1365 Arc::new(Mutex::new(Snapshot {
1366 id: 0,
1367 scan_id: 0,
1368 abs_path: root_dir.path().into(),
1369 entries: Default::default(),
1370 ignores: Default::default(),
1371 root_name_chars: Default::default(),
1372 })),
1373 notify_tx,
1374 1,
1375 );
1376 new_scanner.scan_dirs().unwrap();
1377 assert_eq!(scanner.snapshot().to_vec(), new_scanner.snapshot().to_vec());
1378 }
1379 }
1380
1381 fn randomly_mutate_tree(
1382 root_path: &Path,
1383 insertion_probability: f64,
1384 rng: &mut impl Rng,
1385 ) -> Result<Vec<fsevent::Event>> {
1386 let root_path = root_path.canonicalize().unwrap();
1387 let (dirs, files) = read_dir_recursive(root_path.clone());
1388
1389 let mut events = Vec::new();
1390 let mut record_event = |path: PathBuf| {
1391 events.push(fsevent::Event {
1392 event_id: SystemTime::now()
1393 .duration_since(UNIX_EPOCH)
1394 .unwrap()
1395 .as_secs(),
1396 flags: fsevent::StreamFlags::empty(),
1397 path,
1398 });
1399 };
1400
1401 if (files.is_empty() && dirs.len() == 1) || rng.gen_bool(insertion_probability) {
1402 let path = dirs.choose(rng).unwrap();
1403 let new_path = path.join(gen_name(rng));
1404
1405 if rng.gen() {
1406 log::info!("Creating dir {:?}", new_path.strip_prefix(root_path)?);
1407 fs::create_dir(&new_path)?;
1408 } else {
1409 log::info!("Creating file {:?}", new_path.strip_prefix(root_path)?);
1410 fs::write(&new_path, "")?;
1411 }
1412 record_event(new_path);
1413 } else if rng.gen_bool(0.05) {
1414 let ignore_dir_path = dirs.choose(rng).unwrap();
1415 let ignore_path = ignore_dir_path.join(&*GITIGNORE);
1416
1417 let (subdirs, subfiles) = read_dir_recursive(ignore_dir_path.clone());
1418 let files_to_ignore = {
1419 let len = rng.gen_range(0..=subfiles.len());
1420 subfiles.choose_multiple(rng, len)
1421 };
1422 let dirs_to_ignore = {
1423 let len = rng.gen_range(0..subdirs.len());
1424 subdirs.choose_multiple(rng, len)
1425 };
1426
1427 let mut ignore_contents = String::new();
1428 for path_to_ignore in files_to_ignore.chain(dirs_to_ignore) {
1429 write!(
1430 ignore_contents,
1431 "{}\n",
1432 path_to_ignore
1433 .strip_prefix(&ignore_dir_path)?
1434 .to_str()
1435 .unwrap()
1436 )
1437 .unwrap();
1438 }
1439 log::info!(
1440 "Creating {:?} with contents:\n{}",
1441 ignore_path.strip_prefix(&root_path)?,
1442 ignore_contents
1443 );
1444 fs::write(&ignore_path, ignore_contents).unwrap();
1445 record_event(ignore_path);
1446 } else {
1447 let old_path = {
1448 let file_path = files.choose(rng);
1449 let dir_path = dirs[1..].choose(rng);
1450 file_path.into_iter().chain(dir_path).choose(rng).unwrap()
1451 };
1452
1453 let is_rename = rng.gen();
1454 if is_rename {
1455 let new_path_parent = dirs
1456 .iter()
1457 .filter(|d| !d.starts_with(old_path))
1458 .choose(rng)
1459 .unwrap();
1460
1461 let overwrite_existing_dir =
1462 !old_path.starts_with(&new_path_parent) && rng.gen_bool(0.3);
1463 let new_path = if overwrite_existing_dir {
1464 fs::remove_dir_all(&new_path_parent).ok();
1465 new_path_parent.to_path_buf()
1466 } else {
1467 new_path_parent.join(gen_name(rng))
1468 };
1469
1470 log::info!(
1471 "Renaming {:?} to {}{:?}",
1472 old_path.strip_prefix(&root_path)?,
1473 if overwrite_existing_dir {
1474 "overwrite "
1475 } else {
1476 ""
1477 },
1478 new_path.strip_prefix(&root_path)?
1479 );
1480 fs::rename(&old_path, &new_path)?;
1481 record_event(old_path.clone());
1482 record_event(new_path);
1483 } else if old_path.is_dir() {
1484 let (dirs, files) = read_dir_recursive(old_path.clone());
1485
1486 log::info!("Deleting dir {:?}", old_path.strip_prefix(&root_path)?);
1487 fs::remove_dir_all(&old_path).unwrap();
1488 for file in files {
1489 record_event(file);
1490 }
1491 for dir in dirs {
1492 record_event(dir);
1493 }
1494 } else {
1495 log::info!("Deleting file {:?}", old_path.strip_prefix(&root_path)?);
1496 fs::remove_file(old_path).unwrap();
1497 record_event(old_path.clone());
1498 }
1499 }
1500
1501 Ok(events)
1502 }
1503
1504 fn read_dir_recursive(path: PathBuf) -> (Vec<PathBuf>, Vec<PathBuf>) {
1505 let child_entries = fs::read_dir(&path).unwrap();
1506 let mut dirs = vec![path];
1507 let mut files = Vec::new();
1508 for child_entry in child_entries {
1509 let child_path = child_entry.unwrap().path();
1510 if child_path.is_dir() {
1511 let (child_dirs, child_files) = read_dir_recursive(child_path);
1512 dirs.extend(child_dirs);
1513 files.extend(child_files);
1514 } else {
1515 files.push(child_path);
1516 }
1517 }
1518 (dirs, files)
1519 }
1520
1521 fn gen_name(rng: &mut impl Rng) -> String {
1522 (0..6)
1523 .map(|_| rng.sample(rand::distributions::Alphanumeric))
1524 .map(char::from)
1525 .collect()
1526 }
1527
1528 impl Snapshot {
1529 fn check_invariants(&self) {
1530 let mut files = self.files(0);
1531 let mut visible_files = self.visible_files(0);
1532 for entry in self.entries.cursor::<(), ()>() {
1533 if matches!(entry.kind, EntryKind::File(_)) {
1534 assert_eq!(files.next().unwrap().inode(), entry.inode);
1535 if !entry.is_ignored {
1536 assert_eq!(visible_files.next().unwrap().inode(), entry.inode);
1537 }
1538 }
1539 }
1540 assert!(files.next().is_none());
1541 assert!(visible_files.next().is_none());
1542
1543 // for (ignore_parent_path, _) in &self.ignores {
1544 // assert!(self.entry_for_path(ignore_parent_path).is_some());
1545 // assert!(self
1546 // .entry_for_path(ignore_parent_path.join(&*GITIGNORE))
1547 // .is_some());
1548 // }
1549 }
1550
1551 fn to_vec(&self) -> Vec<(&Path, u64, bool)> {
1552 let mut paths = Vec::new();
1553 for entry in self.entries.cursor::<(), ()>() {
1554 paths.push((entry.path().as_ref(), entry.inode(), entry.is_ignored()));
1555 }
1556 paths.sort_by(|a, b| a.0.cmp(&b.0));
1557 paths
1558 }
1559 }
1560}