1use crate::{
2 copy_recursive, ignore::IgnoreStack, DiagnosticSummary, ProjectEntryId, RemoveOptions,
3};
4use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
5use anyhow::{anyhow, Context, Result};
6use client::{proto, Client};
7use clock::ReplicaId;
8use collections::{HashMap, VecDeque};
9use fs::{repository::GitRepository, Fs, LineEnding};
10use futures::{
11 channel::{
12 mpsc::{self, UnboundedSender},
13 oneshot,
14 },
15 select_biased,
16 task::Poll,
17 Stream, StreamExt,
18};
19use fuzzy::CharBag;
20use git::{DOT_GIT, GITIGNORE};
21use gpui::{executor, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task};
22use language::{
23 proto::{
24 deserialize_fingerprint, deserialize_version, serialize_fingerprint, serialize_line_ending,
25 serialize_version,
26 },
27 Buffer, DiagnosticEntry, File as _, PointUtf16, Rope, RopeFingerprint, Unclipped,
28};
29use parking_lot::Mutex;
30use postage::{
31 barrier,
32 prelude::{Sink as _, Stream as _},
33 watch,
34};
35use smol::channel::{self, Sender};
36use std::{
37 any::Any,
38 cmp::{self, Ordering},
39 convert::TryFrom,
40 ffi::OsStr,
41 fmt,
42 future::Future,
43 mem,
44 ops::{Deref, DerefMut},
45 path::{Path, PathBuf},
46 pin::Pin,
47 sync::{
48 atomic::{AtomicUsize, Ordering::SeqCst},
49 Arc,
50 },
51 time::{Duration, SystemTime},
52};
53use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet};
54use util::{paths::HOME, ResultExt, TryFutureExt};
55
56#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
57pub struct WorktreeId(usize);
58
59pub enum Worktree {
60 Local(LocalWorktree),
61 Remote(RemoteWorktree),
62}
63
64pub struct LocalWorktree {
65 snapshot: LocalSnapshot,
66 path_changes_tx: channel::Sender<(Vec<PathBuf>, barrier::Sender)>,
67 is_scanning: (watch::Sender<bool>, watch::Receiver<bool>),
68 _background_scanner_task: Task<()>,
69 share: Option<ShareState>,
70 diagnostics: HashMap<Arc<Path>, Vec<DiagnosticEntry<Unclipped<PointUtf16>>>>,
71 diagnostic_summaries: TreeMap<PathKey, DiagnosticSummary>,
72 client: Arc<Client>,
73 fs: Arc<dyn Fs>,
74 visible: bool,
75}
76
77pub struct RemoteWorktree {
78 snapshot: Snapshot,
79 background_snapshot: Arc<Mutex<Snapshot>>,
80 project_id: u64,
81 client: Arc<Client>,
82 updates_tx: Option<UnboundedSender<proto::UpdateWorktree>>,
83 snapshot_subscriptions: VecDeque<(usize, oneshot::Sender<()>)>,
84 replica_id: ReplicaId,
85 diagnostic_summaries: TreeMap<PathKey, DiagnosticSummary>,
86 visible: bool,
87 disconnected: bool,
88}
89
90#[derive(Clone)]
91pub struct Snapshot {
92 id: WorktreeId,
93 abs_path: Arc<Path>,
94 root_name: String,
95 root_char_bag: CharBag,
96 entries_by_path: SumTree<Entry>,
97 entries_by_id: SumTree<PathEntry>,
98
99 /// A number that increases every time the worktree begins scanning
100 /// a set of paths from the filesystem. This scanning could be caused
101 /// by some operation performed on the worktree, such as reading or
102 /// writing a file, or by an event reported by the filesystem.
103 scan_id: usize,
104
105 /// The latest scan id that has completed, and whose preceding scans
106 /// have all completed. The current `scan_id` could be more than one
107 /// greater than the `completed_scan_id` if operations are performed
108 /// on the worktree while it is processing a file-system event.
109 completed_scan_id: usize,
110}
111
112#[derive(Clone)]
113pub struct GitRepositoryEntry {
114 pub(crate) repo: Arc<Mutex<dyn GitRepository>>,
115
116 pub(crate) scan_id: usize,
117 // Path to folder containing the .git file or directory
118 pub(crate) content_path: Arc<Path>,
119 // Path to the actual .git folder.
120 // Note: if .git is a file, this points to the folder indicated by the .git file
121 pub(crate) git_dir_path: Arc<Path>,
122}
123
124impl std::fmt::Debug for GitRepositoryEntry {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 f.debug_struct("GitRepositoryEntry")
127 .field("content_path", &self.content_path)
128 .field("git_dir_path", &self.git_dir_path)
129 .finish()
130 }
131}
132
133#[derive(Debug)]
134pub struct LocalSnapshot {
135 ignores_by_parent_abs_path: HashMap<Arc<Path>, (Arc<Gitignore>, usize)>,
136 git_repositories: Vec<GitRepositoryEntry>,
137 removed_entry_ids: HashMap<u64, ProjectEntryId>,
138 next_entry_id: Arc<AtomicUsize>,
139 snapshot: Snapshot,
140}
141
142impl Clone for LocalSnapshot {
143 fn clone(&self) -> Self {
144 Self {
145 ignores_by_parent_abs_path: self.ignores_by_parent_abs_path.clone(),
146 git_repositories: self.git_repositories.iter().cloned().collect(),
147 removed_entry_ids: self.removed_entry_ids.clone(),
148 next_entry_id: self.next_entry_id.clone(),
149 snapshot: self.snapshot.clone(),
150 }
151 }
152}
153
154impl Deref for LocalSnapshot {
155 type Target = Snapshot;
156
157 fn deref(&self) -> &Self::Target {
158 &self.snapshot
159 }
160}
161
162impl DerefMut for LocalSnapshot {
163 fn deref_mut(&mut self) -> &mut Self::Target {
164 &mut self.snapshot
165 }
166}
167
168enum ScanState {
169 Started,
170 Updated {
171 snapshot: LocalSnapshot,
172 changes: HashMap<Arc<Path>, PathChange>,
173 barrier: Option<barrier::Sender>,
174 scanning: bool,
175 },
176}
177
178struct ShareState {
179 project_id: u64,
180 snapshots_tx: watch::Sender<LocalSnapshot>,
181 resume_updates: watch::Sender<()>,
182 _maintain_remote_snapshot: Task<Option<()>>,
183}
184
185pub enum Event {
186 UpdatedEntries(HashMap<Arc<Path>, PathChange>),
187 UpdatedGitRepositories(Vec<GitRepositoryEntry>),
188}
189
190impl Entity for Worktree {
191 type Event = Event;
192}
193
194impl Worktree {
195 pub async fn local(
196 client: Arc<Client>,
197 path: impl Into<Arc<Path>>,
198 visible: bool,
199 fs: Arc<dyn Fs>,
200 next_entry_id: Arc<AtomicUsize>,
201 cx: &mut AsyncAppContext,
202 ) -> Result<ModelHandle<Self>> {
203 // After determining whether the root entry is a file or a directory, populate the
204 // snapshot's "root name", which will be used for the purpose of fuzzy matching.
205 let abs_path = path.into();
206 let metadata = fs
207 .metadata(&abs_path)
208 .await
209 .context("failed to stat worktree path")?;
210
211 Ok(cx.add_model(move |cx: &mut ModelContext<Worktree>| {
212 let root_name = abs_path
213 .file_name()
214 .map_or(String::new(), |f| f.to_string_lossy().to_string());
215
216 let mut snapshot = LocalSnapshot {
217 ignores_by_parent_abs_path: Default::default(),
218 git_repositories: Default::default(),
219 removed_entry_ids: Default::default(),
220 next_entry_id,
221 snapshot: Snapshot {
222 id: WorktreeId::from_usize(cx.model_id()),
223 abs_path: abs_path.clone(),
224 root_name: root_name.clone(),
225 root_char_bag: root_name.chars().map(|c| c.to_ascii_lowercase()).collect(),
226 entries_by_path: Default::default(),
227 entries_by_id: Default::default(),
228 scan_id: 1,
229 completed_scan_id: 0,
230 },
231 };
232
233 if let Some(metadata) = metadata {
234 snapshot.insert_entry(
235 Entry::new(
236 Arc::from(Path::new("")),
237 &metadata,
238 &snapshot.next_entry_id,
239 snapshot.root_char_bag,
240 ),
241 fs.as_ref(),
242 );
243 }
244
245 let (path_changes_tx, path_changes_rx) = channel::unbounded();
246 let (scan_states_tx, mut scan_states_rx) = mpsc::unbounded();
247
248 cx.spawn_weak(|this, mut cx| async move {
249 while let Some((state, this)) = scan_states_rx.next().await.zip(this.upgrade(&cx)) {
250 this.update(&mut cx, |this, cx| {
251 let this = this.as_local_mut().unwrap();
252 match state {
253 ScanState::Started => {
254 *this.is_scanning.0.borrow_mut() = true;
255 }
256 ScanState::Updated {
257 snapshot,
258 changes,
259 barrier,
260 scanning,
261 } => {
262 *this.is_scanning.0.borrow_mut() = scanning;
263 this.set_snapshot(snapshot, cx);
264 cx.emit(Event::UpdatedEntries(changes));
265 drop(barrier);
266 }
267 }
268 cx.notify();
269 });
270 }
271 })
272 .detach();
273
274 let background_scanner_task = cx.background().spawn({
275 let fs = fs.clone();
276 let snapshot = snapshot.clone();
277 let background = cx.background().clone();
278 async move {
279 let events = fs.watch(&abs_path, Duration::from_millis(100)).await;
280 BackgroundScanner::new(
281 snapshot,
282 fs,
283 scan_states_tx,
284 background,
285 path_changes_rx,
286 )
287 .run(events)
288 .await;
289 }
290 });
291
292 Worktree::Local(LocalWorktree {
293 snapshot,
294 is_scanning: watch::channel_with(true),
295 share: None,
296 path_changes_tx,
297 _background_scanner_task: background_scanner_task,
298 diagnostics: Default::default(),
299 diagnostic_summaries: Default::default(),
300 client,
301 fs,
302 visible,
303 })
304 }))
305 }
306
307 pub fn remote(
308 project_remote_id: u64,
309 replica_id: ReplicaId,
310 worktree: proto::WorktreeMetadata,
311 client: Arc<Client>,
312 cx: &mut AppContext,
313 ) -> ModelHandle<Self> {
314 cx.add_model(|cx: &mut ModelContext<Self>| {
315 let snapshot = Snapshot {
316 id: WorktreeId(worktree.id as usize),
317 abs_path: Arc::from(PathBuf::from(worktree.abs_path)),
318 root_name: worktree.root_name.clone(),
319 root_char_bag: worktree
320 .root_name
321 .chars()
322 .map(|c| c.to_ascii_lowercase())
323 .collect(),
324 entries_by_path: Default::default(),
325 entries_by_id: Default::default(),
326 scan_id: 1,
327 completed_scan_id: 0,
328 };
329
330 let (updates_tx, mut updates_rx) = mpsc::unbounded();
331 let background_snapshot = Arc::new(Mutex::new(snapshot.clone()));
332 let (mut snapshot_updated_tx, mut snapshot_updated_rx) = watch::channel();
333
334 cx.background()
335 .spawn({
336 let background_snapshot = background_snapshot.clone();
337 async move {
338 while let Some(update) = updates_rx.next().await {
339 if let Err(error) =
340 background_snapshot.lock().apply_remote_update(update)
341 {
342 log::error!("error applying worktree update: {}", error);
343 }
344 snapshot_updated_tx.send(()).await.ok();
345 }
346 }
347 })
348 .detach();
349
350 cx.spawn_weak(|this, mut cx| async move {
351 while (snapshot_updated_rx.recv().await).is_some() {
352 if let Some(this) = this.upgrade(&cx) {
353 this.update(&mut cx, |this, cx| {
354 let this = this.as_remote_mut().unwrap();
355 this.snapshot = this.background_snapshot.lock().clone();
356 cx.emit(Event::UpdatedEntries(Default::default()));
357 cx.notify();
358 while let Some((scan_id, _)) = this.snapshot_subscriptions.front() {
359 if this.observed_snapshot(*scan_id) {
360 let (_, tx) = this.snapshot_subscriptions.pop_front().unwrap();
361 let _ = tx.send(());
362 } else {
363 break;
364 }
365 }
366 });
367 } else {
368 break;
369 }
370 }
371 })
372 .detach();
373
374 Worktree::Remote(RemoteWorktree {
375 project_id: project_remote_id,
376 replica_id,
377 snapshot: snapshot.clone(),
378 background_snapshot,
379 updates_tx: Some(updates_tx),
380 snapshot_subscriptions: Default::default(),
381 client: client.clone(),
382 diagnostic_summaries: Default::default(),
383 visible: worktree.visible,
384 disconnected: false,
385 })
386 })
387 }
388
389 pub fn as_local(&self) -> Option<&LocalWorktree> {
390 if let Worktree::Local(worktree) = self {
391 Some(worktree)
392 } else {
393 None
394 }
395 }
396
397 pub fn as_remote(&self) -> Option<&RemoteWorktree> {
398 if let Worktree::Remote(worktree) = self {
399 Some(worktree)
400 } else {
401 None
402 }
403 }
404
405 pub fn as_local_mut(&mut self) -> Option<&mut LocalWorktree> {
406 if let Worktree::Local(worktree) = self {
407 Some(worktree)
408 } else {
409 None
410 }
411 }
412
413 pub fn as_remote_mut(&mut self) -> Option<&mut RemoteWorktree> {
414 if let Worktree::Remote(worktree) = self {
415 Some(worktree)
416 } else {
417 None
418 }
419 }
420
421 pub fn is_local(&self) -> bool {
422 matches!(self, Worktree::Local(_))
423 }
424
425 pub fn is_remote(&self) -> bool {
426 !self.is_local()
427 }
428
429 pub fn snapshot(&self) -> Snapshot {
430 match self {
431 Worktree::Local(worktree) => worktree.snapshot().snapshot,
432 Worktree::Remote(worktree) => worktree.snapshot(),
433 }
434 }
435
436 pub fn scan_id(&self) -> usize {
437 match self {
438 Worktree::Local(worktree) => worktree.snapshot.scan_id,
439 Worktree::Remote(worktree) => worktree.snapshot.scan_id,
440 }
441 }
442
443 pub fn completed_scan_id(&self) -> usize {
444 match self {
445 Worktree::Local(worktree) => worktree.snapshot.completed_scan_id,
446 Worktree::Remote(worktree) => worktree.snapshot.completed_scan_id,
447 }
448 }
449
450 pub fn is_visible(&self) -> bool {
451 match self {
452 Worktree::Local(worktree) => worktree.visible,
453 Worktree::Remote(worktree) => worktree.visible,
454 }
455 }
456
457 pub fn replica_id(&self) -> ReplicaId {
458 match self {
459 Worktree::Local(_) => 0,
460 Worktree::Remote(worktree) => worktree.replica_id,
461 }
462 }
463
464 pub fn diagnostic_summaries(
465 &self,
466 ) -> impl Iterator<Item = (Arc<Path>, DiagnosticSummary)> + '_ {
467 match self {
468 Worktree::Local(worktree) => &worktree.diagnostic_summaries,
469 Worktree::Remote(worktree) => &worktree.diagnostic_summaries,
470 }
471 .iter()
472 .map(|(path, summary)| (path.0.clone(), *summary))
473 }
474
475 pub fn abs_path(&self) -> Arc<Path> {
476 match self {
477 Worktree::Local(worktree) => worktree.abs_path.clone(),
478 Worktree::Remote(worktree) => worktree.abs_path.clone(),
479 }
480 }
481}
482
483impl LocalWorktree {
484 pub fn contains_abs_path(&self, path: &Path) -> bool {
485 path.starts_with(&self.abs_path)
486 }
487
488 fn absolutize(&self, path: &Path) -> PathBuf {
489 if path.file_name().is_some() {
490 self.abs_path.join(path)
491 } else {
492 self.abs_path.to_path_buf()
493 }
494 }
495
496 pub(crate) fn load_buffer(
497 &mut self,
498 path: &Path,
499 cx: &mut ModelContext<Worktree>,
500 ) -> Task<Result<ModelHandle<Buffer>>> {
501 let path = Arc::from(path);
502 cx.spawn(move |this, mut cx| async move {
503 let (file, contents, diff_base) = this
504 .update(&mut cx, |t, cx| t.as_local().unwrap().load(&path, cx))
505 .await?;
506 Ok(cx.add_model(|cx| {
507 let mut buffer = Buffer::from_file(0, contents, diff_base, Arc::new(file), cx);
508 buffer.git_diff_recalc(cx);
509 buffer
510 }))
511 })
512 }
513
514 pub fn diagnostics_for_path(
515 &self,
516 path: &Path,
517 ) -> Option<Vec<DiagnosticEntry<Unclipped<PointUtf16>>>> {
518 self.diagnostics.get(path).cloned()
519 }
520
521 pub fn update_diagnostics(
522 &mut self,
523 language_server_id: usize,
524 worktree_path: Arc<Path>,
525 diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
526 _: &mut ModelContext<Worktree>,
527 ) -> Result<bool> {
528 self.diagnostics.remove(&worktree_path);
529 let old_summary = self
530 .diagnostic_summaries
531 .remove(&PathKey(worktree_path.clone()))
532 .unwrap_or_default();
533 let new_summary = DiagnosticSummary::new(language_server_id, &diagnostics);
534 if !new_summary.is_empty() {
535 self.diagnostic_summaries
536 .insert(PathKey(worktree_path.clone()), new_summary);
537 self.diagnostics.insert(worktree_path.clone(), diagnostics);
538 }
539
540 let updated = !old_summary.is_empty() || !new_summary.is_empty();
541 if updated {
542 if let Some(share) = self.share.as_ref() {
543 self.client
544 .send(proto::UpdateDiagnosticSummary {
545 project_id: share.project_id,
546 worktree_id: self.id().to_proto(),
547 summary: Some(proto::DiagnosticSummary {
548 path: worktree_path.to_string_lossy().to_string(),
549 language_server_id: language_server_id as u64,
550 error_count: new_summary.error_count as u32,
551 warning_count: new_summary.warning_count as u32,
552 }),
553 })
554 .log_err();
555 }
556 }
557
558 Ok(updated)
559 }
560
561 fn set_snapshot(&mut self, new_snapshot: LocalSnapshot, cx: &mut ModelContext<Worktree>) {
562 let updated_repos = Self::changed_repos(
563 &self.snapshot.git_repositories,
564 &new_snapshot.git_repositories,
565 );
566 self.snapshot = new_snapshot;
567
568 if let Some(share) = self.share.as_mut() {
569 *share.snapshots_tx.borrow_mut() = self.snapshot.clone();
570 }
571
572 if !updated_repos.is_empty() {
573 cx.emit(Event::UpdatedGitRepositories(updated_repos));
574 }
575 }
576
577 fn changed_repos(
578 old_repos: &[GitRepositoryEntry],
579 new_repos: &[GitRepositoryEntry],
580 ) -> Vec<GitRepositoryEntry> {
581 fn diff<'a>(
582 a: &'a [GitRepositoryEntry],
583 b: &'a [GitRepositoryEntry],
584 updated: &mut HashMap<&'a Path, GitRepositoryEntry>,
585 ) {
586 for a_repo in a {
587 let matched = b.iter().find(|b_repo| {
588 a_repo.git_dir_path == b_repo.git_dir_path && a_repo.scan_id == b_repo.scan_id
589 });
590
591 if matched.is_none() {
592 updated.insert(a_repo.git_dir_path.as_ref(), a_repo.clone());
593 }
594 }
595 }
596
597 let mut updated = HashMap::<&Path, GitRepositoryEntry>::default();
598
599 diff(old_repos, new_repos, &mut updated);
600 diff(new_repos, old_repos, &mut updated);
601
602 updated.into_values().collect()
603 }
604
605 pub fn scan_complete(&self) -> impl Future<Output = ()> {
606 let mut is_scanning_rx = self.is_scanning.1.clone();
607 async move {
608 let mut is_scanning = is_scanning_rx.borrow().clone();
609 while is_scanning {
610 if let Some(value) = is_scanning_rx.recv().await {
611 is_scanning = value;
612 } else {
613 break;
614 }
615 }
616 }
617 }
618
619 pub fn snapshot(&self) -> LocalSnapshot {
620 self.snapshot.clone()
621 }
622
623 pub fn metadata_proto(&self) -> proto::WorktreeMetadata {
624 proto::WorktreeMetadata {
625 id: self.id().to_proto(),
626 root_name: self.root_name().to_string(),
627 visible: self.visible,
628 abs_path: self.abs_path().as_os_str().to_string_lossy().into(),
629 }
630 }
631
632 fn load(
633 &self,
634 path: &Path,
635 cx: &mut ModelContext<Worktree>,
636 ) -> Task<Result<(File, String, Option<String>)>> {
637 let handle = cx.handle();
638 let path = Arc::from(path);
639 let abs_path = self.absolutize(&path);
640 let fs = self.fs.clone();
641 let snapshot = self.snapshot();
642
643 cx.spawn(|this, mut cx| async move {
644 let text = fs.load(&abs_path).await?;
645
646 let diff_base = if let Some(repo) = snapshot.repo_for(&path) {
647 if let Ok(repo_relative) = path.strip_prefix(repo.content_path) {
648 let repo_relative = repo_relative.to_owned();
649 cx.background()
650 .spawn(async move { repo.repo.lock().load_index_text(&repo_relative) })
651 .await
652 } else {
653 None
654 }
655 } else {
656 None
657 };
658
659 // Eagerly populate the snapshot with an updated entry for the loaded file
660 let entry = this
661 .update(&mut cx, |this, cx| {
662 this.as_local().unwrap().refresh_entry(path, None, cx)
663 })
664 .await?;
665
666 Ok((
667 File {
668 entry_id: entry.id,
669 worktree: handle,
670 path: entry.path,
671 mtime: entry.mtime,
672 is_local: true,
673 is_deleted: false,
674 },
675 text,
676 diff_base,
677 ))
678 })
679 }
680
681 pub fn save_buffer(
682 &self,
683 buffer_handle: ModelHandle<Buffer>,
684 path: Arc<Path>,
685 has_changed_file: bool,
686 cx: &mut ModelContext<Worktree>,
687 ) -> Task<Result<(clock::Global, RopeFingerprint, SystemTime)>> {
688 let handle = cx.handle();
689 let buffer = buffer_handle.read(cx);
690
691 let rpc = self.client.clone();
692 let buffer_id = buffer.remote_id();
693 let project_id = self.share.as_ref().map(|share| share.project_id);
694
695 let text = buffer.as_rope().clone();
696 let fingerprint = text.fingerprint();
697 let version = buffer.version();
698 let save = self.write_file(path, text, buffer.line_ending(), cx);
699
700 cx.as_mut().spawn(|mut cx| async move {
701 let entry = save.await?;
702
703 if has_changed_file {
704 let new_file = Arc::new(File {
705 entry_id: entry.id,
706 worktree: handle,
707 path: entry.path,
708 mtime: entry.mtime,
709 is_local: true,
710 is_deleted: false,
711 });
712
713 if let Some(project_id) = project_id {
714 rpc.send(proto::UpdateBufferFile {
715 project_id,
716 buffer_id,
717 file: Some(new_file.to_proto()),
718 })
719 .log_err();
720 }
721
722 buffer_handle.update(&mut cx, |buffer, cx| {
723 if has_changed_file {
724 buffer.file_updated(new_file, cx).detach();
725 }
726 });
727 }
728
729 if let Some(project_id) = project_id {
730 rpc.send(proto::BufferSaved {
731 project_id,
732 buffer_id,
733 version: serialize_version(&version),
734 mtime: Some(entry.mtime.into()),
735 fingerprint: serialize_fingerprint(fingerprint),
736 })?;
737 }
738
739 buffer_handle.update(&mut cx, |buffer, cx| {
740 buffer.did_save(version.clone(), fingerprint, entry.mtime, cx);
741 });
742
743 Ok((version, fingerprint, entry.mtime))
744 })
745 }
746
747 pub fn create_entry(
748 &self,
749 path: impl Into<Arc<Path>>,
750 is_dir: bool,
751 cx: &mut ModelContext<Worktree>,
752 ) -> Task<Result<Entry>> {
753 let path = path.into();
754 let abs_path = self.absolutize(&path);
755 let fs = self.fs.clone();
756 let write = cx.background().spawn(async move {
757 if is_dir {
758 fs.create_dir(&abs_path).await
759 } else {
760 fs.save(&abs_path, &Default::default(), Default::default())
761 .await
762 }
763 });
764
765 cx.spawn(|this, mut cx| async move {
766 write.await?;
767 this.update(&mut cx, |this, cx| {
768 this.as_local_mut().unwrap().refresh_entry(path, None, cx)
769 })
770 .await
771 })
772 }
773
774 pub fn write_file(
775 &self,
776 path: impl Into<Arc<Path>>,
777 text: Rope,
778 line_ending: LineEnding,
779 cx: &mut ModelContext<Worktree>,
780 ) -> Task<Result<Entry>> {
781 let path = path.into();
782 let abs_path = self.absolutize(&path);
783 let fs = self.fs.clone();
784 let write = cx
785 .background()
786 .spawn(async move { fs.save(&abs_path, &text, line_ending).await });
787
788 cx.spawn(|this, mut cx| async move {
789 write.await?;
790 this.update(&mut cx, |this, cx| {
791 this.as_local_mut().unwrap().refresh_entry(path, None, cx)
792 })
793 .await
794 })
795 }
796
797 pub fn delete_entry(
798 &self,
799 entry_id: ProjectEntryId,
800 cx: &mut ModelContext<Worktree>,
801 ) -> Option<Task<Result<()>>> {
802 let entry = self.entry_for_id(entry_id)?.clone();
803 let abs_path = self.abs_path.clone();
804 let fs = self.fs.clone();
805
806 let delete = cx.background().spawn(async move {
807 let mut abs_path = fs.canonicalize(&abs_path).await?;
808 if entry.path.file_name().is_some() {
809 abs_path = abs_path.join(&entry.path);
810 }
811 if entry.is_file() {
812 fs.remove_file(&abs_path, Default::default()).await?;
813 } else {
814 fs.remove_dir(
815 &abs_path,
816 RemoveOptions {
817 recursive: true,
818 ignore_if_not_exists: false,
819 },
820 )
821 .await?;
822 }
823 anyhow::Ok(abs_path)
824 });
825
826 Some(cx.spawn(|this, mut cx| async move {
827 let abs_path = delete.await?;
828 let (tx, mut rx) = barrier::channel();
829 this.update(&mut cx, |this, _| {
830 this.as_local_mut()
831 .unwrap()
832 .path_changes_tx
833 .try_send((vec![abs_path], tx))
834 })?;
835 rx.recv().await;
836 Ok(())
837 }))
838 }
839
840 pub fn rename_entry(
841 &self,
842 entry_id: ProjectEntryId,
843 new_path: impl Into<Arc<Path>>,
844 cx: &mut ModelContext<Worktree>,
845 ) -> Option<Task<Result<Entry>>> {
846 let old_path = self.entry_for_id(entry_id)?.path.clone();
847 let new_path = new_path.into();
848 let abs_old_path = self.absolutize(&old_path);
849 let abs_new_path = self.absolutize(&new_path);
850 let fs = self.fs.clone();
851 let rename = cx.background().spawn(async move {
852 fs.rename(&abs_old_path, &abs_new_path, Default::default())
853 .await
854 });
855
856 Some(cx.spawn(|this, mut cx| async move {
857 rename.await?;
858 this.update(&mut cx, |this, cx| {
859 this.as_local_mut()
860 .unwrap()
861 .refresh_entry(new_path.clone(), Some(old_path), cx)
862 })
863 .await
864 }))
865 }
866
867 pub fn copy_entry(
868 &self,
869 entry_id: ProjectEntryId,
870 new_path: impl Into<Arc<Path>>,
871 cx: &mut ModelContext<Worktree>,
872 ) -> Option<Task<Result<Entry>>> {
873 let old_path = self.entry_for_id(entry_id)?.path.clone();
874 let new_path = new_path.into();
875 let abs_old_path = self.absolutize(&old_path);
876 let abs_new_path = self.absolutize(&new_path);
877 let fs = self.fs.clone();
878 let copy = cx.background().spawn(async move {
879 copy_recursive(
880 fs.as_ref(),
881 &abs_old_path,
882 &abs_new_path,
883 Default::default(),
884 )
885 .await
886 });
887
888 Some(cx.spawn(|this, mut cx| async move {
889 copy.await?;
890 this.update(&mut cx, |this, cx| {
891 this.as_local_mut()
892 .unwrap()
893 .refresh_entry(new_path.clone(), None, cx)
894 })
895 .await
896 }))
897 }
898
899 fn refresh_entry(
900 &self,
901 path: Arc<Path>,
902 old_path: Option<Arc<Path>>,
903 cx: &mut ModelContext<Worktree>,
904 ) -> Task<Result<Entry>> {
905 let fs = self.fs.clone();
906 let abs_root_path = self.abs_path.clone();
907 let path_changes_tx = self.path_changes_tx.clone();
908 cx.spawn_weak(move |this, mut cx| async move {
909 let abs_path = fs.canonicalize(&abs_root_path).await?;
910 let mut paths = Vec::with_capacity(2);
911 paths.push(if path.file_name().is_some() {
912 abs_path.join(&path)
913 } else {
914 abs_path.clone()
915 });
916 if let Some(old_path) = old_path {
917 paths.push(if old_path.file_name().is_some() {
918 abs_path.join(&old_path)
919 } else {
920 abs_path.clone()
921 });
922 }
923
924 let (tx, mut rx) = barrier::channel();
925 path_changes_tx.try_send((paths, tx))?;
926 rx.recv().await;
927 this.upgrade(&cx)
928 .ok_or_else(|| anyhow!("worktree was dropped"))?
929 .update(&mut cx, |this, _| {
930 this.entry_for_path(path)
931 .cloned()
932 .ok_or_else(|| anyhow!("failed to read path after update"))
933 })
934 })
935 }
936
937 pub fn share(&mut self, project_id: u64, cx: &mut ModelContext<Worktree>) -> Task<Result<()>> {
938 let (share_tx, share_rx) = oneshot::channel();
939
940 if let Some(share) = self.share.as_mut() {
941 let _ = share_tx.send(());
942 *share.resume_updates.borrow_mut() = ();
943 } else {
944 let (snapshots_tx, mut snapshots_rx) = watch::channel_with(self.snapshot());
945 let (resume_updates_tx, mut resume_updates_rx) = watch::channel();
946 let worktree_id = cx.model_id() as u64;
947
948 for (path, summary) in self.diagnostic_summaries.iter() {
949 if let Err(e) = self.client.send(proto::UpdateDiagnosticSummary {
950 project_id,
951 worktree_id,
952 summary: Some(summary.to_proto(&path.0)),
953 }) {
954 return Task::ready(Err(e));
955 }
956 }
957
958 let _maintain_remote_snapshot = cx.background().spawn({
959 let client = self.client.clone();
960 async move {
961 let mut share_tx = Some(share_tx);
962 let mut prev_snapshot = LocalSnapshot {
963 ignores_by_parent_abs_path: Default::default(),
964 git_repositories: Default::default(),
965 removed_entry_ids: Default::default(),
966 next_entry_id: Default::default(),
967 snapshot: Snapshot {
968 id: WorktreeId(worktree_id as usize),
969 abs_path: Path::new("").into(),
970 root_name: Default::default(),
971 root_char_bag: Default::default(),
972 entries_by_path: Default::default(),
973 entries_by_id: Default::default(),
974 scan_id: 0,
975 completed_scan_id: 0,
976 },
977 };
978 while let Some(snapshot) = snapshots_rx.recv().await {
979 #[cfg(any(test, feature = "test-support"))]
980 const MAX_CHUNK_SIZE: usize = 2;
981 #[cfg(not(any(test, feature = "test-support")))]
982 const MAX_CHUNK_SIZE: usize = 256;
983
984 let update =
985 snapshot.build_update(&prev_snapshot, project_id, worktree_id, true);
986 for update in proto::split_worktree_update(update, MAX_CHUNK_SIZE) {
987 let _ = resume_updates_rx.try_recv();
988 while let Err(error) = client.request(update.clone()).await {
989 log::error!("failed to send worktree update: {}", error);
990 log::info!("waiting to resume updates");
991 if resume_updates_rx.next().await.is_none() {
992 return Ok(());
993 }
994 }
995 }
996
997 if let Some(share_tx) = share_tx.take() {
998 let _ = share_tx.send(());
999 }
1000
1001 prev_snapshot = snapshot;
1002 }
1003
1004 Ok::<_, anyhow::Error>(())
1005 }
1006 .log_err()
1007 });
1008
1009 self.share = Some(ShareState {
1010 project_id,
1011 snapshots_tx,
1012 resume_updates: resume_updates_tx,
1013 _maintain_remote_snapshot,
1014 });
1015 }
1016
1017 cx.foreground()
1018 .spawn(async move { share_rx.await.map_err(|_| anyhow!("share ended")) })
1019 }
1020
1021 pub fn unshare(&mut self) {
1022 self.share.take();
1023 }
1024
1025 pub fn is_shared(&self) -> bool {
1026 self.share.is_some()
1027 }
1028}
1029
1030impl RemoteWorktree {
1031 fn snapshot(&self) -> Snapshot {
1032 self.snapshot.clone()
1033 }
1034
1035 pub fn disconnected_from_host(&mut self) {
1036 self.updates_tx.take();
1037 self.snapshot_subscriptions.clear();
1038 self.disconnected = true;
1039 }
1040
1041 pub fn save_buffer(
1042 &self,
1043 buffer_handle: ModelHandle<Buffer>,
1044 cx: &mut ModelContext<Worktree>,
1045 ) -> Task<Result<(clock::Global, RopeFingerprint, SystemTime)>> {
1046 let buffer = buffer_handle.read(cx);
1047 let buffer_id = buffer.remote_id();
1048 let version = buffer.version();
1049 let rpc = self.client.clone();
1050 let project_id = self.project_id;
1051 cx.as_mut().spawn(|mut cx| async move {
1052 let response = rpc
1053 .request(proto::SaveBuffer {
1054 project_id,
1055 buffer_id,
1056 version: serialize_version(&version),
1057 })
1058 .await?;
1059 let version = deserialize_version(&response.version);
1060 let fingerprint = deserialize_fingerprint(&response.fingerprint)?;
1061 let mtime = response
1062 .mtime
1063 .ok_or_else(|| anyhow!("missing mtime"))?
1064 .into();
1065
1066 buffer_handle.update(&mut cx, |buffer, cx| {
1067 buffer.did_save(version.clone(), fingerprint, mtime, cx);
1068 });
1069
1070 Ok((version, fingerprint, mtime))
1071 })
1072 }
1073
1074 pub fn update_from_remote(&mut self, update: proto::UpdateWorktree) {
1075 if let Some(updates_tx) = &self.updates_tx {
1076 updates_tx
1077 .unbounded_send(update)
1078 .expect("consumer runs to completion");
1079 }
1080 }
1081
1082 fn observed_snapshot(&self, scan_id: usize) -> bool {
1083 self.completed_scan_id >= scan_id
1084 }
1085
1086 fn wait_for_snapshot(&mut self, scan_id: usize) -> impl Future<Output = Result<()>> {
1087 let (tx, rx) = oneshot::channel();
1088 if self.observed_snapshot(scan_id) {
1089 let _ = tx.send(());
1090 } else if self.disconnected {
1091 drop(tx);
1092 } else {
1093 match self
1094 .snapshot_subscriptions
1095 .binary_search_by_key(&scan_id, |probe| probe.0)
1096 {
1097 Ok(ix) | Err(ix) => self.snapshot_subscriptions.insert(ix, (scan_id, tx)),
1098 }
1099 }
1100
1101 async move {
1102 rx.await?;
1103 Ok(())
1104 }
1105 }
1106
1107 pub fn update_diagnostic_summary(
1108 &mut self,
1109 path: Arc<Path>,
1110 summary: &proto::DiagnosticSummary,
1111 ) {
1112 let summary = DiagnosticSummary {
1113 language_server_id: summary.language_server_id as usize,
1114 error_count: summary.error_count as usize,
1115 warning_count: summary.warning_count as usize,
1116 };
1117 if summary.is_empty() {
1118 self.diagnostic_summaries.remove(&PathKey(path));
1119 } else {
1120 self.diagnostic_summaries.insert(PathKey(path), summary);
1121 }
1122 }
1123
1124 pub fn insert_entry(
1125 &mut self,
1126 entry: proto::Entry,
1127 scan_id: usize,
1128 cx: &mut ModelContext<Worktree>,
1129 ) -> Task<Result<Entry>> {
1130 let wait_for_snapshot = self.wait_for_snapshot(scan_id);
1131 cx.spawn(|this, mut cx| async move {
1132 wait_for_snapshot.await?;
1133 this.update(&mut cx, |worktree, _| {
1134 let worktree = worktree.as_remote_mut().unwrap();
1135 let mut snapshot = worktree.background_snapshot.lock();
1136 let entry = snapshot.insert_entry(entry);
1137 worktree.snapshot = snapshot.clone();
1138 entry
1139 })
1140 })
1141 }
1142
1143 pub(crate) fn delete_entry(
1144 &mut self,
1145 id: ProjectEntryId,
1146 scan_id: usize,
1147 cx: &mut ModelContext<Worktree>,
1148 ) -> Task<Result<()>> {
1149 let wait_for_snapshot = self.wait_for_snapshot(scan_id);
1150 cx.spawn(|this, mut cx| async move {
1151 wait_for_snapshot.await?;
1152 this.update(&mut cx, |worktree, _| {
1153 let worktree = worktree.as_remote_mut().unwrap();
1154 let mut snapshot = worktree.background_snapshot.lock();
1155 snapshot.delete_entry(id);
1156 worktree.snapshot = snapshot.clone();
1157 });
1158 Ok(())
1159 })
1160 }
1161}
1162
1163impl Snapshot {
1164 pub fn id(&self) -> WorktreeId {
1165 self.id
1166 }
1167
1168 pub fn abs_path(&self) -> &Arc<Path> {
1169 &self.abs_path
1170 }
1171
1172 pub fn contains_entry(&self, entry_id: ProjectEntryId) -> bool {
1173 self.entries_by_id.get(&entry_id, &()).is_some()
1174 }
1175
1176 pub(crate) fn insert_entry(&mut self, entry: proto::Entry) -> Result<Entry> {
1177 let entry = Entry::try_from((&self.root_char_bag, entry))?;
1178 let old_entry = self.entries_by_id.insert_or_replace(
1179 PathEntry {
1180 id: entry.id,
1181 path: entry.path.clone(),
1182 is_ignored: entry.is_ignored,
1183 scan_id: 0,
1184 },
1185 &(),
1186 );
1187 if let Some(old_entry) = old_entry {
1188 self.entries_by_path.remove(&PathKey(old_entry.path), &());
1189 }
1190 self.entries_by_path.insert_or_replace(entry.clone(), &());
1191 Ok(entry)
1192 }
1193
1194 fn delete_entry(&mut self, entry_id: ProjectEntryId) -> Option<Arc<Path>> {
1195 let removed_entry = self.entries_by_id.remove(&entry_id, &())?;
1196 self.entries_by_path = {
1197 let mut cursor = self.entries_by_path.cursor();
1198 let mut new_entries_by_path =
1199 cursor.slice(&TraversalTarget::Path(&removed_entry.path), Bias::Left, &());
1200 while let Some(entry) = cursor.item() {
1201 if entry.path.starts_with(&removed_entry.path) {
1202 self.entries_by_id.remove(&entry.id, &());
1203 cursor.next(&());
1204 } else {
1205 break;
1206 }
1207 }
1208 new_entries_by_path.push_tree(cursor.suffix(&()), &());
1209 new_entries_by_path
1210 };
1211
1212 Some(removed_entry.path)
1213 }
1214
1215 pub(crate) fn apply_remote_update(&mut self, update: proto::UpdateWorktree) -> Result<()> {
1216 let mut entries_by_path_edits = Vec::new();
1217 let mut entries_by_id_edits = Vec::new();
1218 for entry_id in update.removed_entries {
1219 if let Some(entry) = self.entry_for_id(ProjectEntryId::from_proto(entry_id)) {
1220 entries_by_path_edits.push(Edit::Remove(PathKey(entry.path.clone())));
1221 entries_by_id_edits.push(Edit::Remove(entry.id));
1222 }
1223 }
1224
1225 for entry in update.updated_entries {
1226 let entry = Entry::try_from((&self.root_char_bag, entry))?;
1227 if let Some(PathEntry { path, .. }) = self.entries_by_id.get(&entry.id, &()) {
1228 entries_by_path_edits.push(Edit::Remove(PathKey(path.clone())));
1229 }
1230 entries_by_id_edits.push(Edit::Insert(PathEntry {
1231 id: entry.id,
1232 path: entry.path.clone(),
1233 is_ignored: entry.is_ignored,
1234 scan_id: 0,
1235 }));
1236 entries_by_path_edits.push(Edit::Insert(entry));
1237 }
1238
1239 self.entries_by_path.edit(entries_by_path_edits, &());
1240 self.entries_by_id.edit(entries_by_id_edits, &());
1241 self.scan_id = update.scan_id as usize;
1242 if update.is_last_update {
1243 self.completed_scan_id = update.scan_id as usize;
1244 }
1245
1246 Ok(())
1247 }
1248
1249 pub fn file_count(&self) -> usize {
1250 self.entries_by_path.summary().file_count
1251 }
1252
1253 pub fn visible_file_count(&self) -> usize {
1254 self.entries_by_path.summary().visible_file_count
1255 }
1256
1257 fn traverse_from_offset(
1258 &self,
1259 include_dirs: bool,
1260 include_ignored: bool,
1261 start_offset: usize,
1262 ) -> Traversal {
1263 let mut cursor = self.entries_by_path.cursor();
1264 cursor.seek(
1265 &TraversalTarget::Count {
1266 count: start_offset,
1267 include_dirs,
1268 include_ignored,
1269 },
1270 Bias::Right,
1271 &(),
1272 );
1273 Traversal {
1274 cursor,
1275 include_dirs,
1276 include_ignored,
1277 }
1278 }
1279
1280 fn traverse_from_path(
1281 &self,
1282 include_dirs: bool,
1283 include_ignored: bool,
1284 path: &Path,
1285 ) -> Traversal {
1286 let mut cursor = self.entries_by_path.cursor();
1287 cursor.seek(&TraversalTarget::Path(path), Bias::Left, &());
1288 Traversal {
1289 cursor,
1290 include_dirs,
1291 include_ignored,
1292 }
1293 }
1294
1295 pub fn files(&self, include_ignored: bool, start: usize) -> Traversal {
1296 self.traverse_from_offset(false, include_ignored, start)
1297 }
1298
1299 pub fn entries(&self, include_ignored: bool) -> Traversal {
1300 self.traverse_from_offset(true, include_ignored, 0)
1301 }
1302
1303 pub fn paths(&self) -> impl Iterator<Item = &Arc<Path>> {
1304 let empty_path = Path::new("");
1305 self.entries_by_path
1306 .cursor::<()>()
1307 .filter(move |entry| entry.path.as_ref() != empty_path)
1308 .map(|entry| &entry.path)
1309 }
1310
1311 fn child_entries<'a>(&'a self, parent_path: &'a Path) -> ChildEntriesIter<'a> {
1312 let mut cursor = self.entries_by_path.cursor();
1313 cursor.seek(&TraversalTarget::Path(parent_path), Bias::Right, &());
1314 let traversal = Traversal {
1315 cursor,
1316 include_dirs: true,
1317 include_ignored: true,
1318 };
1319 ChildEntriesIter {
1320 traversal,
1321 parent_path,
1322 }
1323 }
1324
1325 pub fn root_entry(&self) -> Option<&Entry> {
1326 self.entry_for_path("")
1327 }
1328
1329 pub fn root_name(&self) -> &str {
1330 &self.root_name
1331 }
1332
1333 pub fn scan_id(&self) -> usize {
1334 self.scan_id
1335 }
1336
1337 pub fn entry_for_path(&self, path: impl AsRef<Path>) -> Option<&Entry> {
1338 let path = path.as_ref();
1339 self.traverse_from_path(true, true, path)
1340 .entry()
1341 .and_then(|entry| {
1342 if entry.path.as_ref() == path {
1343 Some(entry)
1344 } else {
1345 None
1346 }
1347 })
1348 }
1349
1350 pub fn entry_for_id(&self, id: ProjectEntryId) -> Option<&Entry> {
1351 let entry = self.entries_by_id.get(&id, &())?;
1352 self.entry_for_path(&entry.path)
1353 }
1354
1355 pub fn inode_for_path(&self, path: impl AsRef<Path>) -> Option<u64> {
1356 self.entry_for_path(path.as_ref()).map(|e| e.inode)
1357 }
1358}
1359
1360impl LocalSnapshot {
1361 // Gives the most specific git repository for a given path
1362 pub(crate) fn repo_for(&self, path: &Path) -> Option<GitRepositoryEntry> {
1363 self.git_repositories
1364 .iter()
1365 .rev() //git_repository is ordered lexicographically
1366 .find(|repo| repo.manages(path))
1367 .cloned()
1368 }
1369
1370 pub(crate) fn repo_with_dot_git_containing(
1371 &mut self,
1372 path: &Path,
1373 ) -> Option<&mut GitRepositoryEntry> {
1374 // Git repositories cannot be nested, so we don't need to reverse the order
1375 self.git_repositories
1376 .iter_mut()
1377 .find(|repo| repo.in_dot_git(path))
1378 }
1379
1380 #[cfg(test)]
1381 pub(crate) fn build_initial_update(&self, project_id: u64) -> proto::UpdateWorktree {
1382 let root_name = self.root_name.clone();
1383 proto::UpdateWorktree {
1384 project_id,
1385 worktree_id: self.id().to_proto(),
1386 abs_path: self.abs_path().to_string_lossy().into(),
1387 root_name,
1388 updated_entries: self.entries_by_path.iter().map(Into::into).collect(),
1389 removed_entries: Default::default(),
1390 scan_id: self.scan_id as u64,
1391 is_last_update: true,
1392 }
1393 }
1394
1395 pub(crate) fn build_update(
1396 &self,
1397 other: &Self,
1398 project_id: u64,
1399 worktree_id: u64,
1400 include_ignored: bool,
1401 ) -> proto::UpdateWorktree {
1402 let mut updated_entries = Vec::new();
1403 let mut removed_entries = Vec::new();
1404 let mut self_entries = self
1405 .entries_by_id
1406 .cursor::<()>()
1407 .filter(|e| include_ignored || !e.is_ignored)
1408 .peekable();
1409 let mut other_entries = other
1410 .entries_by_id
1411 .cursor::<()>()
1412 .filter(|e| include_ignored || !e.is_ignored)
1413 .peekable();
1414 loop {
1415 match (self_entries.peek(), other_entries.peek()) {
1416 (Some(self_entry), Some(other_entry)) => {
1417 match Ord::cmp(&self_entry.id, &other_entry.id) {
1418 Ordering::Less => {
1419 let entry = self.entry_for_id(self_entry.id).unwrap().into();
1420 updated_entries.push(entry);
1421 self_entries.next();
1422 }
1423 Ordering::Equal => {
1424 if self_entry.scan_id != other_entry.scan_id {
1425 let entry = self.entry_for_id(self_entry.id).unwrap().into();
1426 updated_entries.push(entry);
1427 }
1428
1429 self_entries.next();
1430 other_entries.next();
1431 }
1432 Ordering::Greater => {
1433 removed_entries.push(other_entry.id.to_proto());
1434 other_entries.next();
1435 }
1436 }
1437 }
1438 (Some(self_entry), None) => {
1439 let entry = self.entry_for_id(self_entry.id).unwrap().into();
1440 updated_entries.push(entry);
1441 self_entries.next();
1442 }
1443 (None, Some(other_entry)) => {
1444 removed_entries.push(other_entry.id.to_proto());
1445 other_entries.next();
1446 }
1447 (None, None) => break,
1448 }
1449 }
1450
1451 proto::UpdateWorktree {
1452 project_id,
1453 worktree_id,
1454 abs_path: self.abs_path().to_string_lossy().into(),
1455 root_name: self.root_name().to_string(),
1456 updated_entries,
1457 removed_entries,
1458 scan_id: self.scan_id as u64,
1459 is_last_update: self.completed_scan_id == self.scan_id,
1460 }
1461 }
1462
1463 fn insert_entry(&mut self, mut entry: Entry, fs: &dyn Fs) -> Entry {
1464 if entry.is_file() && entry.path.file_name() == Some(&GITIGNORE) {
1465 let abs_path = self.abs_path.join(&entry.path);
1466 match smol::block_on(build_gitignore(&abs_path, fs)) {
1467 Ok(ignore) => {
1468 self.ignores_by_parent_abs_path.insert(
1469 abs_path.parent().unwrap().into(),
1470 (Arc::new(ignore), self.scan_id),
1471 );
1472 }
1473 Err(error) => {
1474 log::error!(
1475 "error loading .gitignore file {:?} - {:?}",
1476 &entry.path,
1477 error
1478 );
1479 }
1480 }
1481 }
1482
1483 self.reuse_entry_id(&mut entry);
1484
1485 if entry.kind == EntryKind::PendingDir {
1486 if let Some(existing_entry) =
1487 self.entries_by_path.get(&PathKey(entry.path.clone()), &())
1488 {
1489 entry.kind = existing_entry.kind;
1490 }
1491 }
1492
1493 let scan_id = self.scan_id;
1494 self.entries_by_path.insert_or_replace(entry.clone(), &());
1495 self.entries_by_id.insert_or_replace(
1496 PathEntry {
1497 id: entry.id,
1498 path: entry.path.clone(),
1499 is_ignored: entry.is_ignored,
1500 scan_id,
1501 },
1502 &(),
1503 );
1504
1505 entry
1506 }
1507
1508 fn populate_dir(
1509 &mut self,
1510 parent_path: Arc<Path>,
1511 entries: impl IntoIterator<Item = Entry>,
1512 ignore: Option<Arc<Gitignore>>,
1513 fs: &dyn Fs,
1514 ) {
1515 let mut parent_entry = if let Some(parent_entry) =
1516 self.entries_by_path.get(&PathKey(parent_path.clone()), &())
1517 {
1518 parent_entry.clone()
1519 } else {
1520 log::warn!(
1521 "populating a directory {:?} that has been removed",
1522 parent_path
1523 );
1524 return;
1525 };
1526
1527 match parent_entry.kind {
1528 EntryKind::PendingDir => {
1529 parent_entry.kind = EntryKind::Dir;
1530 }
1531 EntryKind::Dir => {}
1532 _ => return,
1533 }
1534
1535 if let Some(ignore) = ignore {
1536 self.ignores_by_parent_abs_path.insert(
1537 self.abs_path.join(&parent_path).into(),
1538 (ignore, self.scan_id),
1539 );
1540 }
1541
1542 if parent_path.file_name() == Some(&DOT_GIT) {
1543 let abs_path = self.abs_path.join(&parent_path);
1544 let content_path: Arc<Path> = parent_path.parent().unwrap().into();
1545 if let Err(ix) = self
1546 .git_repositories
1547 .binary_search_by_key(&&content_path, |repo| &repo.content_path)
1548 {
1549 if let Some(repo) = fs.open_repo(abs_path.as_path()) {
1550 self.git_repositories.insert(
1551 ix,
1552 GitRepositoryEntry {
1553 repo,
1554 scan_id: 0,
1555 content_path,
1556 git_dir_path: parent_path,
1557 },
1558 );
1559 }
1560 }
1561 }
1562
1563 let mut entries_by_path_edits = vec![Edit::Insert(parent_entry)];
1564 let mut entries_by_id_edits = Vec::new();
1565
1566 for mut entry in entries {
1567 self.reuse_entry_id(&mut entry);
1568 entries_by_id_edits.push(Edit::Insert(PathEntry {
1569 id: entry.id,
1570 path: entry.path.clone(),
1571 is_ignored: entry.is_ignored,
1572 scan_id: self.scan_id,
1573 }));
1574 entries_by_path_edits.push(Edit::Insert(entry));
1575 }
1576
1577 self.entries_by_path.edit(entries_by_path_edits, &());
1578 self.entries_by_id.edit(entries_by_id_edits, &());
1579 }
1580
1581 fn reuse_entry_id(&mut self, entry: &mut Entry) {
1582 if let Some(removed_entry_id) = self.removed_entry_ids.remove(&entry.inode) {
1583 entry.id = removed_entry_id;
1584 } else if let Some(existing_entry) = self.entry_for_path(&entry.path) {
1585 entry.id = existing_entry.id;
1586 }
1587 }
1588
1589 fn remove_path(&mut self, path: &Path) {
1590 let mut new_entries;
1591 let removed_entries;
1592 {
1593 let mut cursor = self.entries_by_path.cursor::<TraversalProgress>();
1594 new_entries = cursor.slice(&TraversalTarget::Path(path), Bias::Left, &());
1595 removed_entries = cursor.slice(&TraversalTarget::PathSuccessor(path), Bias::Left, &());
1596 new_entries.push_tree(cursor.suffix(&()), &());
1597 }
1598 self.entries_by_path = new_entries;
1599
1600 let mut entries_by_id_edits = Vec::new();
1601 for entry in removed_entries.cursor::<()>() {
1602 let removed_entry_id = self
1603 .removed_entry_ids
1604 .entry(entry.inode)
1605 .or_insert(entry.id);
1606 *removed_entry_id = cmp::max(*removed_entry_id, entry.id);
1607 entries_by_id_edits.push(Edit::Remove(entry.id));
1608 }
1609 self.entries_by_id.edit(entries_by_id_edits, &());
1610
1611 if path.file_name() == Some(&GITIGNORE) {
1612 let abs_parent_path = self.abs_path.join(path.parent().unwrap());
1613 if let Some((_, scan_id)) = self
1614 .ignores_by_parent_abs_path
1615 .get_mut(abs_parent_path.as_path())
1616 {
1617 *scan_id = self.snapshot.scan_id;
1618 }
1619 } else if path.file_name() == Some(&DOT_GIT) {
1620 let parent_path = path.parent().unwrap();
1621 if let Ok(ix) = self
1622 .git_repositories
1623 .binary_search_by_key(&parent_path, |repo| repo.git_dir_path.as_ref())
1624 {
1625 self.git_repositories[ix].scan_id = self.snapshot.scan_id;
1626 }
1627 }
1628 }
1629
1630 fn ancestor_inodes_for_path(&self, path: &Path) -> TreeSet<u64> {
1631 let mut inodes = TreeSet::default();
1632 for ancestor in path.ancestors().skip(1) {
1633 if let Some(entry) = self.entry_for_path(ancestor) {
1634 inodes.insert(entry.inode);
1635 }
1636 }
1637 inodes
1638 }
1639
1640 fn ignore_stack_for_abs_path(&self, abs_path: &Path, is_dir: bool) -> Arc<IgnoreStack> {
1641 let mut new_ignores = Vec::new();
1642 for ancestor in abs_path.ancestors().skip(1) {
1643 if let Some((ignore, _)) = self.ignores_by_parent_abs_path.get(ancestor) {
1644 new_ignores.push((ancestor, Some(ignore.clone())));
1645 } else {
1646 new_ignores.push((ancestor, None));
1647 }
1648 }
1649
1650 let mut ignore_stack = IgnoreStack::none();
1651 for (parent_abs_path, ignore) in new_ignores.into_iter().rev() {
1652 if ignore_stack.is_abs_path_ignored(parent_abs_path, true) {
1653 ignore_stack = IgnoreStack::all();
1654 break;
1655 } else if let Some(ignore) = ignore {
1656 ignore_stack = ignore_stack.append(parent_abs_path.into(), ignore);
1657 }
1658 }
1659
1660 if ignore_stack.is_abs_path_ignored(abs_path, is_dir) {
1661 ignore_stack = IgnoreStack::all();
1662 }
1663
1664 ignore_stack
1665 }
1666
1667 pub fn git_repo_entries(&self) -> &[GitRepositoryEntry] {
1668 &self.git_repositories
1669 }
1670}
1671
1672impl GitRepositoryEntry {
1673 // Note that these paths should be relative to the worktree root.
1674 pub(crate) fn manages(&self, path: &Path) -> bool {
1675 path.starts_with(self.content_path.as_ref())
1676 }
1677
1678 // Note that this path should be relative to the worktree root.
1679 pub(crate) fn in_dot_git(&self, path: &Path) -> bool {
1680 path.starts_with(self.git_dir_path.as_ref())
1681 }
1682}
1683
1684async fn build_gitignore(abs_path: &Path, fs: &dyn Fs) -> Result<Gitignore> {
1685 let contents = fs.load(abs_path).await?;
1686 let parent = abs_path.parent().unwrap_or_else(|| Path::new("/"));
1687 let mut builder = GitignoreBuilder::new(parent);
1688 for line in contents.lines() {
1689 builder.add_line(Some(abs_path.into()), line)?;
1690 }
1691 Ok(builder.build()?)
1692}
1693
1694impl WorktreeId {
1695 pub fn from_usize(handle_id: usize) -> Self {
1696 Self(handle_id)
1697 }
1698
1699 pub(crate) fn from_proto(id: u64) -> Self {
1700 Self(id as usize)
1701 }
1702
1703 pub fn to_proto(&self) -> u64 {
1704 self.0 as u64
1705 }
1706
1707 pub fn to_usize(&self) -> usize {
1708 self.0
1709 }
1710}
1711
1712impl fmt::Display for WorktreeId {
1713 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1714 self.0.fmt(f)
1715 }
1716}
1717
1718impl Deref for Worktree {
1719 type Target = Snapshot;
1720
1721 fn deref(&self) -> &Self::Target {
1722 match self {
1723 Worktree::Local(worktree) => &worktree.snapshot,
1724 Worktree::Remote(worktree) => &worktree.snapshot,
1725 }
1726 }
1727}
1728
1729impl Deref for LocalWorktree {
1730 type Target = LocalSnapshot;
1731
1732 fn deref(&self) -> &Self::Target {
1733 &self.snapshot
1734 }
1735}
1736
1737impl Deref for RemoteWorktree {
1738 type Target = Snapshot;
1739
1740 fn deref(&self) -> &Self::Target {
1741 &self.snapshot
1742 }
1743}
1744
1745impl fmt::Debug for LocalWorktree {
1746 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1747 self.snapshot.fmt(f)
1748 }
1749}
1750
1751impl fmt::Debug for Snapshot {
1752 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1753 struct EntriesById<'a>(&'a SumTree<PathEntry>);
1754 struct EntriesByPath<'a>(&'a SumTree<Entry>);
1755
1756 impl<'a> fmt::Debug for EntriesByPath<'a> {
1757 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1758 f.debug_map()
1759 .entries(self.0.iter().map(|entry| (&entry.path, entry.id)))
1760 .finish()
1761 }
1762 }
1763
1764 impl<'a> fmt::Debug for EntriesById<'a> {
1765 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1766 f.debug_list().entries(self.0.iter()).finish()
1767 }
1768 }
1769
1770 f.debug_struct("Snapshot")
1771 .field("id", &self.id)
1772 .field("root_name", &self.root_name)
1773 .field("entries_by_path", &EntriesByPath(&self.entries_by_path))
1774 .field("entries_by_id", &EntriesById(&self.entries_by_id))
1775 .finish()
1776 }
1777}
1778
1779#[derive(Clone, PartialEq)]
1780pub struct File {
1781 pub worktree: ModelHandle<Worktree>,
1782 pub path: Arc<Path>,
1783 pub mtime: SystemTime,
1784 pub(crate) entry_id: ProjectEntryId,
1785 pub(crate) is_local: bool,
1786 pub(crate) is_deleted: bool,
1787}
1788
1789impl language::File for File {
1790 fn as_local(&self) -> Option<&dyn language::LocalFile> {
1791 if self.is_local {
1792 Some(self)
1793 } else {
1794 None
1795 }
1796 }
1797
1798 fn mtime(&self) -> SystemTime {
1799 self.mtime
1800 }
1801
1802 fn path(&self) -> &Arc<Path> {
1803 &self.path
1804 }
1805
1806 fn full_path(&self, cx: &AppContext) -> PathBuf {
1807 let mut full_path = PathBuf::new();
1808 let worktree = self.worktree.read(cx);
1809
1810 if worktree.is_visible() {
1811 full_path.push(worktree.root_name());
1812 } else {
1813 let path = worktree.abs_path();
1814
1815 if worktree.is_local() && path.starts_with(HOME.as_path()) {
1816 full_path.push("~");
1817 full_path.push(path.strip_prefix(HOME.as_path()).unwrap());
1818 } else {
1819 full_path.push(path)
1820 }
1821 }
1822
1823 if self.path.components().next().is_some() {
1824 full_path.push(&self.path);
1825 }
1826
1827 full_path
1828 }
1829
1830 /// Returns the last component of this handle's absolute path. If this handle refers to the root
1831 /// of its worktree, then this method will return the name of the worktree itself.
1832 fn file_name<'a>(&'a self, cx: &'a AppContext) -> &'a OsStr {
1833 self.path
1834 .file_name()
1835 .unwrap_or_else(|| OsStr::new(&self.worktree.read(cx).root_name))
1836 }
1837
1838 fn is_deleted(&self) -> bool {
1839 self.is_deleted
1840 }
1841
1842 fn as_any(&self) -> &dyn Any {
1843 self
1844 }
1845
1846 fn to_proto(&self) -> rpc::proto::File {
1847 rpc::proto::File {
1848 worktree_id: self.worktree.id() as u64,
1849 entry_id: self.entry_id.to_proto(),
1850 path: self.path.to_string_lossy().into(),
1851 mtime: Some(self.mtime.into()),
1852 is_deleted: self.is_deleted,
1853 }
1854 }
1855}
1856
1857impl language::LocalFile for File {
1858 fn abs_path(&self, cx: &AppContext) -> PathBuf {
1859 self.worktree
1860 .read(cx)
1861 .as_local()
1862 .unwrap()
1863 .abs_path
1864 .join(&self.path)
1865 }
1866
1867 fn load(&self, cx: &AppContext) -> Task<Result<String>> {
1868 let worktree = self.worktree.read(cx).as_local().unwrap();
1869 let abs_path = worktree.absolutize(&self.path);
1870 let fs = worktree.fs.clone();
1871 cx.background()
1872 .spawn(async move { fs.load(&abs_path).await })
1873 }
1874
1875 fn buffer_reloaded(
1876 &self,
1877 buffer_id: u64,
1878 version: &clock::Global,
1879 fingerprint: RopeFingerprint,
1880 line_ending: LineEnding,
1881 mtime: SystemTime,
1882 cx: &mut AppContext,
1883 ) {
1884 let worktree = self.worktree.read(cx).as_local().unwrap();
1885 if let Some(project_id) = worktree.share.as_ref().map(|share| share.project_id) {
1886 worktree
1887 .client
1888 .send(proto::BufferReloaded {
1889 project_id,
1890 buffer_id,
1891 version: serialize_version(version),
1892 mtime: Some(mtime.into()),
1893 fingerprint: serialize_fingerprint(fingerprint),
1894 line_ending: serialize_line_ending(line_ending) as i32,
1895 })
1896 .log_err();
1897 }
1898 }
1899}
1900
1901impl File {
1902 pub fn from_proto(
1903 proto: rpc::proto::File,
1904 worktree: ModelHandle<Worktree>,
1905 cx: &AppContext,
1906 ) -> Result<Self> {
1907 let worktree_id = worktree
1908 .read(cx)
1909 .as_remote()
1910 .ok_or_else(|| anyhow!("not remote"))?
1911 .id();
1912
1913 if worktree_id.to_proto() != proto.worktree_id {
1914 return Err(anyhow!("worktree id does not match file"));
1915 }
1916
1917 Ok(Self {
1918 worktree,
1919 path: Path::new(&proto.path).into(),
1920 mtime: proto.mtime.ok_or_else(|| anyhow!("no timestamp"))?.into(),
1921 entry_id: ProjectEntryId::from_proto(proto.entry_id),
1922 is_local: false,
1923 is_deleted: proto.is_deleted,
1924 })
1925 }
1926
1927 pub fn from_dyn(file: Option<&Arc<dyn language::File>>) -> Option<&Self> {
1928 file.and_then(|f| f.as_any().downcast_ref())
1929 }
1930
1931 pub fn worktree_id(&self, cx: &AppContext) -> WorktreeId {
1932 self.worktree.read(cx).id()
1933 }
1934
1935 pub fn project_entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
1936 if self.is_deleted {
1937 None
1938 } else {
1939 Some(self.entry_id)
1940 }
1941 }
1942}
1943
1944#[derive(Clone, Debug, PartialEq, Eq)]
1945pub struct Entry {
1946 pub id: ProjectEntryId,
1947 pub kind: EntryKind,
1948 pub path: Arc<Path>,
1949 pub inode: u64,
1950 pub mtime: SystemTime,
1951 pub is_symlink: bool,
1952 pub is_ignored: bool,
1953}
1954
1955#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1956pub enum EntryKind {
1957 PendingDir,
1958 Dir,
1959 File(CharBag),
1960}
1961
1962#[derive(Clone, Copy, Debug)]
1963pub enum PathChange {
1964 Added,
1965 Removed,
1966 Updated,
1967 AddedOrUpdated,
1968}
1969
1970impl Entry {
1971 fn new(
1972 path: Arc<Path>,
1973 metadata: &fs::Metadata,
1974 next_entry_id: &AtomicUsize,
1975 root_char_bag: CharBag,
1976 ) -> Self {
1977 Self {
1978 id: ProjectEntryId::new(next_entry_id),
1979 kind: if metadata.is_dir {
1980 EntryKind::PendingDir
1981 } else {
1982 EntryKind::File(char_bag_for_path(root_char_bag, &path))
1983 },
1984 path,
1985 inode: metadata.inode,
1986 mtime: metadata.mtime,
1987 is_symlink: metadata.is_symlink,
1988 is_ignored: false,
1989 }
1990 }
1991
1992 pub fn is_dir(&self) -> bool {
1993 matches!(self.kind, EntryKind::Dir | EntryKind::PendingDir)
1994 }
1995
1996 pub fn is_file(&self) -> bool {
1997 matches!(self.kind, EntryKind::File(_))
1998 }
1999}
2000
2001impl sum_tree::Item for Entry {
2002 type Summary = EntrySummary;
2003
2004 fn summary(&self) -> Self::Summary {
2005 let visible_count = if self.is_ignored { 0 } else { 1 };
2006 let file_count;
2007 let visible_file_count;
2008 if self.is_file() {
2009 file_count = 1;
2010 visible_file_count = visible_count;
2011 } else {
2012 file_count = 0;
2013 visible_file_count = 0;
2014 }
2015
2016 EntrySummary {
2017 max_path: self.path.clone(),
2018 count: 1,
2019 visible_count,
2020 file_count,
2021 visible_file_count,
2022 }
2023 }
2024}
2025
2026impl sum_tree::KeyedItem for Entry {
2027 type Key = PathKey;
2028
2029 fn key(&self) -> Self::Key {
2030 PathKey(self.path.clone())
2031 }
2032}
2033
2034#[derive(Clone, Debug)]
2035pub struct EntrySummary {
2036 max_path: Arc<Path>,
2037 count: usize,
2038 visible_count: usize,
2039 file_count: usize,
2040 visible_file_count: usize,
2041}
2042
2043impl Default for EntrySummary {
2044 fn default() -> Self {
2045 Self {
2046 max_path: Arc::from(Path::new("")),
2047 count: 0,
2048 visible_count: 0,
2049 file_count: 0,
2050 visible_file_count: 0,
2051 }
2052 }
2053}
2054
2055impl sum_tree::Summary for EntrySummary {
2056 type Context = ();
2057
2058 fn add_summary(&mut self, rhs: &Self, _: &()) {
2059 self.max_path = rhs.max_path.clone();
2060 self.count += rhs.count;
2061 self.visible_count += rhs.visible_count;
2062 self.file_count += rhs.file_count;
2063 self.visible_file_count += rhs.visible_file_count;
2064 }
2065}
2066
2067#[derive(Clone, Debug)]
2068struct PathEntry {
2069 id: ProjectEntryId,
2070 path: Arc<Path>,
2071 is_ignored: bool,
2072 scan_id: usize,
2073}
2074
2075impl sum_tree::Item for PathEntry {
2076 type Summary = PathEntrySummary;
2077
2078 fn summary(&self) -> Self::Summary {
2079 PathEntrySummary { max_id: self.id }
2080 }
2081}
2082
2083impl sum_tree::KeyedItem for PathEntry {
2084 type Key = ProjectEntryId;
2085
2086 fn key(&self) -> Self::Key {
2087 self.id
2088 }
2089}
2090
2091#[derive(Clone, Debug, Default)]
2092struct PathEntrySummary {
2093 max_id: ProjectEntryId,
2094}
2095
2096impl sum_tree::Summary for PathEntrySummary {
2097 type Context = ();
2098
2099 fn add_summary(&mut self, summary: &Self, _: &Self::Context) {
2100 self.max_id = summary.max_id;
2101 }
2102}
2103
2104impl<'a> sum_tree::Dimension<'a, PathEntrySummary> for ProjectEntryId {
2105 fn add_summary(&mut self, summary: &'a PathEntrySummary, _: &()) {
2106 *self = summary.max_id;
2107 }
2108}
2109
2110#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
2111pub struct PathKey(Arc<Path>);
2112
2113impl Default for PathKey {
2114 fn default() -> Self {
2115 Self(Path::new("").into())
2116 }
2117}
2118
2119impl<'a> sum_tree::Dimension<'a, EntrySummary> for PathKey {
2120 fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
2121 self.0 = summary.max_path.clone();
2122 }
2123}
2124
2125struct BackgroundScanner {
2126 snapshot: Mutex<LocalSnapshot>,
2127 fs: Arc<dyn Fs>,
2128 status_updates_tx: UnboundedSender<ScanState>,
2129 executor: Arc<executor::Background>,
2130 refresh_requests_rx: channel::Receiver<(Vec<PathBuf>, barrier::Sender)>,
2131 prev_state: Mutex<(Snapshot, Vec<Arc<Path>>)>,
2132 finished_initial_scan: bool,
2133}
2134
2135impl BackgroundScanner {
2136 fn new(
2137 snapshot: LocalSnapshot,
2138 fs: Arc<dyn Fs>,
2139 status_updates_tx: UnboundedSender<ScanState>,
2140 executor: Arc<executor::Background>,
2141 refresh_requests_rx: channel::Receiver<(Vec<PathBuf>, barrier::Sender)>,
2142 ) -> Self {
2143 Self {
2144 fs,
2145 status_updates_tx,
2146 executor,
2147 refresh_requests_rx,
2148 prev_state: Mutex::new((snapshot.snapshot.clone(), Vec::new())),
2149 snapshot: Mutex::new(snapshot),
2150 finished_initial_scan: false,
2151 }
2152 }
2153
2154 async fn run(
2155 &mut self,
2156 mut events_rx: Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>>,
2157 ) {
2158 use futures::FutureExt as _;
2159
2160 let (root_abs_path, root_inode) = {
2161 let snapshot = self.snapshot.lock();
2162 (
2163 snapshot.abs_path.clone(),
2164 snapshot.root_entry().map(|e| e.inode),
2165 )
2166 };
2167
2168 // Populate ignores above the root.
2169 let ignore_stack;
2170 for ancestor in root_abs_path.ancestors().skip(1) {
2171 if let Ok(ignore) = build_gitignore(&ancestor.join(&*GITIGNORE), self.fs.as_ref()).await
2172 {
2173 self.snapshot
2174 .lock()
2175 .ignores_by_parent_abs_path
2176 .insert(ancestor.into(), (ignore.into(), 0));
2177 }
2178 }
2179 {
2180 let mut snapshot = self.snapshot.lock();
2181 snapshot.scan_id += 1;
2182 ignore_stack = snapshot.ignore_stack_for_abs_path(&root_abs_path, true);
2183 if ignore_stack.is_all() {
2184 if let Some(mut root_entry) = snapshot.root_entry().cloned() {
2185 root_entry.is_ignored = true;
2186 snapshot.insert_entry(root_entry, self.fs.as_ref());
2187 }
2188 }
2189 };
2190
2191 // Perform an initial scan of the directory.
2192 let (scan_job_tx, scan_job_rx) = channel::unbounded();
2193 smol::block_on(scan_job_tx.send(ScanJob {
2194 abs_path: root_abs_path,
2195 path: Arc::from(Path::new("")),
2196 ignore_stack,
2197 ancestor_inodes: TreeSet::from_ordered_entries(root_inode),
2198 scan_queue: scan_job_tx.clone(),
2199 }))
2200 .unwrap();
2201 drop(scan_job_tx);
2202 self.scan_dirs(true, scan_job_rx).await;
2203 {
2204 let mut snapshot = self.snapshot.lock();
2205 snapshot.completed_scan_id = snapshot.scan_id;
2206 }
2207 self.send_status_update(false, None);
2208
2209 // Process any any FS events that occurred while performing the initial scan.
2210 // For these events, update events cannot be as precise, because we didn't
2211 // have the previous state loaded yet.
2212 if let Poll::Ready(Some(events)) = futures::poll!(events_rx.next()) {
2213 let mut paths = events.into_iter().map(|e| e.path).collect::<Vec<_>>();
2214 while let Poll::Ready(Some(more_events)) = futures::poll!(events_rx.next()) {
2215 paths.extend(more_events.into_iter().map(|e| e.path));
2216 }
2217 self.process_events(paths).await;
2218 }
2219
2220 self.finished_initial_scan = true;
2221
2222 // Continue processing events until the worktree is dropped.
2223 loop {
2224 select_biased! {
2225 // Process any path refresh requests from the worktree. Prioritize
2226 // these before handling changes reported by the filesystem.
2227 request = self.refresh_requests_rx.recv().fuse() => {
2228 let Ok((paths, barrier)) = request else { break };
2229 if !self.process_refresh_request(paths, barrier).await {
2230 return;
2231 }
2232 }
2233
2234 events = events_rx.next().fuse() => {
2235 let Some(events) = events else { break };
2236 let mut paths = events.into_iter().map(|e| e.path).collect::<Vec<_>>();
2237 while let Poll::Ready(Some(more_events)) = futures::poll!(events_rx.next()) {
2238 paths.extend(more_events.into_iter().map(|e| e.path));
2239 }
2240 self.process_events(paths).await;
2241 }
2242 }
2243 }
2244 }
2245
2246 async fn process_refresh_request(&self, paths: Vec<PathBuf>, barrier: barrier::Sender) -> bool {
2247 self.reload_entries_for_paths(paths, None).await;
2248 self.send_status_update(false, Some(barrier))
2249 }
2250
2251 async fn process_events(&mut self, paths: Vec<PathBuf>) {
2252 let (scan_job_tx, scan_job_rx) = channel::unbounded();
2253 if let Some(mut paths) = self
2254 .reload_entries_for_paths(paths, Some(scan_job_tx.clone()))
2255 .await
2256 {
2257 paths.sort_unstable();
2258 util::extend_sorted(&mut self.prev_state.lock().1, paths, usize::MAX, Ord::cmp);
2259 }
2260 drop(scan_job_tx);
2261 self.scan_dirs(false, scan_job_rx).await;
2262
2263 self.update_ignore_statuses().await;
2264
2265 let mut snapshot = self.snapshot.lock();
2266 let mut git_repositories = mem::take(&mut snapshot.git_repositories);
2267 git_repositories.retain(|repo| snapshot.entry_for_path(&repo.git_dir_path).is_some());
2268 snapshot.git_repositories = git_repositories;
2269 snapshot.removed_entry_ids.clear();
2270 snapshot.completed_scan_id = snapshot.scan_id;
2271 drop(snapshot);
2272
2273 self.send_status_update(false, None);
2274 }
2275
2276 async fn scan_dirs(
2277 &self,
2278 enable_progress_updates: bool,
2279 scan_jobs_rx: channel::Receiver<ScanJob>,
2280 ) {
2281 use futures::FutureExt as _;
2282
2283 if self
2284 .status_updates_tx
2285 .unbounded_send(ScanState::Started)
2286 .is_err()
2287 {
2288 return;
2289 }
2290
2291 let progress_update_count = AtomicUsize::new(0);
2292 self.executor
2293 .scoped(|scope| {
2294 for _ in 0..self.executor.num_cpus() {
2295 scope.spawn(async {
2296 let mut last_progress_update_count = 0;
2297 let progress_update_timer = self.progress_timer(enable_progress_updates).fuse();
2298 futures::pin_mut!(progress_update_timer);
2299
2300 loop {
2301 select_biased! {
2302 // Process any path refresh requests before moving on to process
2303 // the scan queue, so that user operations are prioritized.
2304 request = self.refresh_requests_rx.recv().fuse() => {
2305 let Ok((paths, barrier)) = request else { break };
2306 if !self.process_refresh_request(paths, barrier).await {
2307 return;
2308 }
2309 }
2310
2311 // Send periodic progress updates to the worktree. Use an atomic counter
2312 // to ensure that only one of the workers sends a progress update after
2313 // the update interval elapses.
2314 _ = progress_update_timer => {
2315 match progress_update_count.compare_exchange(
2316 last_progress_update_count,
2317 last_progress_update_count + 1,
2318 SeqCst,
2319 SeqCst
2320 ) {
2321 Ok(_) => {
2322 last_progress_update_count += 1;
2323 self.send_status_update(true, None);
2324 }
2325 Err(count) => {
2326 last_progress_update_count = count;
2327 }
2328 }
2329 progress_update_timer.set(self.progress_timer(enable_progress_updates).fuse());
2330 }
2331
2332 // Recursively load directories from the file system.
2333 job = scan_jobs_rx.recv().fuse() => {
2334 let Ok(job) = job else { break };
2335 if let Err(err) = self.scan_dir(&job).await {
2336 if job.path.as_ref() != Path::new("") {
2337 log::error!("error scanning directory {:?}: {}", job.abs_path, err);
2338 }
2339 }
2340 }
2341 }
2342 }
2343 })
2344 }
2345 })
2346 .await;
2347 }
2348
2349 fn send_status_update(&self, scanning: bool, barrier: Option<barrier::Sender>) -> bool {
2350 let mut prev_state = self.prev_state.lock();
2351 let snapshot = self.snapshot.lock().clone();
2352 let mut old_snapshot = snapshot.snapshot.clone();
2353 mem::swap(&mut old_snapshot, &mut prev_state.0);
2354 let changed_paths = mem::take(&mut prev_state.1);
2355 let changes = self.build_change_set(&old_snapshot, &snapshot.snapshot, changed_paths);
2356 self.status_updates_tx
2357 .unbounded_send(ScanState::Updated {
2358 snapshot,
2359 changes,
2360 scanning,
2361 barrier,
2362 })
2363 .is_ok()
2364 }
2365
2366 async fn scan_dir(&self, job: &ScanJob) -> Result<()> {
2367 let mut new_entries: Vec<Entry> = Vec::new();
2368 let mut new_jobs: Vec<Option<ScanJob>> = Vec::new();
2369 let mut ignore_stack = job.ignore_stack.clone();
2370 let mut new_ignore = None;
2371 let (root_abs_path, root_char_bag, next_entry_id) = {
2372 let snapshot = self.snapshot.lock();
2373 (
2374 snapshot.abs_path().clone(),
2375 snapshot.root_char_bag,
2376 snapshot.next_entry_id.clone(),
2377 )
2378 };
2379 let mut child_paths = self.fs.read_dir(&job.abs_path).await?;
2380 while let Some(child_abs_path) = child_paths.next().await {
2381 let child_abs_path: Arc<Path> = match child_abs_path {
2382 Ok(child_abs_path) => child_abs_path.into(),
2383 Err(error) => {
2384 log::error!("error processing entry {:?}", error);
2385 continue;
2386 }
2387 };
2388
2389 let child_name = child_abs_path.file_name().unwrap();
2390 let child_path: Arc<Path> = job.path.join(child_name).into();
2391 let child_metadata = match self.fs.metadata(&child_abs_path).await {
2392 Ok(Some(metadata)) => metadata,
2393 Ok(None) => continue,
2394 Err(err) => {
2395 log::error!("error processing {:?}: {:?}", child_abs_path, err);
2396 continue;
2397 }
2398 };
2399
2400 // If we find a .gitignore, add it to the stack of ignores used to determine which paths are ignored
2401 if child_name == *GITIGNORE {
2402 match build_gitignore(&child_abs_path, self.fs.as_ref()).await {
2403 Ok(ignore) => {
2404 let ignore = Arc::new(ignore);
2405 ignore_stack = ignore_stack.append(job.abs_path.clone(), ignore.clone());
2406 new_ignore = Some(ignore);
2407 }
2408 Err(error) => {
2409 log::error!(
2410 "error loading .gitignore file {:?} - {:?}",
2411 child_name,
2412 error
2413 );
2414 }
2415 }
2416
2417 // Update ignore status of any child entries we've already processed to reflect the
2418 // ignore file in the current directory. Because `.gitignore` starts with a `.`,
2419 // there should rarely be too numerous. Update the ignore stack associated with any
2420 // new jobs as well.
2421 let mut new_jobs = new_jobs.iter_mut();
2422 for entry in &mut new_entries {
2423 let entry_abs_path = root_abs_path.join(&entry.path);
2424 entry.is_ignored =
2425 ignore_stack.is_abs_path_ignored(&entry_abs_path, entry.is_dir());
2426
2427 if entry.is_dir() {
2428 if let Some(job) = new_jobs.next().expect("Missing scan job for entry") {
2429 job.ignore_stack = if entry.is_ignored {
2430 IgnoreStack::all()
2431 } else {
2432 ignore_stack.clone()
2433 };
2434 }
2435 }
2436 }
2437 }
2438
2439 let mut child_entry = Entry::new(
2440 child_path.clone(),
2441 &child_metadata,
2442 &next_entry_id,
2443 root_char_bag,
2444 );
2445
2446 if child_entry.is_dir() {
2447 let is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, true);
2448 child_entry.is_ignored = is_ignored;
2449
2450 // Avoid recursing until crash in the case of a recursive symlink
2451 if !job.ancestor_inodes.contains(&child_entry.inode) {
2452 let mut ancestor_inodes = job.ancestor_inodes.clone();
2453 ancestor_inodes.insert(child_entry.inode);
2454
2455 new_jobs.push(Some(ScanJob {
2456 abs_path: child_abs_path,
2457 path: child_path,
2458 ignore_stack: if is_ignored {
2459 IgnoreStack::all()
2460 } else {
2461 ignore_stack.clone()
2462 },
2463 ancestor_inodes,
2464 scan_queue: job.scan_queue.clone(),
2465 }));
2466 } else {
2467 new_jobs.push(None);
2468 }
2469 } else {
2470 child_entry.is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, false);
2471 }
2472
2473 new_entries.push(child_entry);
2474 }
2475
2476 self.snapshot.lock().populate_dir(
2477 job.path.clone(),
2478 new_entries,
2479 new_ignore,
2480 self.fs.as_ref(),
2481 );
2482
2483 for new_job in new_jobs {
2484 if let Some(new_job) = new_job {
2485 job.scan_queue.send(new_job).await.unwrap();
2486 }
2487 }
2488
2489 Ok(())
2490 }
2491
2492 async fn reload_entries_for_paths(
2493 &self,
2494 mut abs_paths: Vec<PathBuf>,
2495 scan_queue_tx: Option<Sender<ScanJob>>,
2496 ) -> Option<Vec<Arc<Path>>> {
2497 let doing_recursive_update = scan_queue_tx.is_some();
2498
2499 abs_paths.sort_unstable();
2500 abs_paths.dedup_by(|a, b| a.starts_with(&b));
2501
2502 let root_abs_path = self.snapshot.lock().abs_path.clone();
2503 let root_canonical_path = self.fs.canonicalize(&root_abs_path).await.log_err()?;
2504 let metadata = futures::future::join_all(
2505 abs_paths
2506 .iter()
2507 .map(|abs_path| self.fs.metadata(&abs_path))
2508 .collect::<Vec<_>>(),
2509 )
2510 .await;
2511
2512 let mut snapshot = self.snapshot.lock();
2513 let is_idle = snapshot.completed_scan_id == snapshot.scan_id;
2514 snapshot.scan_id += 1;
2515 if is_idle && !doing_recursive_update {
2516 snapshot.completed_scan_id = snapshot.scan_id;
2517 }
2518
2519 // Remove any entries for paths that no longer exist or are being recursively
2520 // refreshed. Do this before adding any new entries, so that renames can be
2521 // detected regardless of the order of the paths.
2522 let mut event_paths = Vec::<Arc<Path>>::with_capacity(abs_paths.len());
2523 for (abs_path, metadata) in abs_paths.iter().zip(metadata.iter()) {
2524 if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) {
2525 if matches!(metadata, Ok(None)) || doing_recursive_update {
2526 snapshot.remove_path(path);
2527 }
2528 event_paths.push(path.into());
2529 } else {
2530 log::error!(
2531 "unexpected event {:?} for root path {:?}",
2532 abs_path,
2533 root_canonical_path
2534 );
2535 }
2536 }
2537
2538 for (path, metadata) in event_paths.iter().cloned().zip(metadata.into_iter()) {
2539 let abs_path: Arc<Path> = root_abs_path.join(&path).into();
2540
2541 match metadata {
2542 Ok(Some(metadata)) => {
2543 let ignore_stack =
2544 snapshot.ignore_stack_for_abs_path(&abs_path, metadata.is_dir);
2545 let mut fs_entry = Entry::new(
2546 path.clone(),
2547 &metadata,
2548 snapshot.next_entry_id.as_ref(),
2549 snapshot.root_char_bag,
2550 );
2551 fs_entry.is_ignored = ignore_stack.is_all();
2552 snapshot.insert_entry(fs_entry, self.fs.as_ref());
2553
2554 let scan_id = snapshot.scan_id;
2555 if let Some(repo) = snapshot.repo_with_dot_git_containing(&path) {
2556 repo.repo.lock().reload_index();
2557 repo.scan_id = scan_id;
2558 }
2559
2560 if let Some(scan_queue_tx) = &scan_queue_tx {
2561 let mut ancestor_inodes = snapshot.ancestor_inodes_for_path(&path);
2562 if metadata.is_dir && !ancestor_inodes.contains(&metadata.inode) {
2563 ancestor_inodes.insert(metadata.inode);
2564 smol::block_on(scan_queue_tx.send(ScanJob {
2565 abs_path,
2566 path,
2567 ignore_stack,
2568 ancestor_inodes,
2569 scan_queue: scan_queue_tx.clone(),
2570 }))
2571 .unwrap();
2572 }
2573 }
2574 }
2575 Ok(None) => {}
2576 Err(err) => {
2577 // TODO - create a special 'error' entry in the entries tree to mark this
2578 log::error!("error reading file on event {:?}", err);
2579 }
2580 }
2581 }
2582
2583 Some(event_paths)
2584 }
2585
2586 async fn update_ignore_statuses(&self) {
2587 use futures::FutureExt as _;
2588
2589 let mut snapshot = self.snapshot.lock().clone();
2590 let mut ignores_to_update = Vec::new();
2591 let mut ignores_to_delete = Vec::new();
2592 for (parent_abs_path, (_, scan_id)) in &snapshot.ignores_by_parent_abs_path {
2593 if let Ok(parent_path) = parent_abs_path.strip_prefix(&snapshot.abs_path) {
2594 if *scan_id > snapshot.completed_scan_id
2595 && snapshot.entry_for_path(parent_path).is_some()
2596 {
2597 ignores_to_update.push(parent_abs_path.clone());
2598 }
2599
2600 let ignore_path = parent_path.join(&*GITIGNORE);
2601 if snapshot.entry_for_path(ignore_path).is_none() {
2602 ignores_to_delete.push(parent_abs_path.clone());
2603 }
2604 }
2605 }
2606
2607 for parent_abs_path in ignores_to_delete {
2608 snapshot.ignores_by_parent_abs_path.remove(&parent_abs_path);
2609 self.snapshot
2610 .lock()
2611 .ignores_by_parent_abs_path
2612 .remove(&parent_abs_path);
2613 }
2614
2615 let (ignore_queue_tx, ignore_queue_rx) = channel::unbounded();
2616 ignores_to_update.sort_unstable();
2617 let mut ignores_to_update = ignores_to_update.into_iter().peekable();
2618 while let Some(parent_abs_path) = ignores_to_update.next() {
2619 while ignores_to_update
2620 .peek()
2621 .map_or(false, |p| p.starts_with(&parent_abs_path))
2622 {
2623 ignores_to_update.next().unwrap();
2624 }
2625
2626 let ignore_stack = snapshot.ignore_stack_for_abs_path(&parent_abs_path, true);
2627 smol::block_on(ignore_queue_tx.send(UpdateIgnoreStatusJob {
2628 abs_path: parent_abs_path,
2629 ignore_stack,
2630 ignore_queue: ignore_queue_tx.clone(),
2631 }))
2632 .unwrap();
2633 }
2634 drop(ignore_queue_tx);
2635
2636 self.executor
2637 .scoped(|scope| {
2638 for _ in 0..self.executor.num_cpus() {
2639 scope.spawn(async {
2640 loop {
2641 select_biased! {
2642 // Process any path refresh requests before moving on to process
2643 // the queue of ignore statuses.
2644 request = self.refresh_requests_rx.recv().fuse() => {
2645 let Ok((paths, barrier)) = request else { break };
2646 if !self.process_refresh_request(paths, barrier).await {
2647 return;
2648 }
2649 }
2650
2651 // Recursively process directories whose ignores have changed.
2652 job = ignore_queue_rx.recv().fuse() => {
2653 let Ok(job) = job else { break };
2654 self.update_ignore_status(job, &snapshot).await;
2655 }
2656 }
2657 }
2658 });
2659 }
2660 })
2661 .await;
2662 }
2663
2664 async fn update_ignore_status(&self, job: UpdateIgnoreStatusJob, snapshot: &LocalSnapshot) {
2665 let mut ignore_stack = job.ignore_stack;
2666 if let Some((ignore, _)) = snapshot.ignores_by_parent_abs_path.get(&job.abs_path) {
2667 ignore_stack = ignore_stack.append(job.abs_path.clone(), ignore.clone());
2668 }
2669
2670 let mut entries_by_id_edits = Vec::new();
2671 let mut entries_by_path_edits = Vec::new();
2672 let path = job.abs_path.strip_prefix(&snapshot.abs_path).unwrap();
2673 for mut entry in snapshot.child_entries(path).cloned() {
2674 let was_ignored = entry.is_ignored;
2675 let abs_path = snapshot.abs_path().join(&entry.path);
2676 entry.is_ignored = ignore_stack.is_abs_path_ignored(&abs_path, entry.is_dir());
2677 if entry.is_dir() {
2678 let child_ignore_stack = if entry.is_ignored {
2679 IgnoreStack::all()
2680 } else {
2681 ignore_stack.clone()
2682 };
2683 job.ignore_queue
2684 .send(UpdateIgnoreStatusJob {
2685 abs_path: abs_path.into(),
2686 ignore_stack: child_ignore_stack,
2687 ignore_queue: job.ignore_queue.clone(),
2688 })
2689 .await
2690 .unwrap();
2691 }
2692
2693 if entry.is_ignored != was_ignored {
2694 let mut path_entry = snapshot.entries_by_id.get(&entry.id, &()).unwrap().clone();
2695 path_entry.scan_id = snapshot.scan_id;
2696 path_entry.is_ignored = entry.is_ignored;
2697 entries_by_id_edits.push(Edit::Insert(path_entry));
2698 entries_by_path_edits.push(Edit::Insert(entry));
2699 }
2700 }
2701
2702 let mut snapshot = self.snapshot.lock();
2703 snapshot.entries_by_path.edit(entries_by_path_edits, &());
2704 snapshot.entries_by_id.edit(entries_by_id_edits, &());
2705 }
2706
2707 fn build_change_set(
2708 &self,
2709 old_snapshot: &Snapshot,
2710 new_snapshot: &Snapshot,
2711 event_paths: Vec<Arc<Path>>,
2712 ) -> HashMap<Arc<Path>, PathChange> {
2713 use PathChange::{Added, AddedOrUpdated, Removed, Updated};
2714
2715 let mut changes = HashMap::default();
2716 let mut old_paths = old_snapshot.entries_by_path.cursor::<PathKey>();
2717 let mut new_paths = new_snapshot.entries_by_path.cursor::<PathKey>();
2718 let received_before_initialized = !self.finished_initial_scan;
2719
2720 for path in event_paths {
2721 let path = PathKey(path);
2722 old_paths.seek(&path, Bias::Left, &());
2723 new_paths.seek(&path, Bias::Left, &());
2724
2725 loop {
2726 match (old_paths.item(), new_paths.item()) {
2727 (Some(old_entry), Some(new_entry)) => {
2728 if old_entry.path > path.0
2729 && new_entry.path > path.0
2730 && !old_entry.path.starts_with(&path.0)
2731 && !new_entry.path.starts_with(&path.0)
2732 {
2733 break;
2734 }
2735
2736 match Ord::cmp(&old_entry.path, &new_entry.path) {
2737 Ordering::Less => {
2738 changes.insert(old_entry.path.clone(), Removed);
2739 old_paths.next(&());
2740 }
2741 Ordering::Equal => {
2742 if received_before_initialized {
2743 // If the worktree was not fully initialized when this event was generated,
2744 // we can't know whether this entry was added during the scan or whether
2745 // it was merely updated.
2746 changes.insert(new_entry.path.clone(), AddedOrUpdated);
2747 } else if old_entry.mtime != new_entry.mtime {
2748 changes.insert(new_entry.path.clone(), Updated);
2749 }
2750 old_paths.next(&());
2751 new_paths.next(&());
2752 }
2753 Ordering::Greater => {
2754 changes.insert(new_entry.path.clone(), Added);
2755 new_paths.next(&());
2756 }
2757 }
2758 }
2759 (Some(old_entry), None) => {
2760 changes.insert(old_entry.path.clone(), Removed);
2761 old_paths.next(&());
2762 }
2763 (None, Some(new_entry)) => {
2764 changes.insert(new_entry.path.clone(), Added);
2765 new_paths.next(&());
2766 }
2767 (None, None) => break,
2768 }
2769 }
2770 }
2771 changes
2772 }
2773
2774 async fn progress_timer(&self, running: bool) {
2775 if !running {
2776 return futures::future::pending().await;
2777 }
2778
2779 #[cfg(any(test, feature = "test-support"))]
2780 if self.fs.is_fake() {
2781 return self.executor.simulate_random_delay().await;
2782 }
2783
2784 smol::Timer::after(Duration::from_millis(100)).await;
2785 }
2786}
2787
2788fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag {
2789 let mut result = root_char_bag;
2790 result.extend(
2791 path.to_string_lossy()
2792 .chars()
2793 .map(|c| c.to_ascii_lowercase()),
2794 );
2795 result
2796}
2797
2798struct ScanJob {
2799 abs_path: Arc<Path>,
2800 path: Arc<Path>,
2801 ignore_stack: Arc<IgnoreStack>,
2802 scan_queue: Sender<ScanJob>,
2803 ancestor_inodes: TreeSet<u64>,
2804}
2805
2806struct UpdateIgnoreStatusJob {
2807 abs_path: Arc<Path>,
2808 ignore_stack: Arc<IgnoreStack>,
2809 ignore_queue: Sender<UpdateIgnoreStatusJob>,
2810}
2811
2812pub trait WorktreeHandle {
2813 #[cfg(any(test, feature = "test-support"))]
2814 fn flush_fs_events<'a>(
2815 &self,
2816 cx: &'a gpui::TestAppContext,
2817 ) -> futures::future::LocalBoxFuture<'a, ()>;
2818}
2819
2820impl WorktreeHandle for ModelHandle<Worktree> {
2821 // When the worktree's FS event stream sometimes delivers "redundant" events for FS changes that
2822 // occurred before the worktree was constructed. These events can cause the worktree to perfrom
2823 // extra directory scans, and emit extra scan-state notifications.
2824 //
2825 // This function mutates the worktree's directory and waits for those mutations to be picked up,
2826 // to ensure that all redundant FS events have already been processed.
2827 #[cfg(any(test, feature = "test-support"))]
2828 fn flush_fs_events<'a>(
2829 &self,
2830 cx: &'a gpui::TestAppContext,
2831 ) -> futures::future::LocalBoxFuture<'a, ()> {
2832 use smol::future::FutureExt;
2833
2834 let filename = "fs-event-sentinel";
2835 let tree = self.clone();
2836 let (fs, root_path) = self.read_with(cx, |tree, _| {
2837 let tree = tree.as_local().unwrap();
2838 (tree.fs.clone(), tree.abs_path().clone())
2839 });
2840
2841 async move {
2842 fs.create_file(&root_path.join(filename), Default::default())
2843 .await
2844 .unwrap();
2845 tree.condition(cx, |tree, _| tree.entry_for_path(filename).is_some())
2846 .await;
2847
2848 fs.remove_file(&root_path.join(filename), Default::default())
2849 .await
2850 .unwrap();
2851 tree.condition(cx, |tree, _| tree.entry_for_path(filename).is_none())
2852 .await;
2853
2854 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
2855 .await;
2856 }
2857 .boxed_local()
2858 }
2859}
2860
2861#[derive(Clone, Debug)]
2862struct TraversalProgress<'a> {
2863 max_path: &'a Path,
2864 count: usize,
2865 visible_count: usize,
2866 file_count: usize,
2867 visible_file_count: usize,
2868}
2869
2870impl<'a> TraversalProgress<'a> {
2871 fn count(&self, include_dirs: bool, include_ignored: bool) -> usize {
2872 match (include_ignored, include_dirs) {
2873 (true, true) => self.count,
2874 (true, false) => self.file_count,
2875 (false, true) => self.visible_count,
2876 (false, false) => self.visible_file_count,
2877 }
2878 }
2879}
2880
2881impl<'a> sum_tree::Dimension<'a, EntrySummary> for TraversalProgress<'a> {
2882 fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
2883 self.max_path = summary.max_path.as_ref();
2884 self.count += summary.count;
2885 self.visible_count += summary.visible_count;
2886 self.file_count += summary.file_count;
2887 self.visible_file_count += summary.visible_file_count;
2888 }
2889}
2890
2891impl<'a> Default for TraversalProgress<'a> {
2892 fn default() -> Self {
2893 Self {
2894 max_path: Path::new(""),
2895 count: 0,
2896 visible_count: 0,
2897 file_count: 0,
2898 visible_file_count: 0,
2899 }
2900 }
2901}
2902
2903pub struct Traversal<'a> {
2904 cursor: sum_tree::Cursor<'a, Entry, TraversalProgress<'a>>,
2905 include_ignored: bool,
2906 include_dirs: bool,
2907}
2908
2909impl<'a> Traversal<'a> {
2910 pub fn advance(&mut self) -> bool {
2911 self.advance_to_offset(self.offset() + 1)
2912 }
2913
2914 pub fn advance_to_offset(&mut self, offset: usize) -> bool {
2915 self.cursor.seek_forward(
2916 &TraversalTarget::Count {
2917 count: offset,
2918 include_dirs: self.include_dirs,
2919 include_ignored: self.include_ignored,
2920 },
2921 Bias::Right,
2922 &(),
2923 )
2924 }
2925
2926 pub fn advance_to_sibling(&mut self) -> bool {
2927 while let Some(entry) = self.cursor.item() {
2928 self.cursor.seek_forward(
2929 &TraversalTarget::PathSuccessor(&entry.path),
2930 Bias::Left,
2931 &(),
2932 );
2933 if let Some(entry) = self.cursor.item() {
2934 if (self.include_dirs || !entry.is_dir())
2935 && (self.include_ignored || !entry.is_ignored)
2936 {
2937 return true;
2938 }
2939 }
2940 }
2941 false
2942 }
2943
2944 pub fn entry(&self) -> Option<&'a Entry> {
2945 self.cursor.item()
2946 }
2947
2948 pub fn offset(&self) -> usize {
2949 self.cursor
2950 .start()
2951 .count(self.include_dirs, self.include_ignored)
2952 }
2953}
2954
2955impl<'a> Iterator for Traversal<'a> {
2956 type Item = &'a Entry;
2957
2958 fn next(&mut self) -> Option<Self::Item> {
2959 if let Some(item) = self.entry() {
2960 self.advance();
2961 Some(item)
2962 } else {
2963 None
2964 }
2965 }
2966}
2967
2968#[derive(Debug)]
2969enum TraversalTarget<'a> {
2970 Path(&'a Path),
2971 PathSuccessor(&'a Path),
2972 Count {
2973 count: usize,
2974 include_ignored: bool,
2975 include_dirs: bool,
2976 },
2977}
2978
2979impl<'a, 'b> SeekTarget<'a, EntrySummary, TraversalProgress<'a>> for TraversalTarget<'b> {
2980 fn cmp(&self, cursor_location: &TraversalProgress<'a>, _: &()) -> Ordering {
2981 match self {
2982 TraversalTarget::Path(path) => path.cmp(&cursor_location.max_path),
2983 TraversalTarget::PathSuccessor(path) => {
2984 if !cursor_location.max_path.starts_with(path) {
2985 Ordering::Equal
2986 } else {
2987 Ordering::Greater
2988 }
2989 }
2990 TraversalTarget::Count {
2991 count,
2992 include_dirs,
2993 include_ignored,
2994 } => Ord::cmp(
2995 count,
2996 &cursor_location.count(*include_dirs, *include_ignored),
2997 ),
2998 }
2999 }
3000}
3001
3002struct ChildEntriesIter<'a> {
3003 parent_path: &'a Path,
3004 traversal: Traversal<'a>,
3005}
3006
3007impl<'a> Iterator for ChildEntriesIter<'a> {
3008 type Item = &'a Entry;
3009
3010 fn next(&mut self) -> Option<Self::Item> {
3011 if let Some(item) = self.traversal.entry() {
3012 if item.path.starts_with(&self.parent_path) {
3013 self.traversal.advance_to_sibling();
3014 return Some(item);
3015 }
3016 }
3017 None
3018 }
3019}
3020
3021impl<'a> From<&'a Entry> for proto::Entry {
3022 fn from(entry: &'a Entry) -> Self {
3023 Self {
3024 id: entry.id.to_proto(),
3025 is_dir: entry.is_dir(),
3026 path: entry.path.to_string_lossy().into(),
3027 inode: entry.inode,
3028 mtime: Some(entry.mtime.into()),
3029 is_symlink: entry.is_symlink,
3030 is_ignored: entry.is_ignored,
3031 }
3032 }
3033}
3034
3035impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry {
3036 type Error = anyhow::Error;
3037
3038 fn try_from((root_char_bag, entry): (&'a CharBag, proto::Entry)) -> Result<Self> {
3039 if let Some(mtime) = entry.mtime {
3040 let kind = if entry.is_dir {
3041 EntryKind::Dir
3042 } else {
3043 let mut char_bag = *root_char_bag;
3044 char_bag.extend(entry.path.chars().map(|c| c.to_ascii_lowercase()));
3045 EntryKind::File(char_bag)
3046 };
3047 let path: Arc<Path> = PathBuf::from(entry.path).into();
3048 Ok(Entry {
3049 id: ProjectEntryId::from_proto(entry.id),
3050 kind,
3051 path,
3052 inode: entry.inode,
3053 mtime: mtime.into(),
3054 is_symlink: entry.is_symlink,
3055 is_ignored: entry.is_ignored,
3056 })
3057 } else {
3058 Err(anyhow!(
3059 "missing mtime in remote worktree entry {:?}",
3060 entry.path
3061 ))
3062 }
3063 }
3064}
3065
3066#[cfg(test)]
3067mod tests {
3068 use super::*;
3069 use fs::repository::FakeGitRepository;
3070 use fs::{FakeFs, RealFs};
3071 use gpui::{executor::Deterministic, TestAppContext};
3072 use pretty_assertions::assert_eq;
3073 use rand::prelude::*;
3074 use serde_json::json;
3075 use std::{env, fmt::Write};
3076 use util::{http::FakeHttpClient, test::temp_tree};
3077
3078 #[gpui::test]
3079 async fn test_traversal(cx: &mut TestAppContext) {
3080 let fs = FakeFs::new(cx.background());
3081 fs.insert_tree(
3082 "/root",
3083 json!({
3084 ".gitignore": "a/b\n",
3085 "a": {
3086 "b": "",
3087 "c": "",
3088 }
3089 }),
3090 )
3091 .await;
3092
3093 let http_client = FakeHttpClient::with_404_response();
3094 let client = cx.read(|cx| Client::new(http_client, cx));
3095
3096 let tree = Worktree::local(
3097 client,
3098 Path::new("/root"),
3099 true,
3100 fs,
3101 Default::default(),
3102 &mut cx.to_async(),
3103 )
3104 .await
3105 .unwrap();
3106 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
3107 .await;
3108
3109 tree.read_with(cx, |tree, _| {
3110 assert_eq!(
3111 tree.entries(false)
3112 .map(|entry| entry.path.as_ref())
3113 .collect::<Vec<_>>(),
3114 vec![
3115 Path::new(""),
3116 Path::new(".gitignore"),
3117 Path::new("a"),
3118 Path::new("a/c"),
3119 ]
3120 );
3121 assert_eq!(
3122 tree.entries(true)
3123 .map(|entry| entry.path.as_ref())
3124 .collect::<Vec<_>>(),
3125 vec![
3126 Path::new(""),
3127 Path::new(".gitignore"),
3128 Path::new("a"),
3129 Path::new("a/b"),
3130 Path::new("a/c"),
3131 ]
3132 );
3133 })
3134 }
3135
3136 #[gpui::test(iterations = 10)]
3137 async fn test_circular_symlinks(executor: Arc<Deterministic>, cx: &mut TestAppContext) {
3138 let fs = FakeFs::new(cx.background());
3139 fs.insert_tree(
3140 "/root",
3141 json!({
3142 "lib": {
3143 "a": {
3144 "a.txt": ""
3145 },
3146 "b": {
3147 "b.txt": ""
3148 }
3149 }
3150 }),
3151 )
3152 .await;
3153 fs.insert_symlink("/root/lib/a/lib", "..".into()).await;
3154 fs.insert_symlink("/root/lib/b/lib", "..".into()).await;
3155
3156 let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
3157 let tree = Worktree::local(
3158 client,
3159 Path::new("/root"),
3160 true,
3161 fs.clone(),
3162 Default::default(),
3163 &mut cx.to_async(),
3164 )
3165 .await
3166 .unwrap();
3167
3168 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
3169 .await;
3170
3171 tree.read_with(cx, |tree, _| {
3172 assert_eq!(
3173 tree.entries(false)
3174 .map(|entry| entry.path.as_ref())
3175 .collect::<Vec<_>>(),
3176 vec![
3177 Path::new(""),
3178 Path::new("lib"),
3179 Path::new("lib/a"),
3180 Path::new("lib/a/a.txt"),
3181 Path::new("lib/a/lib"),
3182 Path::new("lib/b"),
3183 Path::new("lib/b/b.txt"),
3184 Path::new("lib/b/lib"),
3185 ]
3186 );
3187 });
3188
3189 fs.rename(
3190 Path::new("/root/lib/a/lib"),
3191 Path::new("/root/lib/a/lib-2"),
3192 Default::default(),
3193 )
3194 .await
3195 .unwrap();
3196 executor.run_until_parked();
3197 tree.read_with(cx, |tree, _| {
3198 assert_eq!(
3199 tree.entries(false)
3200 .map(|entry| entry.path.as_ref())
3201 .collect::<Vec<_>>(),
3202 vec![
3203 Path::new(""),
3204 Path::new("lib"),
3205 Path::new("lib/a"),
3206 Path::new("lib/a/a.txt"),
3207 Path::new("lib/a/lib-2"),
3208 Path::new("lib/b"),
3209 Path::new("lib/b/b.txt"),
3210 Path::new("lib/b/lib"),
3211 ]
3212 );
3213 });
3214 }
3215
3216 #[gpui::test]
3217 async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
3218 let parent_dir = temp_tree(json!({
3219 ".gitignore": "ancestor-ignored-file1\nancestor-ignored-file2\n",
3220 "tree": {
3221 ".git": {},
3222 ".gitignore": "ignored-dir\n",
3223 "tracked-dir": {
3224 "tracked-file1": "",
3225 "ancestor-ignored-file1": "",
3226 },
3227 "ignored-dir": {
3228 "ignored-file1": ""
3229 }
3230 }
3231 }));
3232 let dir = parent_dir.path().join("tree");
3233
3234 let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
3235
3236 let tree = Worktree::local(
3237 client,
3238 dir.as_path(),
3239 true,
3240 Arc::new(RealFs),
3241 Default::default(),
3242 &mut cx.to_async(),
3243 )
3244 .await
3245 .unwrap();
3246 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
3247 .await;
3248 tree.flush_fs_events(cx).await;
3249 cx.read(|cx| {
3250 let tree = tree.read(cx);
3251 assert!(
3252 !tree
3253 .entry_for_path("tracked-dir/tracked-file1")
3254 .unwrap()
3255 .is_ignored
3256 );
3257 assert!(
3258 tree.entry_for_path("tracked-dir/ancestor-ignored-file1")
3259 .unwrap()
3260 .is_ignored
3261 );
3262 assert!(
3263 tree.entry_for_path("ignored-dir/ignored-file1")
3264 .unwrap()
3265 .is_ignored
3266 );
3267 });
3268
3269 std::fs::write(dir.join("tracked-dir/tracked-file2"), "").unwrap();
3270 std::fs::write(dir.join("tracked-dir/ancestor-ignored-file2"), "").unwrap();
3271 std::fs::write(dir.join("ignored-dir/ignored-file2"), "").unwrap();
3272 tree.flush_fs_events(cx).await;
3273 cx.read(|cx| {
3274 let tree = tree.read(cx);
3275 assert!(
3276 !tree
3277 .entry_for_path("tracked-dir/tracked-file2")
3278 .unwrap()
3279 .is_ignored
3280 );
3281 assert!(
3282 tree.entry_for_path("tracked-dir/ancestor-ignored-file2")
3283 .unwrap()
3284 .is_ignored
3285 );
3286 assert!(
3287 tree.entry_for_path("ignored-dir/ignored-file2")
3288 .unwrap()
3289 .is_ignored
3290 );
3291 assert!(tree.entry_for_path(".git").unwrap().is_ignored);
3292 });
3293 }
3294
3295 #[gpui::test]
3296 async fn test_git_repository_for_path(cx: &mut TestAppContext) {
3297 let root = temp_tree(json!({
3298 "dir1": {
3299 ".git": {},
3300 "deps": {
3301 "dep1": {
3302 ".git": {},
3303 "src": {
3304 "a.txt": ""
3305 }
3306 }
3307 },
3308 "src": {
3309 "b.txt": ""
3310 }
3311 },
3312 "c.txt": "",
3313 }));
3314
3315 let http_client = FakeHttpClient::with_404_response();
3316 let client = cx.read(|cx| Client::new(http_client, cx));
3317 let tree = Worktree::local(
3318 client,
3319 root.path(),
3320 true,
3321 Arc::new(RealFs),
3322 Default::default(),
3323 &mut cx.to_async(),
3324 )
3325 .await
3326 .unwrap();
3327
3328 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
3329 .await;
3330 tree.flush_fs_events(cx).await;
3331
3332 tree.read_with(cx, |tree, _cx| {
3333 let tree = tree.as_local().unwrap();
3334
3335 assert!(tree.repo_for("c.txt".as_ref()).is_none());
3336
3337 let repo = tree.repo_for("dir1/src/b.txt".as_ref()).unwrap();
3338 assert_eq!(repo.content_path.as_ref(), Path::new("dir1"));
3339 assert_eq!(repo.git_dir_path.as_ref(), Path::new("dir1/.git"));
3340
3341 let repo = tree.repo_for("dir1/deps/dep1/src/a.txt".as_ref()).unwrap();
3342 assert_eq!(repo.content_path.as_ref(), Path::new("dir1/deps/dep1"));
3343 assert_eq!(repo.git_dir_path.as_ref(), Path::new("dir1/deps/dep1/.git"),);
3344 });
3345
3346 let original_scan_id = tree.read_with(cx, |tree, _cx| {
3347 let tree = tree.as_local().unwrap();
3348 tree.repo_for("dir1/src/b.txt".as_ref()).unwrap().scan_id
3349 });
3350
3351 std::fs::write(root.path().join("dir1/.git/random_new_file"), "hello").unwrap();
3352 tree.flush_fs_events(cx).await;
3353
3354 tree.read_with(cx, |tree, _cx| {
3355 let tree = tree.as_local().unwrap();
3356 let new_scan_id = tree.repo_for("dir1/src/b.txt".as_ref()).unwrap().scan_id;
3357 assert_ne!(
3358 original_scan_id, new_scan_id,
3359 "original {original_scan_id}, new {new_scan_id}"
3360 );
3361 });
3362
3363 std::fs::remove_dir_all(root.path().join("dir1/.git")).unwrap();
3364 tree.flush_fs_events(cx).await;
3365
3366 tree.read_with(cx, |tree, _cx| {
3367 let tree = tree.as_local().unwrap();
3368
3369 assert!(tree.repo_for("dir1/src/b.txt".as_ref()).is_none());
3370 });
3371 }
3372
3373 #[test]
3374 fn test_changed_repos() {
3375 fn fake_entry(git_dir_path: impl AsRef<Path>, scan_id: usize) -> GitRepositoryEntry {
3376 GitRepositoryEntry {
3377 repo: Arc::new(Mutex::new(FakeGitRepository::default())),
3378 scan_id,
3379 content_path: git_dir_path.as_ref().parent().unwrap().into(),
3380 git_dir_path: git_dir_path.as_ref().into(),
3381 }
3382 }
3383
3384 let prev_repos: Vec<GitRepositoryEntry> = vec![
3385 fake_entry("/.git", 0),
3386 fake_entry("/a/.git", 0),
3387 fake_entry("/a/b/.git", 0),
3388 ];
3389
3390 let new_repos: Vec<GitRepositoryEntry> = vec![
3391 fake_entry("/a/.git", 1),
3392 fake_entry("/a/b/.git", 0),
3393 fake_entry("/a/c/.git", 0),
3394 ];
3395
3396 let res = LocalWorktree::changed_repos(&prev_repos, &new_repos);
3397
3398 // Deletion retained
3399 assert!(res
3400 .iter()
3401 .find(|repo| repo.git_dir_path.as_ref() == Path::new("/.git") && repo.scan_id == 0)
3402 .is_some());
3403
3404 // Update retained
3405 assert!(res
3406 .iter()
3407 .find(|repo| repo.git_dir_path.as_ref() == Path::new("/a/.git") && repo.scan_id == 1)
3408 .is_some());
3409
3410 // Addition retained
3411 assert!(res
3412 .iter()
3413 .find(|repo| repo.git_dir_path.as_ref() == Path::new("/a/c/.git") && repo.scan_id == 0)
3414 .is_some());
3415
3416 // Nochange, not retained
3417 assert!(res
3418 .iter()
3419 .find(|repo| repo.git_dir_path.as_ref() == Path::new("/a/b/.git") && repo.scan_id == 0)
3420 .is_none());
3421 }
3422
3423 #[gpui::test]
3424 async fn test_write_file(cx: &mut TestAppContext) {
3425 let dir = temp_tree(json!({
3426 ".git": {},
3427 ".gitignore": "ignored-dir\n",
3428 "tracked-dir": {},
3429 "ignored-dir": {}
3430 }));
3431
3432 let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
3433
3434 let tree = Worktree::local(
3435 client,
3436 dir.path(),
3437 true,
3438 Arc::new(RealFs),
3439 Default::default(),
3440 &mut cx.to_async(),
3441 )
3442 .await
3443 .unwrap();
3444 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
3445 .await;
3446 tree.flush_fs_events(cx).await;
3447
3448 tree.update(cx, |tree, cx| {
3449 tree.as_local().unwrap().write_file(
3450 Path::new("tracked-dir/file.txt"),
3451 "hello".into(),
3452 Default::default(),
3453 cx,
3454 )
3455 })
3456 .await
3457 .unwrap();
3458 tree.update(cx, |tree, cx| {
3459 tree.as_local().unwrap().write_file(
3460 Path::new("ignored-dir/file.txt"),
3461 "world".into(),
3462 Default::default(),
3463 cx,
3464 )
3465 })
3466 .await
3467 .unwrap();
3468
3469 tree.read_with(cx, |tree, _| {
3470 let tracked = tree.entry_for_path("tracked-dir/file.txt").unwrap();
3471 let ignored = tree.entry_for_path("ignored-dir/file.txt").unwrap();
3472 assert!(!tracked.is_ignored);
3473 assert!(ignored.is_ignored);
3474 });
3475 }
3476
3477 #[gpui::test(iterations = 30)]
3478 async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
3479 let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
3480
3481 let fs = FakeFs::new(cx.background());
3482 fs.insert_tree(
3483 "/root",
3484 json!({
3485 "b": {},
3486 "c": {},
3487 "d": {},
3488 }),
3489 )
3490 .await;
3491
3492 let tree = Worktree::local(
3493 client,
3494 "/root".as_ref(),
3495 true,
3496 fs,
3497 Default::default(),
3498 &mut cx.to_async(),
3499 )
3500 .await
3501 .unwrap();
3502
3503 let mut snapshot1 = tree.update(cx, |tree, _| tree.as_local().unwrap().snapshot());
3504
3505 let entry = tree
3506 .update(cx, |tree, cx| {
3507 tree.as_local_mut()
3508 .unwrap()
3509 .create_entry("a/e".as_ref(), true, cx)
3510 })
3511 .await
3512 .unwrap();
3513 assert!(entry.is_dir());
3514
3515 cx.foreground().run_until_parked();
3516 tree.read_with(cx, |tree, _| {
3517 assert_eq!(tree.entry_for_path("a/e").unwrap().kind, EntryKind::Dir);
3518 });
3519
3520 let snapshot2 = tree.update(cx, |tree, _| tree.as_local().unwrap().snapshot());
3521 let update = snapshot2.build_update(&snapshot1, 0, 0, true);
3522 snapshot1.apply_remote_update(update).unwrap();
3523 assert_eq!(snapshot1.to_vec(true), snapshot2.to_vec(true),);
3524 }
3525
3526 #[gpui::test(iterations = 100)]
3527 async fn test_random_worktree_operations_during_initial_scan(
3528 cx: &mut TestAppContext,
3529 mut rng: StdRng,
3530 ) {
3531 let operations = env::var("OPERATIONS")
3532 .map(|o| o.parse().unwrap())
3533 .unwrap_or(5);
3534 let initial_entries = env::var("INITIAL_ENTRIES")
3535 .map(|o| o.parse().unwrap())
3536 .unwrap_or(20);
3537
3538 let root_dir = Path::new("/test");
3539 let fs = FakeFs::new(cx.background()) as Arc<dyn Fs>;
3540 fs.as_fake().insert_tree(root_dir, json!({})).await;
3541 for _ in 0..initial_entries {
3542 randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
3543 }
3544 log::info!("generated initial tree");
3545
3546 let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
3547 let worktree = Worktree::local(
3548 client.clone(),
3549 root_dir,
3550 true,
3551 fs.clone(),
3552 Default::default(),
3553 &mut cx.to_async(),
3554 )
3555 .await
3556 .unwrap();
3557
3558 let mut snapshot = worktree.update(cx, |tree, _| tree.as_local().unwrap().snapshot());
3559
3560 for _ in 0..operations {
3561 worktree
3562 .update(cx, |worktree, cx| {
3563 randomly_mutate_worktree(worktree, &mut rng, cx)
3564 })
3565 .await
3566 .log_err();
3567 worktree.read_with(cx, |tree, _| {
3568 tree.as_local().unwrap().snapshot.check_invariants()
3569 });
3570
3571 if rng.gen_bool(0.6) {
3572 let new_snapshot =
3573 worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
3574 let update = new_snapshot.build_update(&snapshot, 0, 0, true);
3575 snapshot.apply_remote_update(update.clone()).unwrap();
3576 assert_eq!(
3577 snapshot.to_vec(true),
3578 new_snapshot.to_vec(true),
3579 "incorrect snapshot after update {:?}",
3580 update
3581 );
3582 }
3583 }
3584
3585 worktree
3586 .update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
3587 .await;
3588 worktree.read_with(cx, |tree, _| {
3589 tree.as_local().unwrap().snapshot.check_invariants()
3590 });
3591
3592 let new_snapshot = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
3593 let update = new_snapshot.build_update(&snapshot, 0, 0, true);
3594 snapshot.apply_remote_update(update.clone()).unwrap();
3595 assert_eq!(
3596 snapshot.to_vec(true),
3597 new_snapshot.to_vec(true),
3598 "incorrect snapshot after update {:?}",
3599 update
3600 );
3601 }
3602
3603 #[gpui::test(iterations = 100)]
3604 async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng) {
3605 let operations = env::var("OPERATIONS")
3606 .map(|o| o.parse().unwrap())
3607 .unwrap_or(40);
3608 let initial_entries = env::var("INITIAL_ENTRIES")
3609 .map(|o| o.parse().unwrap())
3610 .unwrap_or(20);
3611
3612 let root_dir = Path::new("/test");
3613 let fs = FakeFs::new(cx.background()) as Arc<dyn Fs>;
3614 fs.as_fake().insert_tree(root_dir, json!({})).await;
3615 for _ in 0..initial_entries {
3616 randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
3617 }
3618 log::info!("generated initial tree");
3619
3620 let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
3621 let worktree = Worktree::local(
3622 client.clone(),
3623 root_dir,
3624 true,
3625 fs.clone(),
3626 Default::default(),
3627 &mut cx.to_async(),
3628 )
3629 .await
3630 .unwrap();
3631
3632 worktree
3633 .update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
3634 .await;
3635
3636 // After the initial scan is complete, the `UpdatedEntries` event can
3637 // be used to follow along with all changes to the worktree's snapshot.
3638 worktree.update(cx, |tree, cx| {
3639 let mut paths = tree
3640 .as_local()
3641 .unwrap()
3642 .paths()
3643 .cloned()
3644 .collect::<Vec<_>>();
3645
3646 cx.subscribe(&worktree, move |tree, _, event, _| {
3647 if let Event::UpdatedEntries(changes) = event {
3648 for (path, change_type) in changes.iter() {
3649 let path = path.clone();
3650 let ix = match paths.binary_search(&path) {
3651 Ok(ix) | Err(ix) => ix,
3652 };
3653 match change_type {
3654 PathChange::Added => {
3655 assert_ne!(paths.get(ix), Some(&path));
3656 paths.insert(ix, path);
3657 }
3658 PathChange::Removed => {
3659 assert_eq!(paths.get(ix), Some(&path));
3660 paths.remove(ix);
3661 }
3662 PathChange::Updated => {
3663 assert_eq!(paths.get(ix), Some(&path));
3664 }
3665 PathChange::AddedOrUpdated => {
3666 if paths[ix] != path {
3667 paths.insert(ix, path);
3668 }
3669 }
3670 }
3671 }
3672 let new_paths = tree.paths().cloned().collect::<Vec<_>>();
3673 assert_eq!(paths, new_paths, "incorrect changes: {:?}", changes);
3674 }
3675 })
3676 .detach();
3677 });
3678
3679 let mut snapshots = Vec::new();
3680 let mut mutations_len = operations;
3681 while mutations_len > 1 {
3682 randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
3683 let buffered_event_count = fs.as_fake().buffered_event_count().await;
3684 if buffered_event_count > 0 && rng.gen_bool(0.3) {
3685 let len = rng.gen_range(0..=buffered_event_count);
3686 log::info!("flushing {} events", len);
3687 fs.as_fake().flush_events(len).await;
3688 } else {
3689 randomly_mutate_fs(&fs, root_dir, 0.6, &mut rng).await;
3690 mutations_len -= 1;
3691 }
3692
3693 cx.foreground().run_until_parked();
3694 if rng.gen_bool(0.2) {
3695 log::info!("storing snapshot {}", snapshots.len());
3696 let snapshot =
3697 worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
3698 snapshots.push(snapshot);
3699 }
3700 }
3701
3702 log::info!("quiescing");
3703 fs.as_fake().flush_events(usize::MAX).await;
3704 cx.foreground().run_until_parked();
3705 let snapshot = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
3706 snapshot.check_invariants();
3707
3708 {
3709 let new_worktree = Worktree::local(
3710 client.clone(),
3711 root_dir,
3712 true,
3713 fs.clone(),
3714 Default::default(),
3715 &mut cx.to_async(),
3716 )
3717 .await
3718 .unwrap();
3719 new_worktree
3720 .update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
3721 .await;
3722 let new_snapshot =
3723 new_worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
3724 assert_eq!(snapshot.to_vec(true), new_snapshot.to_vec(true));
3725 }
3726
3727 for (i, mut prev_snapshot) in snapshots.into_iter().enumerate() {
3728 let include_ignored = rng.gen::<bool>();
3729 if !include_ignored {
3730 let mut entries_by_path_edits = Vec::new();
3731 let mut entries_by_id_edits = Vec::new();
3732 for entry in prev_snapshot
3733 .entries_by_id
3734 .cursor::<()>()
3735 .filter(|e| e.is_ignored)
3736 {
3737 entries_by_path_edits.push(Edit::Remove(PathKey(entry.path.clone())));
3738 entries_by_id_edits.push(Edit::Remove(entry.id));
3739 }
3740
3741 prev_snapshot
3742 .entries_by_path
3743 .edit(entries_by_path_edits, &());
3744 prev_snapshot.entries_by_id.edit(entries_by_id_edits, &());
3745 }
3746
3747 let update = snapshot.build_update(&prev_snapshot, 0, 0, include_ignored);
3748 prev_snapshot.apply_remote_update(update.clone()).unwrap();
3749 assert_eq!(
3750 prev_snapshot.to_vec(include_ignored),
3751 snapshot.to_vec(include_ignored),
3752 "wrong update for snapshot {i}. update: {:?}",
3753 update
3754 );
3755 }
3756 }
3757
3758 fn randomly_mutate_worktree(
3759 worktree: &mut Worktree,
3760 rng: &mut impl Rng,
3761 cx: &mut ModelContext<Worktree>,
3762 ) -> Task<Result<()>> {
3763 let worktree = worktree.as_local_mut().unwrap();
3764 let snapshot = worktree.snapshot();
3765 let entry = snapshot.entries(false).choose(rng).unwrap();
3766
3767 match rng.gen_range(0_u32..100) {
3768 0..=33 if entry.path.as_ref() != Path::new("") => {
3769 log::info!("deleting entry {:?} ({})", entry.path, entry.id.0);
3770 worktree.delete_entry(entry.id, cx).unwrap()
3771 }
3772 ..=66 if entry.path.as_ref() != Path::new("") => {
3773 let other_entry = snapshot.entries(false).choose(rng).unwrap();
3774 let new_parent_path = if other_entry.is_dir() {
3775 other_entry.path.clone()
3776 } else {
3777 other_entry.path.parent().unwrap().into()
3778 };
3779 let mut new_path = new_parent_path.join(gen_name(rng));
3780 if new_path.starts_with(&entry.path) {
3781 new_path = gen_name(rng).into();
3782 }
3783
3784 log::info!(
3785 "renaming entry {:?} ({}) to {:?}",
3786 entry.path,
3787 entry.id.0,
3788 new_path
3789 );
3790 let task = worktree.rename_entry(entry.id, new_path, cx).unwrap();
3791 cx.foreground().spawn(async move {
3792 task.await?;
3793 Ok(())
3794 })
3795 }
3796 _ => {
3797 let task = if entry.is_dir() {
3798 let child_path = entry.path.join(gen_name(rng));
3799 let is_dir = rng.gen_bool(0.3);
3800 log::info!(
3801 "creating {} at {:?}",
3802 if is_dir { "dir" } else { "file" },
3803 child_path,
3804 );
3805 worktree.create_entry(child_path, is_dir, cx)
3806 } else {
3807 log::info!("overwriting file {:?} ({})", entry.path, entry.id.0);
3808 worktree.write_file(entry.path.clone(), "".into(), Default::default(), cx)
3809 };
3810 cx.foreground().spawn(async move {
3811 task.await?;
3812 Ok(())
3813 })
3814 }
3815 }
3816 }
3817
3818 async fn randomly_mutate_fs(
3819 fs: &Arc<dyn Fs>,
3820 root_path: &Path,
3821 insertion_probability: f64,
3822 rng: &mut impl Rng,
3823 ) {
3824 let mut files = Vec::new();
3825 let mut dirs = Vec::new();
3826 for path in fs.as_fake().paths() {
3827 if path.starts_with(root_path) {
3828 if fs.is_file(&path).await {
3829 files.push(path);
3830 } else {
3831 dirs.push(path);
3832 }
3833 }
3834 }
3835
3836 if (files.is_empty() && dirs.len() == 1) || rng.gen_bool(insertion_probability) {
3837 let path = dirs.choose(rng).unwrap();
3838 let new_path = path.join(gen_name(rng));
3839
3840 if rng.gen() {
3841 log::info!(
3842 "creating dir {:?}",
3843 new_path.strip_prefix(root_path).unwrap()
3844 );
3845 fs.create_dir(&new_path).await.unwrap();
3846 } else {
3847 log::info!(
3848 "creating file {:?}",
3849 new_path.strip_prefix(root_path).unwrap()
3850 );
3851 fs.create_file(&new_path, Default::default()).await.unwrap();
3852 }
3853 } else if rng.gen_bool(0.05) {
3854 let ignore_dir_path = dirs.choose(rng).unwrap();
3855 let ignore_path = ignore_dir_path.join(&*GITIGNORE);
3856
3857 let subdirs = dirs
3858 .iter()
3859 .filter(|d| d.starts_with(&ignore_dir_path))
3860 .cloned()
3861 .collect::<Vec<_>>();
3862 let subfiles = files
3863 .iter()
3864 .filter(|d| d.starts_with(&ignore_dir_path))
3865 .cloned()
3866 .collect::<Vec<_>>();
3867 let files_to_ignore = {
3868 let len = rng.gen_range(0..=subfiles.len());
3869 subfiles.choose_multiple(rng, len)
3870 };
3871 let dirs_to_ignore = {
3872 let len = rng.gen_range(0..subdirs.len());
3873 subdirs.choose_multiple(rng, len)
3874 };
3875
3876 let mut ignore_contents = String::new();
3877 for path_to_ignore in files_to_ignore.chain(dirs_to_ignore) {
3878 writeln!(
3879 ignore_contents,
3880 "{}",
3881 path_to_ignore
3882 .strip_prefix(&ignore_dir_path)
3883 .unwrap()
3884 .to_str()
3885 .unwrap()
3886 )
3887 .unwrap();
3888 }
3889 log::info!(
3890 "creating gitignore {:?} with contents:\n{}",
3891 ignore_path.strip_prefix(&root_path).unwrap(),
3892 ignore_contents
3893 );
3894 fs.save(
3895 &ignore_path,
3896 &ignore_contents.as_str().into(),
3897 Default::default(),
3898 )
3899 .await
3900 .unwrap();
3901 } else {
3902 let old_path = {
3903 let file_path = files.choose(rng);
3904 let dir_path = dirs[1..].choose(rng);
3905 file_path.into_iter().chain(dir_path).choose(rng).unwrap()
3906 };
3907
3908 let is_rename = rng.gen();
3909 if is_rename {
3910 let new_path_parent = dirs
3911 .iter()
3912 .filter(|d| !d.starts_with(old_path))
3913 .choose(rng)
3914 .unwrap();
3915
3916 let overwrite_existing_dir =
3917 !old_path.starts_with(&new_path_parent) && rng.gen_bool(0.3);
3918 let new_path = if overwrite_existing_dir {
3919 fs.remove_dir(
3920 &new_path_parent,
3921 RemoveOptions {
3922 recursive: true,
3923 ignore_if_not_exists: true,
3924 },
3925 )
3926 .await
3927 .unwrap();
3928 new_path_parent.to_path_buf()
3929 } else {
3930 new_path_parent.join(gen_name(rng))
3931 };
3932
3933 log::info!(
3934 "renaming {:?} to {}{:?}",
3935 old_path.strip_prefix(&root_path).unwrap(),
3936 if overwrite_existing_dir {
3937 "overwrite "
3938 } else {
3939 ""
3940 },
3941 new_path.strip_prefix(&root_path).unwrap()
3942 );
3943 fs.rename(
3944 &old_path,
3945 &new_path,
3946 fs::RenameOptions {
3947 overwrite: true,
3948 ignore_if_exists: true,
3949 },
3950 )
3951 .await
3952 .unwrap();
3953 } else if fs.is_file(&old_path).await {
3954 log::info!(
3955 "deleting file {:?}",
3956 old_path.strip_prefix(&root_path).unwrap()
3957 );
3958 fs.remove_file(old_path, Default::default()).await.unwrap();
3959 } else {
3960 log::info!(
3961 "deleting dir {:?}",
3962 old_path.strip_prefix(&root_path).unwrap()
3963 );
3964 fs.remove_dir(
3965 &old_path,
3966 RemoveOptions {
3967 recursive: true,
3968 ignore_if_not_exists: true,
3969 },
3970 )
3971 .await
3972 .unwrap();
3973 }
3974 }
3975 }
3976
3977 fn gen_name(rng: &mut impl Rng) -> String {
3978 (0..6)
3979 .map(|_| rng.sample(rand::distributions::Alphanumeric))
3980 .map(char::from)
3981 .collect()
3982 }
3983
3984 impl LocalSnapshot {
3985 fn check_invariants(&self) {
3986 assert_eq!(
3987 self.entries_by_path
3988 .cursor::<()>()
3989 .map(|e| (&e.path, e.id))
3990 .collect::<Vec<_>>(),
3991 self.entries_by_id
3992 .cursor::<()>()
3993 .map(|e| (&e.path, e.id))
3994 .collect::<collections::BTreeSet<_>>()
3995 .into_iter()
3996 .collect::<Vec<_>>(),
3997 "entries_by_path and entries_by_id are inconsistent"
3998 );
3999
4000 let mut files = self.files(true, 0);
4001 let mut visible_files = self.files(false, 0);
4002 for entry in self.entries_by_path.cursor::<()>() {
4003 if entry.is_file() {
4004 assert_eq!(files.next().unwrap().inode, entry.inode);
4005 if !entry.is_ignored {
4006 assert_eq!(visible_files.next().unwrap().inode, entry.inode);
4007 }
4008 }
4009 }
4010
4011 assert!(files.next().is_none());
4012 assert!(visible_files.next().is_none());
4013
4014 let mut bfs_paths = Vec::new();
4015 let mut stack = vec![Path::new("")];
4016 while let Some(path) = stack.pop() {
4017 bfs_paths.push(path);
4018 let ix = stack.len();
4019 for child_entry in self.child_entries(path) {
4020 stack.insert(ix, &child_entry.path);
4021 }
4022 }
4023
4024 let dfs_paths_via_iter = self
4025 .entries_by_path
4026 .cursor::<()>()
4027 .map(|e| e.path.as_ref())
4028 .collect::<Vec<_>>();
4029 assert_eq!(bfs_paths, dfs_paths_via_iter);
4030
4031 let dfs_paths_via_traversal = self
4032 .entries(true)
4033 .map(|e| e.path.as_ref())
4034 .collect::<Vec<_>>();
4035 assert_eq!(dfs_paths_via_traversal, dfs_paths_via_iter);
4036
4037 for ignore_parent_abs_path in self.ignores_by_parent_abs_path.keys() {
4038 let ignore_parent_path =
4039 ignore_parent_abs_path.strip_prefix(&self.abs_path).unwrap();
4040 assert!(self.entry_for_path(&ignore_parent_path).is_some());
4041 assert!(self
4042 .entry_for_path(ignore_parent_path.join(&*GITIGNORE))
4043 .is_some());
4044 }
4045 }
4046
4047 fn to_vec(&self, include_ignored: bool) -> Vec<(&Path, u64, bool)> {
4048 let mut paths = Vec::new();
4049 for entry in self.entries_by_path.cursor::<()>() {
4050 if include_ignored || !entry.is_ignored {
4051 paths.push((entry.path.as_ref(), entry.inode, entry.is_ignored));
4052 }
4053 }
4054 paths.sort_by(|a, b| a.0.cmp(b.0));
4055 paths
4056 }
4057 }
4058}