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