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 pub fn git_branch(&self) -> Option<String> {
1489 Some("test".to_owned())
1490 }
1491}
1492
1493impl LocalSnapshot {
1494 pub(crate) fn repo_for(&self, path: &Path) -> Option<RepositoryEntry> {
1495 let mut max_len = 0;
1496 let mut current_candidate = None;
1497 for (work_directory, repo) in (&self.repository_entries).iter() {
1498 if work_directory.contains(path) {
1499 if work_directory.0.as_os_str().len() >= max_len {
1500 current_candidate = Some(repo);
1501 max_len = work_directory.0.as_os_str().len();
1502 } else {
1503 break;
1504 }
1505 }
1506 }
1507
1508 current_candidate.map(|entry| entry.to_owned())
1509 }
1510
1511 pub(crate) fn repo_for_metadata(
1512 &self,
1513 path: &Path,
1514 ) -> Option<(RepositoryWorkDirectory, Arc<Mutex<dyn GitRepository>>)> {
1515 self.repository_entries
1516 .iter()
1517 .find(|(_, repo)| repo.in_dot_git(path))
1518 .map(|(work_directory, entry)| {
1519 (
1520 work_directory.to_owned(),
1521 self.git_repositories
1522 .get(&entry.git_dir_entry_id)
1523 .expect("These two data structures should be in sync")
1524 .to_owned(),
1525 )
1526 })
1527 }
1528
1529 #[cfg(test)]
1530 pub(crate) fn build_initial_update(&self, project_id: u64) -> proto::UpdateWorktree {
1531 let root_name = self.root_name.clone();
1532 proto::UpdateWorktree {
1533 project_id,
1534 worktree_id: self.id().to_proto(),
1535 abs_path: self.abs_path().to_string_lossy().into(),
1536 root_name,
1537 updated_entries: self.entries_by_path.iter().map(Into::into).collect(),
1538 removed_entries: Default::default(),
1539 scan_id: self.scan_id as u64,
1540 is_last_update: true,
1541 }
1542 }
1543
1544 pub(crate) fn build_update(
1545 &self,
1546 other: &Self,
1547 project_id: u64,
1548 worktree_id: u64,
1549 include_ignored: bool,
1550 ) -> proto::UpdateWorktree {
1551 let mut updated_entries = Vec::new();
1552 let mut removed_entries = Vec::new();
1553 let mut self_entries = self
1554 .entries_by_id
1555 .cursor::<()>()
1556 .filter(|e| include_ignored || !e.is_ignored)
1557 .peekable();
1558 let mut other_entries = other
1559 .entries_by_id
1560 .cursor::<()>()
1561 .filter(|e| include_ignored || !e.is_ignored)
1562 .peekable();
1563 loop {
1564 match (self_entries.peek(), other_entries.peek()) {
1565 (Some(self_entry), Some(other_entry)) => {
1566 match Ord::cmp(&self_entry.id, &other_entry.id) {
1567 Ordering::Less => {
1568 let entry = self.entry_for_id(self_entry.id).unwrap().into();
1569 updated_entries.push(entry);
1570 self_entries.next();
1571 }
1572 Ordering::Equal => {
1573 if self_entry.scan_id != other_entry.scan_id {
1574 let entry = self.entry_for_id(self_entry.id).unwrap().into();
1575 updated_entries.push(entry);
1576 }
1577
1578 self_entries.next();
1579 other_entries.next();
1580 }
1581 Ordering::Greater => {
1582 removed_entries.push(other_entry.id.to_proto());
1583 other_entries.next();
1584 }
1585 }
1586 }
1587 (Some(self_entry), None) => {
1588 let entry = self.entry_for_id(self_entry.id).unwrap().into();
1589 updated_entries.push(entry);
1590 self_entries.next();
1591 }
1592 (None, Some(other_entry)) => {
1593 removed_entries.push(other_entry.id.to_proto());
1594 other_entries.next();
1595 }
1596 (None, None) => break,
1597 }
1598 }
1599
1600 proto::UpdateWorktree {
1601 project_id,
1602 worktree_id,
1603 abs_path: self.abs_path().to_string_lossy().into(),
1604 root_name: self.root_name().to_string(),
1605 updated_entries,
1606 removed_entries,
1607 scan_id: self.scan_id as u64,
1608 is_last_update: self.completed_scan_id == self.scan_id,
1609 }
1610 }
1611
1612 fn insert_entry(&mut self, mut entry: Entry, fs: &dyn Fs) -> Entry {
1613 if entry.is_file() && entry.path.file_name() == Some(&GITIGNORE) {
1614 let abs_path = self.abs_path.join(&entry.path);
1615 match smol::block_on(build_gitignore(&abs_path, fs)) {
1616 Ok(ignore) => {
1617 self.ignores_by_parent_abs_path.insert(
1618 abs_path.parent().unwrap().into(),
1619 (Arc::new(ignore), self.scan_id),
1620 );
1621 }
1622 Err(error) => {
1623 log::error!(
1624 "error loading .gitignore file {:?} - {:?}",
1625 &entry.path,
1626 error
1627 );
1628 }
1629 }
1630 }
1631
1632 self.reuse_entry_id(&mut entry);
1633
1634 if entry.kind == EntryKind::PendingDir {
1635 if let Some(existing_entry) =
1636 self.entries_by_path.get(&PathKey(entry.path.clone()), &())
1637 {
1638 entry.kind = existing_entry.kind;
1639 }
1640 }
1641
1642 let scan_id = self.scan_id;
1643 let removed = self.entries_by_path.insert_or_replace(entry.clone(), &());
1644 if let Some(removed) = removed {
1645 if removed.id != entry.id {
1646 self.entries_by_id.remove(&removed.id, &());
1647 }
1648 }
1649 self.entries_by_id.insert_or_replace(
1650 PathEntry {
1651 id: entry.id,
1652 path: entry.path.clone(),
1653 is_ignored: entry.is_ignored,
1654 scan_id,
1655 },
1656 &(),
1657 );
1658
1659 entry
1660 }
1661
1662 fn populate_dir(
1663 &mut self,
1664 parent_path: Arc<Path>,
1665 entries: impl IntoIterator<Item = Entry>,
1666 ignore: Option<Arc<Gitignore>>,
1667 fs: &dyn Fs,
1668 ) {
1669 let mut parent_entry = if let Some(parent_entry) =
1670 self.entries_by_path.get(&PathKey(parent_path.clone()), &())
1671 {
1672 parent_entry.clone()
1673 } else {
1674 log::warn!(
1675 "populating a directory {:?} that has been removed",
1676 parent_path
1677 );
1678 return;
1679 };
1680
1681 match parent_entry.kind {
1682 EntryKind::PendingDir => {
1683 parent_entry.kind = EntryKind::Dir;
1684 }
1685 EntryKind::Dir => {}
1686 _ => return,
1687 }
1688
1689 if let Some(ignore) = ignore {
1690 self.ignores_by_parent_abs_path.insert(
1691 self.abs_path.join(&parent_path).into(),
1692 (ignore, self.scan_id),
1693 );
1694 }
1695
1696 if parent_path.file_name() == Some(&DOT_GIT) {
1697 let abs_path = self.abs_path.join(&parent_path);
1698 let content_path: Arc<Path> = parent_path.parent().unwrap().into();
1699
1700 let key = RepositoryWorkDirectory(content_path.clone());
1701 if self.repository_entries.get(&key).is_none() {
1702 if let Some(repo) = fs.open_repo(abs_path.as_path()) {
1703 self.repository_entries.insert(
1704 key.clone(),
1705 RepositoryEntry {
1706 git_dir_path: parent_path.clone(),
1707 git_dir_entry_id: parent_entry.id,
1708 work_directory: key,
1709 scan_id: 0,
1710 branch: None,
1711 },
1712 );
1713
1714 self.git_repositories.insert(parent_entry.id, repo)
1715 }
1716 }
1717 }
1718
1719 let mut entries_by_path_edits = vec![Edit::Insert(parent_entry)];
1720 let mut entries_by_id_edits = Vec::new();
1721
1722 for mut entry in entries {
1723 self.reuse_entry_id(&mut entry);
1724 entries_by_id_edits.push(Edit::Insert(PathEntry {
1725 id: entry.id,
1726 path: entry.path.clone(),
1727 is_ignored: entry.is_ignored,
1728 scan_id: self.scan_id,
1729 }));
1730 entries_by_path_edits.push(Edit::Insert(entry));
1731 }
1732
1733 self.entries_by_path.edit(entries_by_path_edits, &());
1734 self.entries_by_id.edit(entries_by_id_edits, &());
1735 }
1736
1737 fn reuse_entry_id(&mut self, entry: &mut Entry) {
1738 if let Some(removed_entry_id) = self.removed_entry_ids.remove(&entry.inode) {
1739 entry.id = removed_entry_id;
1740 } else if let Some(existing_entry) = self.entry_for_path(&entry.path) {
1741 entry.id = existing_entry.id;
1742 }
1743 }
1744
1745 fn remove_path(&mut self, path: &Path) {
1746 let mut new_entries;
1747 let removed_entries;
1748 {
1749 let mut cursor = self.entries_by_path.cursor::<TraversalProgress>();
1750 new_entries = cursor.slice(&TraversalTarget::Path(path), Bias::Left, &());
1751 removed_entries = cursor.slice(&TraversalTarget::PathSuccessor(path), Bias::Left, &());
1752 new_entries.push_tree(cursor.suffix(&()), &());
1753 }
1754 self.entries_by_path = new_entries;
1755
1756 let mut entries_by_id_edits = Vec::new();
1757 for entry in removed_entries.cursor::<()>() {
1758 let removed_entry_id = self
1759 .removed_entry_ids
1760 .entry(entry.inode)
1761 .or_insert(entry.id);
1762 *removed_entry_id = cmp::max(*removed_entry_id, entry.id);
1763 entries_by_id_edits.push(Edit::Remove(entry.id));
1764 }
1765 self.entries_by_id.edit(entries_by_id_edits, &());
1766
1767 if path.file_name() == Some(&GITIGNORE) {
1768 let abs_parent_path = self.abs_path.join(path.parent().unwrap());
1769 if let Some((_, scan_id)) = self
1770 .ignores_by_parent_abs_path
1771 .get_mut(abs_parent_path.as_path())
1772 {
1773 *scan_id = self.snapshot.scan_id;
1774 }
1775 } else if path.file_name() == Some(&DOT_GIT) {
1776 let repo_entry_key = RepositoryWorkDirectory(path.parent().unwrap().into());
1777 self.snapshot
1778 .repository_entries
1779 .update(&repo_entry_key, |repo| repo.scan_id = self.snapshot.scan_id);
1780 }
1781 }
1782
1783 fn ancestor_inodes_for_path(&self, path: &Path) -> TreeSet<u64> {
1784 let mut inodes = TreeSet::default();
1785 for ancestor in path.ancestors().skip(1) {
1786 if let Some(entry) = self.entry_for_path(ancestor) {
1787 inodes.insert(entry.inode);
1788 }
1789 }
1790 inodes
1791 }
1792
1793 fn ignore_stack_for_abs_path(&self, abs_path: &Path, is_dir: bool) -> Arc<IgnoreStack> {
1794 let mut new_ignores = Vec::new();
1795 for ancestor in abs_path.ancestors().skip(1) {
1796 if let Some((ignore, _)) = self.ignores_by_parent_abs_path.get(ancestor) {
1797 new_ignores.push((ancestor, Some(ignore.clone())));
1798 } else {
1799 new_ignores.push((ancestor, None));
1800 }
1801 }
1802
1803 let mut ignore_stack = IgnoreStack::none();
1804 for (parent_abs_path, ignore) in new_ignores.into_iter().rev() {
1805 if ignore_stack.is_abs_path_ignored(parent_abs_path, true) {
1806 ignore_stack = IgnoreStack::all();
1807 break;
1808 } else if let Some(ignore) = ignore {
1809 ignore_stack = ignore_stack.append(parent_abs_path.into(), ignore);
1810 }
1811 }
1812
1813 if ignore_stack.is_abs_path_ignored(abs_path, is_dir) {
1814 ignore_stack = IgnoreStack::all();
1815 }
1816
1817 ignore_stack
1818 }
1819}
1820
1821async fn build_gitignore(abs_path: &Path, fs: &dyn Fs) -> Result<Gitignore> {
1822 let contents = fs.load(abs_path).await?;
1823 let parent = abs_path.parent().unwrap_or_else(|| Path::new("/"));
1824 let mut builder = GitignoreBuilder::new(parent);
1825 for line in contents.lines() {
1826 builder.add_line(Some(abs_path.into()), line)?;
1827 }
1828 Ok(builder.build()?)
1829}
1830
1831impl WorktreeId {
1832 pub fn from_usize(handle_id: usize) -> Self {
1833 Self(handle_id)
1834 }
1835
1836 pub(crate) fn from_proto(id: u64) -> Self {
1837 Self(id as usize)
1838 }
1839
1840 pub fn to_proto(&self) -> u64 {
1841 self.0 as u64
1842 }
1843
1844 pub fn to_usize(&self) -> usize {
1845 self.0
1846 }
1847}
1848
1849impl fmt::Display for WorktreeId {
1850 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1851 self.0.fmt(f)
1852 }
1853}
1854
1855impl Deref for Worktree {
1856 type Target = Snapshot;
1857
1858 fn deref(&self) -> &Self::Target {
1859 match self {
1860 Worktree::Local(worktree) => &worktree.snapshot,
1861 Worktree::Remote(worktree) => &worktree.snapshot,
1862 }
1863 }
1864}
1865
1866impl Deref for LocalWorktree {
1867 type Target = LocalSnapshot;
1868
1869 fn deref(&self) -> &Self::Target {
1870 &self.snapshot
1871 }
1872}
1873
1874impl Deref for RemoteWorktree {
1875 type Target = Snapshot;
1876
1877 fn deref(&self) -> &Self::Target {
1878 &self.snapshot
1879 }
1880}
1881
1882impl fmt::Debug for LocalWorktree {
1883 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1884 self.snapshot.fmt(f)
1885 }
1886}
1887
1888impl fmt::Debug for Snapshot {
1889 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1890 struct EntriesById<'a>(&'a SumTree<PathEntry>);
1891 struct EntriesByPath<'a>(&'a SumTree<Entry>);
1892
1893 impl<'a> fmt::Debug for EntriesByPath<'a> {
1894 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1895 f.debug_map()
1896 .entries(self.0.iter().map(|entry| (&entry.path, entry.id)))
1897 .finish()
1898 }
1899 }
1900
1901 impl<'a> fmt::Debug for EntriesById<'a> {
1902 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1903 f.debug_list().entries(self.0.iter()).finish()
1904 }
1905 }
1906
1907 f.debug_struct("Snapshot")
1908 .field("id", &self.id)
1909 .field("root_name", &self.root_name)
1910 .field("entries_by_path", &EntriesByPath(&self.entries_by_path))
1911 .field("entries_by_id", &EntriesById(&self.entries_by_id))
1912 .finish()
1913 }
1914}
1915
1916#[derive(Clone, PartialEq)]
1917pub struct File {
1918 pub worktree: ModelHandle<Worktree>,
1919 pub path: Arc<Path>,
1920 pub mtime: SystemTime,
1921 pub(crate) entry_id: ProjectEntryId,
1922 pub(crate) is_local: bool,
1923 pub(crate) is_deleted: bool,
1924}
1925
1926impl language::File for File {
1927 fn as_local(&self) -> Option<&dyn language::LocalFile> {
1928 if self.is_local {
1929 Some(self)
1930 } else {
1931 None
1932 }
1933 }
1934
1935 fn mtime(&self) -> SystemTime {
1936 self.mtime
1937 }
1938
1939 fn path(&self) -> &Arc<Path> {
1940 &self.path
1941 }
1942
1943 fn full_path(&self, cx: &AppContext) -> PathBuf {
1944 let mut full_path = PathBuf::new();
1945 let worktree = self.worktree.read(cx);
1946
1947 if worktree.is_visible() {
1948 full_path.push(worktree.root_name());
1949 } else {
1950 let path = worktree.abs_path();
1951
1952 if worktree.is_local() && path.starts_with(HOME.as_path()) {
1953 full_path.push("~");
1954 full_path.push(path.strip_prefix(HOME.as_path()).unwrap());
1955 } else {
1956 full_path.push(path)
1957 }
1958 }
1959
1960 if self.path.components().next().is_some() {
1961 full_path.push(&self.path);
1962 }
1963
1964 full_path
1965 }
1966
1967 /// Returns the last component of this handle's absolute path. If this handle refers to the root
1968 /// of its worktree, then this method will return the name of the worktree itself.
1969 fn file_name<'a>(&'a self, cx: &'a AppContext) -> &'a OsStr {
1970 self.path
1971 .file_name()
1972 .unwrap_or_else(|| OsStr::new(&self.worktree.read(cx).root_name))
1973 }
1974
1975 fn is_deleted(&self) -> bool {
1976 self.is_deleted
1977 }
1978
1979 fn as_any(&self) -> &dyn Any {
1980 self
1981 }
1982
1983 fn to_proto(&self) -> rpc::proto::File {
1984 rpc::proto::File {
1985 worktree_id: self.worktree.id() as u64,
1986 entry_id: self.entry_id.to_proto(),
1987 path: self.path.to_string_lossy().into(),
1988 mtime: Some(self.mtime.into()),
1989 is_deleted: self.is_deleted,
1990 }
1991 }
1992}
1993
1994impl language::LocalFile for File {
1995 fn abs_path(&self, cx: &AppContext) -> PathBuf {
1996 self.worktree
1997 .read(cx)
1998 .as_local()
1999 .unwrap()
2000 .abs_path
2001 .join(&self.path)
2002 }
2003
2004 fn load(&self, cx: &AppContext) -> Task<Result<String>> {
2005 let worktree = self.worktree.read(cx).as_local().unwrap();
2006 let abs_path = worktree.absolutize(&self.path);
2007 let fs = worktree.fs.clone();
2008 cx.background()
2009 .spawn(async move { fs.load(&abs_path).await })
2010 }
2011
2012 fn buffer_reloaded(
2013 &self,
2014 buffer_id: u64,
2015 version: &clock::Global,
2016 fingerprint: RopeFingerprint,
2017 line_ending: LineEnding,
2018 mtime: SystemTime,
2019 cx: &mut AppContext,
2020 ) {
2021 let worktree = self.worktree.read(cx).as_local().unwrap();
2022 if let Some(project_id) = worktree.share.as_ref().map(|share| share.project_id) {
2023 worktree
2024 .client
2025 .send(proto::BufferReloaded {
2026 project_id,
2027 buffer_id,
2028 version: serialize_version(version),
2029 mtime: Some(mtime.into()),
2030 fingerprint: serialize_fingerprint(fingerprint),
2031 line_ending: serialize_line_ending(line_ending) as i32,
2032 })
2033 .log_err();
2034 }
2035 }
2036}
2037
2038impl File {
2039 pub fn from_proto(
2040 proto: rpc::proto::File,
2041 worktree: ModelHandle<Worktree>,
2042 cx: &AppContext,
2043 ) -> Result<Self> {
2044 let worktree_id = worktree
2045 .read(cx)
2046 .as_remote()
2047 .ok_or_else(|| anyhow!("not remote"))?
2048 .id();
2049
2050 if worktree_id.to_proto() != proto.worktree_id {
2051 return Err(anyhow!("worktree id does not match file"));
2052 }
2053
2054 Ok(Self {
2055 worktree,
2056 path: Path::new(&proto.path).into(),
2057 mtime: proto.mtime.ok_or_else(|| anyhow!("no timestamp"))?.into(),
2058 entry_id: ProjectEntryId::from_proto(proto.entry_id),
2059 is_local: false,
2060 is_deleted: proto.is_deleted,
2061 })
2062 }
2063
2064 pub fn from_dyn(file: Option<&Arc<dyn language::File>>) -> Option<&Self> {
2065 file.and_then(|f| f.as_any().downcast_ref())
2066 }
2067
2068 pub fn worktree_id(&self, cx: &AppContext) -> WorktreeId {
2069 self.worktree.read(cx).id()
2070 }
2071
2072 pub fn project_entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
2073 if self.is_deleted {
2074 None
2075 } else {
2076 Some(self.entry_id)
2077 }
2078 }
2079}
2080
2081#[derive(Clone, Debug, PartialEq, Eq)]
2082pub struct Entry {
2083 pub id: ProjectEntryId,
2084 pub kind: EntryKind,
2085 pub path: Arc<Path>,
2086 pub inode: u64,
2087 pub mtime: SystemTime,
2088 pub is_symlink: bool,
2089 pub is_ignored: bool,
2090}
2091
2092#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2093pub enum EntryKind {
2094 PendingDir,
2095 Dir,
2096 File(CharBag),
2097}
2098
2099#[derive(Clone, Copy, Debug)]
2100pub enum PathChange {
2101 Added,
2102 Removed,
2103 Updated,
2104 AddedOrUpdated,
2105}
2106
2107impl Entry {
2108 fn new(
2109 path: Arc<Path>,
2110 metadata: &fs::Metadata,
2111 next_entry_id: &AtomicUsize,
2112 root_char_bag: CharBag,
2113 ) -> Self {
2114 Self {
2115 id: ProjectEntryId::new(next_entry_id),
2116 kind: if metadata.is_dir {
2117 EntryKind::PendingDir
2118 } else {
2119 EntryKind::File(char_bag_for_path(root_char_bag, &path))
2120 },
2121 path,
2122 inode: metadata.inode,
2123 mtime: metadata.mtime,
2124 is_symlink: metadata.is_symlink,
2125 is_ignored: false,
2126 }
2127 }
2128
2129 pub fn is_dir(&self) -> bool {
2130 matches!(self.kind, EntryKind::Dir | EntryKind::PendingDir)
2131 }
2132
2133 pub fn is_file(&self) -> bool {
2134 matches!(self.kind, EntryKind::File(_))
2135 }
2136}
2137
2138impl sum_tree::Item for Entry {
2139 type Summary = EntrySummary;
2140
2141 fn summary(&self) -> Self::Summary {
2142 let visible_count = if self.is_ignored { 0 } else { 1 };
2143 let file_count;
2144 let visible_file_count;
2145 if self.is_file() {
2146 file_count = 1;
2147 visible_file_count = visible_count;
2148 } else {
2149 file_count = 0;
2150 visible_file_count = 0;
2151 }
2152
2153 EntrySummary {
2154 max_path: self.path.clone(),
2155 count: 1,
2156 visible_count,
2157 file_count,
2158 visible_file_count,
2159 }
2160 }
2161}
2162
2163impl sum_tree::KeyedItem for Entry {
2164 type Key = PathKey;
2165
2166 fn key(&self) -> Self::Key {
2167 PathKey(self.path.clone())
2168 }
2169}
2170
2171#[derive(Clone, Debug)]
2172pub struct EntrySummary {
2173 max_path: Arc<Path>,
2174 count: usize,
2175 visible_count: usize,
2176 file_count: usize,
2177 visible_file_count: usize,
2178}
2179
2180impl Default for EntrySummary {
2181 fn default() -> Self {
2182 Self {
2183 max_path: Arc::from(Path::new("")),
2184 count: 0,
2185 visible_count: 0,
2186 file_count: 0,
2187 visible_file_count: 0,
2188 }
2189 }
2190}
2191
2192impl sum_tree::Summary for EntrySummary {
2193 type Context = ();
2194
2195 fn add_summary(&mut self, rhs: &Self, _: &()) {
2196 self.max_path = rhs.max_path.clone();
2197 self.count += rhs.count;
2198 self.visible_count += rhs.visible_count;
2199 self.file_count += rhs.file_count;
2200 self.visible_file_count += rhs.visible_file_count;
2201 }
2202}
2203
2204#[derive(Clone, Debug)]
2205struct PathEntry {
2206 id: ProjectEntryId,
2207 path: Arc<Path>,
2208 is_ignored: bool,
2209 scan_id: usize,
2210}
2211
2212impl sum_tree::Item for PathEntry {
2213 type Summary = PathEntrySummary;
2214
2215 fn summary(&self) -> Self::Summary {
2216 PathEntrySummary { max_id: self.id }
2217 }
2218}
2219
2220impl sum_tree::KeyedItem for PathEntry {
2221 type Key = ProjectEntryId;
2222
2223 fn key(&self) -> Self::Key {
2224 self.id
2225 }
2226}
2227
2228#[derive(Clone, Debug, Default)]
2229struct PathEntrySummary {
2230 max_id: ProjectEntryId,
2231}
2232
2233impl sum_tree::Summary for PathEntrySummary {
2234 type Context = ();
2235
2236 fn add_summary(&mut self, summary: &Self, _: &Self::Context) {
2237 self.max_id = summary.max_id;
2238 }
2239}
2240
2241impl<'a> sum_tree::Dimension<'a, PathEntrySummary> for ProjectEntryId {
2242 fn add_summary(&mut self, summary: &'a PathEntrySummary, _: &()) {
2243 *self = summary.max_id;
2244 }
2245}
2246
2247#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
2248pub struct PathKey(Arc<Path>);
2249
2250impl Default for PathKey {
2251 fn default() -> Self {
2252 Self(Path::new("").into())
2253 }
2254}
2255
2256impl<'a> sum_tree::Dimension<'a, EntrySummary> for PathKey {
2257 fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
2258 self.0 = summary.max_path.clone();
2259 }
2260}
2261
2262struct BackgroundScanner {
2263 snapshot: Mutex<LocalSnapshot>,
2264 fs: Arc<dyn Fs>,
2265 status_updates_tx: UnboundedSender<ScanState>,
2266 executor: Arc<executor::Background>,
2267 refresh_requests_rx: channel::Receiver<(Vec<PathBuf>, barrier::Sender)>,
2268 prev_state: Mutex<(Snapshot, Vec<Arc<Path>>)>,
2269 finished_initial_scan: bool,
2270}
2271
2272impl BackgroundScanner {
2273 fn new(
2274 snapshot: LocalSnapshot,
2275 fs: Arc<dyn Fs>,
2276 status_updates_tx: UnboundedSender<ScanState>,
2277 executor: Arc<executor::Background>,
2278 refresh_requests_rx: channel::Receiver<(Vec<PathBuf>, barrier::Sender)>,
2279 ) -> Self {
2280 Self {
2281 fs,
2282 status_updates_tx,
2283 executor,
2284 refresh_requests_rx,
2285 prev_state: Mutex::new((snapshot.snapshot.clone(), Vec::new())),
2286 snapshot: Mutex::new(snapshot),
2287 finished_initial_scan: false,
2288 }
2289 }
2290
2291 async fn run(
2292 &mut self,
2293 mut events_rx: Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>>,
2294 ) {
2295 use futures::FutureExt as _;
2296
2297 let (root_abs_path, root_inode) = {
2298 let snapshot = self.snapshot.lock();
2299 (
2300 snapshot.abs_path.clone(),
2301 snapshot.root_entry().map(|e| e.inode),
2302 )
2303 };
2304
2305 // Populate ignores above the root.
2306 let ignore_stack;
2307 for ancestor in root_abs_path.ancestors().skip(1) {
2308 if let Ok(ignore) = build_gitignore(&ancestor.join(&*GITIGNORE), self.fs.as_ref()).await
2309 {
2310 self.snapshot
2311 .lock()
2312 .ignores_by_parent_abs_path
2313 .insert(ancestor.into(), (ignore.into(), 0));
2314 }
2315 }
2316 {
2317 let mut snapshot = self.snapshot.lock();
2318 snapshot.scan_id += 1;
2319 ignore_stack = snapshot.ignore_stack_for_abs_path(&root_abs_path, true);
2320 if ignore_stack.is_all() {
2321 if let Some(mut root_entry) = snapshot.root_entry().cloned() {
2322 root_entry.is_ignored = true;
2323 snapshot.insert_entry(root_entry, self.fs.as_ref());
2324 }
2325 }
2326 };
2327
2328 // Perform an initial scan of the directory.
2329 let (scan_job_tx, scan_job_rx) = channel::unbounded();
2330 smol::block_on(scan_job_tx.send(ScanJob {
2331 abs_path: root_abs_path,
2332 path: Arc::from(Path::new("")),
2333 ignore_stack,
2334 ancestor_inodes: TreeSet::from_ordered_entries(root_inode),
2335 scan_queue: scan_job_tx.clone(),
2336 }))
2337 .unwrap();
2338 drop(scan_job_tx);
2339 self.scan_dirs(true, scan_job_rx).await;
2340 {
2341 let mut snapshot = self.snapshot.lock();
2342 snapshot.completed_scan_id = snapshot.scan_id;
2343 }
2344 self.send_status_update(false, None);
2345
2346 // Process any any FS events that occurred while performing the initial scan.
2347 // For these events, update events cannot be as precise, because we didn't
2348 // have the previous state loaded yet.
2349 if let Poll::Ready(Some(events)) = futures::poll!(events_rx.next()) {
2350 let mut paths = events.into_iter().map(|e| e.path).collect::<Vec<_>>();
2351 while let Poll::Ready(Some(more_events)) = futures::poll!(events_rx.next()) {
2352 paths.extend(more_events.into_iter().map(|e| e.path));
2353 }
2354 self.process_events(paths).await;
2355 }
2356
2357 self.finished_initial_scan = true;
2358
2359 // Continue processing events until the worktree is dropped.
2360 loop {
2361 select_biased! {
2362 // Process any path refresh requests from the worktree. Prioritize
2363 // these before handling changes reported by the filesystem.
2364 request = self.refresh_requests_rx.recv().fuse() => {
2365 let Ok((paths, barrier)) = request else { break };
2366 if !self.process_refresh_request(paths, barrier).await {
2367 return;
2368 }
2369 }
2370
2371 events = events_rx.next().fuse() => {
2372 let Some(events) = events else { break };
2373 let mut paths = events.into_iter().map(|e| e.path).collect::<Vec<_>>();
2374 while let Poll::Ready(Some(more_events)) = futures::poll!(events_rx.next()) {
2375 paths.extend(more_events.into_iter().map(|e| e.path));
2376 }
2377 self.process_events(paths).await;
2378 }
2379 }
2380 }
2381 }
2382
2383 async fn process_refresh_request(&self, paths: Vec<PathBuf>, barrier: barrier::Sender) -> bool {
2384 self.reload_entries_for_paths(paths, None).await;
2385 self.send_status_update(false, Some(barrier))
2386 }
2387
2388 async fn process_events(&mut self, paths: Vec<PathBuf>) {
2389 let (scan_job_tx, scan_job_rx) = channel::unbounded();
2390 if let Some(mut paths) = self
2391 .reload_entries_for_paths(paths, Some(scan_job_tx.clone()))
2392 .await
2393 {
2394 paths.sort_unstable();
2395 util::extend_sorted(&mut self.prev_state.lock().1, paths, usize::MAX, Ord::cmp);
2396 }
2397 drop(scan_job_tx);
2398 self.scan_dirs(false, scan_job_rx).await;
2399
2400 self.update_ignore_statuses().await;
2401
2402 let mut snapshot = self.snapshot.lock();
2403
2404 let mut git_repositories = mem::take(&mut snapshot.git_repositories);
2405 git_repositories.retain(|project_entry_id, _| snapshot.contains_entry(*project_entry_id));
2406 snapshot.git_repositories = git_repositories;
2407
2408 let mut git_repository_entries = mem::take(&mut snapshot.snapshot.repository_entries);
2409 git_repository_entries.retain(|_, entry| snapshot.contains_entry(entry.git_dir_entry_id));
2410 snapshot.snapshot.repository_entries = git_repository_entries;
2411
2412 snapshot.removed_entry_ids.clear();
2413 snapshot.completed_scan_id = snapshot.scan_id;
2414
2415 drop(snapshot);
2416
2417 self.send_status_update(false, None);
2418 }
2419
2420 async fn scan_dirs(
2421 &self,
2422 enable_progress_updates: bool,
2423 scan_jobs_rx: channel::Receiver<ScanJob>,
2424 ) {
2425 use futures::FutureExt as _;
2426
2427 if self
2428 .status_updates_tx
2429 .unbounded_send(ScanState::Started)
2430 .is_err()
2431 {
2432 return;
2433 }
2434
2435 let progress_update_count = AtomicUsize::new(0);
2436 self.executor
2437 .scoped(|scope| {
2438 for _ in 0..self.executor.num_cpus() {
2439 scope.spawn(async {
2440 let mut last_progress_update_count = 0;
2441 let progress_update_timer = self.progress_timer(enable_progress_updates).fuse();
2442 futures::pin_mut!(progress_update_timer);
2443
2444 loop {
2445 select_biased! {
2446 // Process any path refresh requests before moving on to process
2447 // the scan queue, so that user operations are prioritized.
2448 request = self.refresh_requests_rx.recv().fuse() => {
2449 let Ok((paths, barrier)) = request else { break };
2450 if !self.process_refresh_request(paths, barrier).await {
2451 return;
2452 }
2453 }
2454
2455 // Send periodic progress updates to the worktree. Use an atomic counter
2456 // to ensure that only one of the workers sends a progress update after
2457 // the update interval elapses.
2458 _ = progress_update_timer => {
2459 match progress_update_count.compare_exchange(
2460 last_progress_update_count,
2461 last_progress_update_count + 1,
2462 SeqCst,
2463 SeqCst
2464 ) {
2465 Ok(_) => {
2466 last_progress_update_count += 1;
2467 self.send_status_update(true, None);
2468 }
2469 Err(count) => {
2470 last_progress_update_count = count;
2471 }
2472 }
2473 progress_update_timer.set(self.progress_timer(enable_progress_updates).fuse());
2474 }
2475
2476 // Recursively load directories from the file system.
2477 job = scan_jobs_rx.recv().fuse() => {
2478 let Ok(job) = job else { break };
2479 if let Err(err) = self.scan_dir(&job).await {
2480 if job.path.as_ref() != Path::new("") {
2481 log::error!("error scanning directory {:?}: {}", job.abs_path, err);
2482 }
2483 }
2484 }
2485 }
2486 }
2487 })
2488 }
2489 })
2490 .await;
2491 }
2492
2493 fn send_status_update(&self, scanning: bool, barrier: Option<barrier::Sender>) -> bool {
2494 let mut prev_state = self.prev_state.lock();
2495 let snapshot = self.snapshot.lock().clone();
2496 let mut old_snapshot = snapshot.snapshot.clone();
2497 mem::swap(&mut old_snapshot, &mut prev_state.0);
2498 let changed_paths = mem::take(&mut prev_state.1);
2499 let changes = self.build_change_set(&old_snapshot, &snapshot.snapshot, changed_paths);
2500 self.status_updates_tx
2501 .unbounded_send(ScanState::Updated {
2502 snapshot,
2503 changes,
2504 scanning,
2505 barrier,
2506 })
2507 .is_ok()
2508 }
2509
2510 async fn scan_dir(&self, job: &ScanJob) -> Result<()> {
2511 let mut new_entries: Vec<Entry> = Vec::new();
2512 let mut new_jobs: Vec<Option<ScanJob>> = Vec::new();
2513 let mut ignore_stack = job.ignore_stack.clone();
2514 let mut new_ignore = None;
2515 let (root_abs_path, root_char_bag, next_entry_id) = {
2516 let snapshot = self.snapshot.lock();
2517 (
2518 snapshot.abs_path().clone(),
2519 snapshot.root_char_bag,
2520 snapshot.next_entry_id.clone(),
2521 )
2522 };
2523 let mut child_paths = self.fs.read_dir(&job.abs_path).await?;
2524 while let Some(child_abs_path) = child_paths.next().await {
2525 let child_abs_path: Arc<Path> = match child_abs_path {
2526 Ok(child_abs_path) => child_abs_path.into(),
2527 Err(error) => {
2528 log::error!("error processing entry {:?}", error);
2529 continue;
2530 }
2531 };
2532
2533 let child_name = child_abs_path.file_name().unwrap();
2534 let child_path: Arc<Path> = job.path.join(child_name).into();
2535 let child_metadata = match self.fs.metadata(&child_abs_path).await {
2536 Ok(Some(metadata)) => metadata,
2537 Ok(None) => continue,
2538 Err(err) => {
2539 log::error!("error processing {:?}: {:?}", child_abs_path, err);
2540 continue;
2541 }
2542 };
2543
2544 // If we find a .gitignore, add it to the stack of ignores used to determine which paths are ignored
2545 if child_name == *GITIGNORE {
2546 match build_gitignore(&child_abs_path, self.fs.as_ref()).await {
2547 Ok(ignore) => {
2548 let ignore = Arc::new(ignore);
2549 ignore_stack = ignore_stack.append(job.abs_path.clone(), ignore.clone());
2550 new_ignore = Some(ignore);
2551 }
2552 Err(error) => {
2553 log::error!(
2554 "error loading .gitignore file {:?} - {:?}",
2555 child_name,
2556 error
2557 );
2558 }
2559 }
2560
2561 // Update ignore status of any child entries we've already processed to reflect the
2562 // ignore file in the current directory. Because `.gitignore` starts with a `.`,
2563 // there should rarely be too numerous. Update the ignore stack associated with any
2564 // new jobs as well.
2565 let mut new_jobs = new_jobs.iter_mut();
2566 for entry in &mut new_entries {
2567 let entry_abs_path = root_abs_path.join(&entry.path);
2568 entry.is_ignored =
2569 ignore_stack.is_abs_path_ignored(&entry_abs_path, entry.is_dir());
2570
2571 if entry.is_dir() {
2572 if let Some(job) = new_jobs.next().expect("Missing scan job for entry") {
2573 job.ignore_stack = if entry.is_ignored {
2574 IgnoreStack::all()
2575 } else {
2576 ignore_stack.clone()
2577 };
2578 }
2579 }
2580 }
2581 }
2582
2583 let mut child_entry = Entry::new(
2584 child_path.clone(),
2585 &child_metadata,
2586 &next_entry_id,
2587 root_char_bag,
2588 );
2589
2590 if child_entry.is_dir() {
2591 let is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, true);
2592 child_entry.is_ignored = is_ignored;
2593
2594 // Avoid recursing until crash in the case of a recursive symlink
2595 if !job.ancestor_inodes.contains(&child_entry.inode) {
2596 let mut ancestor_inodes = job.ancestor_inodes.clone();
2597 ancestor_inodes.insert(child_entry.inode);
2598
2599 new_jobs.push(Some(ScanJob {
2600 abs_path: child_abs_path,
2601 path: child_path,
2602 ignore_stack: if is_ignored {
2603 IgnoreStack::all()
2604 } else {
2605 ignore_stack.clone()
2606 },
2607 ancestor_inodes,
2608 scan_queue: job.scan_queue.clone(),
2609 }));
2610 } else {
2611 new_jobs.push(None);
2612 }
2613 } else {
2614 child_entry.is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, false);
2615 }
2616
2617 new_entries.push(child_entry);
2618 }
2619
2620 self.snapshot.lock().populate_dir(
2621 job.path.clone(),
2622 new_entries,
2623 new_ignore,
2624 self.fs.as_ref(),
2625 );
2626
2627 for new_job in new_jobs {
2628 if let Some(new_job) = new_job {
2629 job.scan_queue.send(new_job).await.unwrap();
2630 }
2631 }
2632
2633 Ok(())
2634 }
2635
2636 async fn reload_entries_for_paths(
2637 &self,
2638 mut abs_paths: Vec<PathBuf>,
2639 scan_queue_tx: Option<Sender<ScanJob>>,
2640 ) -> Option<Vec<Arc<Path>>> {
2641 let doing_recursive_update = scan_queue_tx.is_some();
2642
2643 abs_paths.sort_unstable();
2644 abs_paths.dedup_by(|a, b| a.starts_with(&b));
2645
2646 let root_abs_path = self.snapshot.lock().abs_path.clone();
2647 let root_canonical_path = self.fs.canonicalize(&root_abs_path).await.log_err()?;
2648 let metadata = futures::future::join_all(
2649 abs_paths
2650 .iter()
2651 .map(|abs_path| self.fs.metadata(&abs_path))
2652 .collect::<Vec<_>>(),
2653 )
2654 .await;
2655
2656 let mut snapshot = self.snapshot.lock();
2657 let is_idle = snapshot.completed_scan_id == snapshot.scan_id;
2658 snapshot.scan_id += 1;
2659 if is_idle && !doing_recursive_update {
2660 snapshot.completed_scan_id = snapshot.scan_id;
2661 }
2662
2663 // Remove any entries for paths that no longer exist or are being recursively
2664 // refreshed. Do this before adding any new entries, so that renames can be
2665 // detected regardless of the order of the paths.
2666 let mut event_paths = Vec::<Arc<Path>>::with_capacity(abs_paths.len());
2667 for (abs_path, metadata) in abs_paths.iter().zip(metadata.iter()) {
2668 if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) {
2669 if matches!(metadata, Ok(None)) || doing_recursive_update {
2670 snapshot.remove_path(path);
2671 }
2672 event_paths.push(path.into());
2673 } else {
2674 log::error!(
2675 "unexpected event {:?} for root path {:?}",
2676 abs_path,
2677 root_canonical_path
2678 );
2679 }
2680 }
2681
2682 for (path, metadata) in event_paths.iter().cloned().zip(metadata.into_iter()) {
2683 let abs_path: Arc<Path> = root_abs_path.join(&path).into();
2684
2685 match metadata {
2686 Ok(Some(metadata)) => {
2687 let ignore_stack =
2688 snapshot.ignore_stack_for_abs_path(&abs_path, metadata.is_dir);
2689 let mut fs_entry = Entry::new(
2690 path.clone(),
2691 &metadata,
2692 snapshot.next_entry_id.as_ref(),
2693 snapshot.root_char_bag,
2694 );
2695 fs_entry.is_ignored = ignore_stack.is_all();
2696 snapshot.insert_entry(fs_entry, self.fs.as_ref());
2697
2698 let scan_id = snapshot.scan_id;
2699
2700 let repo_with_path_in_dotgit = snapshot.repo_for_metadata(&path);
2701 if let Some((key, repo)) = repo_with_path_in_dotgit {
2702 let repo = repo.lock();
2703 repo.reload_index();
2704 let branch = repo.branch_name();
2705
2706 snapshot.repository_entries.update(&key, |entry| {
2707 entry.scan_id = scan_id;
2708 entry.branch = branch.map(Into::into)
2709 });
2710 }
2711
2712 if let Some(scan_queue_tx) = &scan_queue_tx {
2713 let mut ancestor_inodes = snapshot.ancestor_inodes_for_path(&path);
2714 if metadata.is_dir && !ancestor_inodes.contains(&metadata.inode) {
2715 ancestor_inodes.insert(metadata.inode);
2716 smol::block_on(scan_queue_tx.send(ScanJob {
2717 abs_path,
2718 path,
2719 ignore_stack,
2720 ancestor_inodes,
2721 scan_queue: scan_queue_tx.clone(),
2722 }))
2723 .unwrap();
2724 }
2725 }
2726 }
2727 Ok(None) => {}
2728 Err(err) => {
2729 // TODO - create a special 'error' entry in the entries tree to mark this
2730 log::error!("error reading file on event {:?}", err);
2731 }
2732 }
2733 }
2734
2735 Some(event_paths)
2736 }
2737
2738 async fn update_ignore_statuses(&self) {
2739 use futures::FutureExt as _;
2740
2741 let mut snapshot = self.snapshot.lock().clone();
2742 let mut ignores_to_update = Vec::new();
2743 let mut ignores_to_delete = Vec::new();
2744 for (parent_abs_path, (_, scan_id)) in &snapshot.ignores_by_parent_abs_path {
2745 if let Ok(parent_path) = parent_abs_path.strip_prefix(&snapshot.abs_path) {
2746 if *scan_id > snapshot.completed_scan_id
2747 && snapshot.entry_for_path(parent_path).is_some()
2748 {
2749 ignores_to_update.push(parent_abs_path.clone());
2750 }
2751
2752 let ignore_path = parent_path.join(&*GITIGNORE);
2753 if snapshot.entry_for_path(ignore_path).is_none() {
2754 ignores_to_delete.push(parent_abs_path.clone());
2755 }
2756 }
2757 }
2758
2759 for parent_abs_path in ignores_to_delete {
2760 snapshot.ignores_by_parent_abs_path.remove(&parent_abs_path);
2761 self.snapshot
2762 .lock()
2763 .ignores_by_parent_abs_path
2764 .remove(&parent_abs_path);
2765 }
2766
2767 let (ignore_queue_tx, ignore_queue_rx) = channel::unbounded();
2768 ignores_to_update.sort_unstable();
2769 let mut ignores_to_update = ignores_to_update.into_iter().peekable();
2770 while let Some(parent_abs_path) = ignores_to_update.next() {
2771 while ignores_to_update
2772 .peek()
2773 .map_or(false, |p| p.starts_with(&parent_abs_path))
2774 {
2775 ignores_to_update.next().unwrap();
2776 }
2777
2778 let ignore_stack = snapshot.ignore_stack_for_abs_path(&parent_abs_path, true);
2779 smol::block_on(ignore_queue_tx.send(UpdateIgnoreStatusJob {
2780 abs_path: parent_abs_path,
2781 ignore_stack,
2782 ignore_queue: ignore_queue_tx.clone(),
2783 }))
2784 .unwrap();
2785 }
2786 drop(ignore_queue_tx);
2787
2788 self.executor
2789 .scoped(|scope| {
2790 for _ in 0..self.executor.num_cpus() {
2791 scope.spawn(async {
2792 loop {
2793 select_biased! {
2794 // Process any path refresh requests before moving on to process
2795 // the queue of ignore statuses.
2796 request = self.refresh_requests_rx.recv().fuse() => {
2797 let Ok((paths, barrier)) = request else { break };
2798 if !self.process_refresh_request(paths, barrier).await {
2799 return;
2800 }
2801 }
2802
2803 // Recursively process directories whose ignores have changed.
2804 job = ignore_queue_rx.recv().fuse() => {
2805 let Ok(job) = job else { break };
2806 self.update_ignore_status(job, &snapshot).await;
2807 }
2808 }
2809 }
2810 });
2811 }
2812 })
2813 .await;
2814 }
2815
2816 async fn update_ignore_status(&self, job: UpdateIgnoreStatusJob, snapshot: &LocalSnapshot) {
2817 let mut ignore_stack = job.ignore_stack;
2818 if let Some((ignore, _)) = snapshot.ignores_by_parent_abs_path.get(&job.abs_path) {
2819 ignore_stack = ignore_stack.append(job.abs_path.clone(), ignore.clone());
2820 }
2821
2822 let mut entries_by_id_edits = Vec::new();
2823 let mut entries_by_path_edits = Vec::new();
2824 let path = job.abs_path.strip_prefix(&snapshot.abs_path).unwrap();
2825 for mut entry in snapshot.child_entries(path).cloned() {
2826 let was_ignored = entry.is_ignored;
2827 let abs_path = snapshot.abs_path().join(&entry.path);
2828 entry.is_ignored = ignore_stack.is_abs_path_ignored(&abs_path, entry.is_dir());
2829 if entry.is_dir() {
2830 let child_ignore_stack = if entry.is_ignored {
2831 IgnoreStack::all()
2832 } else {
2833 ignore_stack.clone()
2834 };
2835 job.ignore_queue
2836 .send(UpdateIgnoreStatusJob {
2837 abs_path: abs_path.into(),
2838 ignore_stack: child_ignore_stack,
2839 ignore_queue: job.ignore_queue.clone(),
2840 })
2841 .await
2842 .unwrap();
2843 }
2844
2845 if entry.is_ignored != was_ignored {
2846 let mut path_entry = snapshot.entries_by_id.get(&entry.id, &()).unwrap().clone();
2847 path_entry.scan_id = snapshot.scan_id;
2848 path_entry.is_ignored = entry.is_ignored;
2849 entries_by_id_edits.push(Edit::Insert(path_entry));
2850 entries_by_path_edits.push(Edit::Insert(entry));
2851 }
2852 }
2853
2854 let mut snapshot = self.snapshot.lock();
2855 snapshot.entries_by_path.edit(entries_by_path_edits, &());
2856 snapshot.entries_by_id.edit(entries_by_id_edits, &());
2857 }
2858
2859 fn build_change_set(
2860 &self,
2861 old_snapshot: &Snapshot,
2862 new_snapshot: &Snapshot,
2863 event_paths: Vec<Arc<Path>>,
2864 ) -> HashMap<Arc<Path>, PathChange> {
2865 use PathChange::{Added, AddedOrUpdated, Removed, Updated};
2866
2867 let mut changes = HashMap::default();
2868 let mut old_paths = old_snapshot.entries_by_path.cursor::<PathKey>();
2869 let mut new_paths = new_snapshot.entries_by_path.cursor::<PathKey>();
2870 let received_before_initialized = !self.finished_initial_scan;
2871
2872 for path in event_paths {
2873 let path = PathKey(path);
2874 old_paths.seek(&path, Bias::Left, &());
2875 new_paths.seek(&path, Bias::Left, &());
2876
2877 loop {
2878 match (old_paths.item(), new_paths.item()) {
2879 (Some(old_entry), Some(new_entry)) => {
2880 if old_entry.path > path.0
2881 && new_entry.path > path.0
2882 && !old_entry.path.starts_with(&path.0)
2883 && !new_entry.path.starts_with(&path.0)
2884 {
2885 break;
2886 }
2887
2888 match Ord::cmp(&old_entry.path, &new_entry.path) {
2889 Ordering::Less => {
2890 changes.insert(old_entry.path.clone(), Removed);
2891 old_paths.next(&());
2892 }
2893 Ordering::Equal => {
2894 if received_before_initialized {
2895 // If the worktree was not fully initialized when this event was generated,
2896 // we can't know whether this entry was added during the scan or whether
2897 // it was merely updated.
2898 changes.insert(new_entry.path.clone(), AddedOrUpdated);
2899 } else if old_entry.mtime != new_entry.mtime {
2900 changes.insert(new_entry.path.clone(), Updated);
2901 }
2902 old_paths.next(&());
2903 new_paths.next(&());
2904 }
2905 Ordering::Greater => {
2906 changes.insert(new_entry.path.clone(), Added);
2907 new_paths.next(&());
2908 }
2909 }
2910 }
2911 (Some(old_entry), None) => {
2912 changes.insert(old_entry.path.clone(), Removed);
2913 old_paths.next(&());
2914 }
2915 (None, Some(new_entry)) => {
2916 changes.insert(new_entry.path.clone(), Added);
2917 new_paths.next(&());
2918 }
2919 (None, None) => break,
2920 }
2921 }
2922 }
2923 changes
2924 }
2925
2926 async fn progress_timer(&self, running: bool) {
2927 if !running {
2928 return futures::future::pending().await;
2929 }
2930
2931 #[cfg(any(test, feature = "test-support"))]
2932 if self.fs.is_fake() {
2933 return self.executor.simulate_random_delay().await;
2934 }
2935
2936 smol::Timer::after(Duration::from_millis(100)).await;
2937 }
2938}
2939
2940fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag {
2941 let mut result = root_char_bag;
2942 result.extend(
2943 path.to_string_lossy()
2944 .chars()
2945 .map(|c| c.to_ascii_lowercase()),
2946 );
2947 result
2948}
2949
2950struct ScanJob {
2951 abs_path: Arc<Path>,
2952 path: Arc<Path>,
2953 ignore_stack: Arc<IgnoreStack>,
2954 scan_queue: Sender<ScanJob>,
2955 ancestor_inodes: TreeSet<u64>,
2956}
2957
2958struct UpdateIgnoreStatusJob {
2959 abs_path: Arc<Path>,
2960 ignore_stack: Arc<IgnoreStack>,
2961 ignore_queue: Sender<UpdateIgnoreStatusJob>,
2962}
2963
2964pub trait WorktreeHandle {
2965 #[cfg(any(test, feature = "test-support"))]
2966 fn flush_fs_events<'a>(
2967 &self,
2968 cx: &'a gpui::TestAppContext,
2969 ) -> futures::future::LocalBoxFuture<'a, ()>;
2970}
2971
2972impl WorktreeHandle for ModelHandle<Worktree> {
2973 // When the worktree's FS event stream sometimes delivers "redundant" events for FS changes that
2974 // occurred before the worktree was constructed. These events can cause the worktree to perfrom
2975 // extra directory scans, and emit extra scan-state notifications.
2976 //
2977 // This function mutates the worktree's directory and waits for those mutations to be picked up,
2978 // to ensure that all redundant FS events have already been processed.
2979 #[cfg(any(test, feature = "test-support"))]
2980 fn flush_fs_events<'a>(
2981 &self,
2982 cx: &'a gpui::TestAppContext,
2983 ) -> futures::future::LocalBoxFuture<'a, ()> {
2984 use smol::future::FutureExt;
2985
2986 let filename = "fs-event-sentinel";
2987 let tree = self.clone();
2988 let (fs, root_path) = self.read_with(cx, |tree, _| {
2989 let tree = tree.as_local().unwrap();
2990 (tree.fs.clone(), tree.abs_path().clone())
2991 });
2992
2993 async move {
2994 fs.create_file(&root_path.join(filename), Default::default())
2995 .await
2996 .unwrap();
2997 tree.condition(cx, |tree, _| tree.entry_for_path(filename).is_some())
2998 .await;
2999
3000 fs.remove_file(&root_path.join(filename), Default::default())
3001 .await
3002 .unwrap();
3003 tree.condition(cx, |tree, _| tree.entry_for_path(filename).is_none())
3004 .await;
3005
3006 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
3007 .await;
3008 }
3009 .boxed_local()
3010 }
3011}
3012
3013#[derive(Clone, Debug)]
3014struct TraversalProgress<'a> {
3015 max_path: &'a Path,
3016 count: usize,
3017 visible_count: usize,
3018 file_count: usize,
3019 visible_file_count: usize,
3020}
3021
3022impl<'a> TraversalProgress<'a> {
3023 fn count(&self, include_dirs: bool, include_ignored: bool) -> usize {
3024 match (include_ignored, include_dirs) {
3025 (true, true) => self.count,
3026 (true, false) => self.file_count,
3027 (false, true) => self.visible_count,
3028 (false, false) => self.visible_file_count,
3029 }
3030 }
3031}
3032
3033impl<'a> sum_tree::Dimension<'a, EntrySummary> for TraversalProgress<'a> {
3034 fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
3035 self.max_path = summary.max_path.as_ref();
3036 self.count += summary.count;
3037 self.visible_count += summary.visible_count;
3038 self.file_count += summary.file_count;
3039 self.visible_file_count += summary.visible_file_count;
3040 }
3041}
3042
3043impl<'a> Default for TraversalProgress<'a> {
3044 fn default() -> Self {
3045 Self {
3046 max_path: Path::new(""),
3047 count: 0,
3048 visible_count: 0,
3049 file_count: 0,
3050 visible_file_count: 0,
3051 }
3052 }
3053}
3054
3055pub struct Traversal<'a> {
3056 cursor: sum_tree::Cursor<'a, Entry, TraversalProgress<'a>>,
3057 include_ignored: bool,
3058 include_dirs: bool,
3059}
3060
3061impl<'a> Traversal<'a> {
3062 pub fn advance(&mut self) -> bool {
3063 self.advance_to_offset(self.offset() + 1)
3064 }
3065
3066 pub fn advance_to_offset(&mut self, offset: usize) -> bool {
3067 self.cursor.seek_forward(
3068 &TraversalTarget::Count {
3069 count: offset,
3070 include_dirs: self.include_dirs,
3071 include_ignored: self.include_ignored,
3072 },
3073 Bias::Right,
3074 &(),
3075 )
3076 }
3077
3078 pub fn advance_to_sibling(&mut self) -> bool {
3079 while let Some(entry) = self.cursor.item() {
3080 self.cursor.seek_forward(
3081 &TraversalTarget::PathSuccessor(&entry.path),
3082 Bias::Left,
3083 &(),
3084 );
3085 if let Some(entry) = self.cursor.item() {
3086 if (self.include_dirs || !entry.is_dir())
3087 && (self.include_ignored || !entry.is_ignored)
3088 {
3089 return true;
3090 }
3091 }
3092 }
3093 false
3094 }
3095
3096 pub fn entry(&self) -> Option<&'a Entry> {
3097 self.cursor.item()
3098 }
3099
3100 pub fn offset(&self) -> usize {
3101 self.cursor
3102 .start()
3103 .count(self.include_dirs, self.include_ignored)
3104 }
3105}
3106
3107impl<'a> Iterator for Traversal<'a> {
3108 type Item = &'a Entry;
3109
3110 fn next(&mut self) -> Option<Self::Item> {
3111 if let Some(item) = self.entry() {
3112 self.advance();
3113 Some(item)
3114 } else {
3115 None
3116 }
3117 }
3118}
3119
3120#[derive(Debug)]
3121enum TraversalTarget<'a> {
3122 Path(&'a Path),
3123 PathSuccessor(&'a Path),
3124 Count {
3125 count: usize,
3126 include_ignored: bool,
3127 include_dirs: bool,
3128 },
3129}
3130
3131impl<'a, 'b> SeekTarget<'a, EntrySummary, TraversalProgress<'a>> for TraversalTarget<'b> {
3132 fn cmp(&self, cursor_location: &TraversalProgress<'a>, _: &()) -> Ordering {
3133 match self {
3134 TraversalTarget::Path(path) => path.cmp(&cursor_location.max_path),
3135 TraversalTarget::PathSuccessor(path) => {
3136 if !cursor_location.max_path.starts_with(path) {
3137 Ordering::Equal
3138 } else {
3139 Ordering::Greater
3140 }
3141 }
3142 TraversalTarget::Count {
3143 count,
3144 include_dirs,
3145 include_ignored,
3146 } => Ord::cmp(
3147 count,
3148 &cursor_location.count(*include_dirs, *include_ignored),
3149 ),
3150 }
3151 }
3152}
3153
3154struct ChildEntriesIter<'a> {
3155 parent_path: &'a Path,
3156 traversal: Traversal<'a>,
3157}
3158
3159impl<'a> Iterator for ChildEntriesIter<'a> {
3160 type Item = &'a Entry;
3161
3162 fn next(&mut self) -> Option<Self::Item> {
3163 if let Some(item) = self.traversal.entry() {
3164 if item.path.starts_with(&self.parent_path) {
3165 self.traversal.advance_to_sibling();
3166 return Some(item);
3167 }
3168 }
3169 None
3170 }
3171}
3172
3173impl<'a> From<&'a Entry> for proto::Entry {
3174 fn from(entry: &'a Entry) -> Self {
3175 Self {
3176 id: entry.id.to_proto(),
3177 is_dir: entry.is_dir(),
3178 path: entry.path.to_string_lossy().into(),
3179 inode: entry.inode,
3180 mtime: Some(entry.mtime.into()),
3181 is_symlink: entry.is_symlink,
3182 is_ignored: entry.is_ignored,
3183 }
3184 }
3185}
3186
3187impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry {
3188 type Error = anyhow::Error;
3189
3190 fn try_from((root_char_bag, entry): (&'a CharBag, proto::Entry)) -> Result<Self> {
3191 if let Some(mtime) = entry.mtime {
3192 let kind = if entry.is_dir {
3193 EntryKind::Dir
3194 } else {
3195 let mut char_bag = *root_char_bag;
3196 char_bag.extend(entry.path.chars().map(|c| c.to_ascii_lowercase()));
3197 EntryKind::File(char_bag)
3198 };
3199 let path: Arc<Path> = PathBuf::from(entry.path).into();
3200 Ok(Entry {
3201 id: ProjectEntryId::from_proto(entry.id),
3202 kind,
3203 path,
3204 inode: entry.inode,
3205 mtime: mtime.into(),
3206 is_symlink: entry.is_symlink,
3207 is_ignored: entry.is_ignored,
3208 })
3209 } else {
3210 Err(anyhow!(
3211 "missing mtime in remote worktree entry {:?}",
3212 entry.path
3213 ))
3214 }
3215 }
3216}
3217
3218#[cfg(test)]
3219mod tests {
3220 use super::*;
3221 use fs::{FakeFs, RealFs};
3222 use gpui::{executor::Deterministic, TestAppContext};
3223 use pretty_assertions::assert_eq;
3224 use rand::prelude::*;
3225 use serde_json::json;
3226 use std::{env, fmt::Write};
3227 use util::{http::FakeHttpClient, test::temp_tree};
3228
3229 #[gpui::test]
3230 async fn test_traversal(cx: &mut TestAppContext) {
3231 let fs = FakeFs::new(cx.background());
3232 fs.insert_tree(
3233 "/root",
3234 json!({
3235 ".gitignore": "a/b\n",
3236 "a": {
3237 "b": "",
3238 "c": "",
3239 }
3240 }),
3241 )
3242 .await;
3243
3244 let http_client = FakeHttpClient::with_404_response();
3245 let client = cx.read(|cx| Client::new(http_client, cx));
3246
3247 let tree = Worktree::local(
3248 client,
3249 Path::new("/root"),
3250 true,
3251 fs,
3252 Default::default(),
3253 &mut cx.to_async(),
3254 )
3255 .await
3256 .unwrap();
3257 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
3258 .await;
3259
3260 tree.read_with(cx, |tree, _| {
3261 assert_eq!(
3262 tree.entries(false)
3263 .map(|entry| entry.path.as_ref())
3264 .collect::<Vec<_>>(),
3265 vec![
3266 Path::new(""),
3267 Path::new(".gitignore"),
3268 Path::new("a"),
3269 Path::new("a/c"),
3270 ]
3271 );
3272 assert_eq!(
3273 tree.entries(true)
3274 .map(|entry| entry.path.as_ref())
3275 .collect::<Vec<_>>(),
3276 vec![
3277 Path::new(""),
3278 Path::new(".gitignore"),
3279 Path::new("a"),
3280 Path::new("a/b"),
3281 Path::new("a/c"),
3282 ]
3283 );
3284 })
3285 }
3286
3287 #[gpui::test(iterations = 10)]
3288 async fn test_circular_symlinks(executor: Arc<Deterministic>, cx: &mut TestAppContext) {
3289 let fs = FakeFs::new(cx.background());
3290 fs.insert_tree(
3291 "/root",
3292 json!({
3293 "lib": {
3294 "a": {
3295 "a.txt": ""
3296 },
3297 "b": {
3298 "b.txt": ""
3299 }
3300 }
3301 }),
3302 )
3303 .await;
3304 fs.insert_symlink("/root/lib/a/lib", "..".into()).await;
3305 fs.insert_symlink("/root/lib/b/lib", "..".into()).await;
3306
3307 let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
3308 let tree = Worktree::local(
3309 client,
3310 Path::new("/root"),
3311 true,
3312 fs.clone(),
3313 Default::default(),
3314 &mut cx.to_async(),
3315 )
3316 .await
3317 .unwrap();
3318
3319 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
3320 .await;
3321
3322 tree.read_with(cx, |tree, _| {
3323 assert_eq!(
3324 tree.entries(false)
3325 .map(|entry| entry.path.as_ref())
3326 .collect::<Vec<_>>(),
3327 vec![
3328 Path::new(""),
3329 Path::new("lib"),
3330 Path::new("lib/a"),
3331 Path::new("lib/a/a.txt"),
3332 Path::new("lib/a/lib"),
3333 Path::new("lib/b"),
3334 Path::new("lib/b/b.txt"),
3335 Path::new("lib/b/lib"),
3336 ]
3337 );
3338 });
3339
3340 fs.rename(
3341 Path::new("/root/lib/a/lib"),
3342 Path::new("/root/lib/a/lib-2"),
3343 Default::default(),
3344 )
3345 .await
3346 .unwrap();
3347 executor.run_until_parked();
3348 tree.read_with(cx, |tree, _| {
3349 assert_eq!(
3350 tree.entries(false)
3351 .map(|entry| entry.path.as_ref())
3352 .collect::<Vec<_>>(),
3353 vec![
3354 Path::new(""),
3355 Path::new("lib"),
3356 Path::new("lib/a"),
3357 Path::new("lib/a/a.txt"),
3358 Path::new("lib/a/lib-2"),
3359 Path::new("lib/b"),
3360 Path::new("lib/b/b.txt"),
3361 Path::new("lib/b/lib"),
3362 ]
3363 );
3364 });
3365 }
3366
3367 #[gpui::test]
3368 async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
3369 let parent_dir = temp_tree(json!({
3370 ".gitignore": "ancestor-ignored-file1\nancestor-ignored-file2\n",
3371 "tree": {
3372 ".git": {},
3373 ".gitignore": "ignored-dir\n",
3374 "tracked-dir": {
3375 "tracked-file1": "",
3376 "ancestor-ignored-file1": "",
3377 },
3378 "ignored-dir": {
3379 "ignored-file1": ""
3380 }
3381 }
3382 }));
3383 let dir = parent_dir.path().join("tree");
3384
3385 let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
3386
3387 let tree = Worktree::local(
3388 client,
3389 dir.as_path(),
3390 true,
3391 Arc::new(RealFs),
3392 Default::default(),
3393 &mut cx.to_async(),
3394 )
3395 .await
3396 .unwrap();
3397 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
3398 .await;
3399 tree.flush_fs_events(cx).await;
3400 cx.read(|cx| {
3401 let tree = tree.read(cx);
3402 assert!(
3403 !tree
3404 .entry_for_path("tracked-dir/tracked-file1")
3405 .unwrap()
3406 .is_ignored
3407 );
3408 assert!(
3409 tree.entry_for_path("tracked-dir/ancestor-ignored-file1")
3410 .unwrap()
3411 .is_ignored
3412 );
3413 assert!(
3414 tree.entry_for_path("ignored-dir/ignored-file1")
3415 .unwrap()
3416 .is_ignored
3417 );
3418 });
3419
3420 std::fs::write(dir.join("tracked-dir/tracked-file2"), "").unwrap();
3421 std::fs::write(dir.join("tracked-dir/ancestor-ignored-file2"), "").unwrap();
3422 std::fs::write(dir.join("ignored-dir/ignored-file2"), "").unwrap();
3423 tree.flush_fs_events(cx).await;
3424 cx.read(|cx| {
3425 let tree = tree.read(cx);
3426 assert!(
3427 !tree
3428 .entry_for_path("tracked-dir/tracked-file2")
3429 .unwrap()
3430 .is_ignored
3431 );
3432 assert!(
3433 tree.entry_for_path("tracked-dir/ancestor-ignored-file2")
3434 .unwrap()
3435 .is_ignored
3436 );
3437 assert!(
3438 tree.entry_for_path("ignored-dir/ignored-file2")
3439 .unwrap()
3440 .is_ignored
3441 );
3442 assert!(tree.entry_for_path(".git").unwrap().is_ignored);
3443 });
3444 }
3445
3446 #[gpui::test]
3447 async fn test_git_repository_for_path(cx: &mut TestAppContext) {
3448 let root = temp_tree(json!({
3449 "dir1": {
3450 ".git": {},
3451 "deps": {
3452 "dep1": {
3453 ".git": {},
3454 "src": {
3455 "a.txt": ""
3456 }
3457 }
3458 },
3459 "src": {
3460 "b.txt": ""
3461 }
3462 },
3463 "c.txt": "",
3464 }));
3465
3466 let http_client = FakeHttpClient::with_404_response();
3467 let client = cx.read(|cx| Client::new(http_client, cx));
3468 let tree = Worktree::local(
3469 client,
3470 root.path(),
3471 true,
3472 Arc::new(RealFs),
3473 Default::default(),
3474 &mut cx.to_async(),
3475 )
3476 .await
3477 .unwrap();
3478
3479 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
3480 .await;
3481 tree.flush_fs_events(cx).await;
3482
3483 tree.read_with(cx, |tree, _cx| {
3484 let tree = tree.as_local().unwrap();
3485
3486 assert!(tree.repo_for("c.txt".as_ref()).is_none());
3487
3488 let entry = tree.repo_for("dir1/src/b.txt".as_ref()).unwrap();
3489 assert_eq!(entry.work_directory.0.as_ref(), Path::new("dir1"));
3490 assert_eq!(entry.git_dir_path.as_ref(), Path::new("dir1/.git"));
3491
3492 let entry = tree.repo_for("dir1/deps/dep1/src/a.txt".as_ref()).unwrap();
3493 assert_eq!(entry.work_directory.deref(), Path::new("dir1/deps/dep1"));
3494 assert_eq!(
3495 entry.git_dir_path.as_ref(),
3496 Path::new("dir1/deps/dep1/.git"),
3497 );
3498 });
3499
3500 let original_scan_id = tree.read_with(cx, |tree, _cx| {
3501 let tree = tree.as_local().unwrap();
3502 let entry = tree.repo_for("dir1/src/b.txt".as_ref()).unwrap();
3503 entry.scan_id
3504 });
3505
3506 std::fs::write(root.path().join("dir1/.git/random_new_file"), "hello").unwrap();
3507 tree.flush_fs_events(cx).await;
3508
3509 tree.read_with(cx, |tree, _cx| {
3510 let tree = tree.as_local().unwrap();
3511 let new_scan_id = {
3512 let entry = tree.repo_for("dir1/src/b.txt".as_ref()).unwrap();
3513 entry.scan_id
3514 };
3515 assert_ne!(
3516 original_scan_id, new_scan_id,
3517 "original {original_scan_id}, new {new_scan_id}"
3518 );
3519 });
3520
3521 std::fs::remove_dir_all(root.path().join("dir1/.git")).unwrap();
3522 tree.flush_fs_events(cx).await;
3523
3524 tree.read_with(cx, |tree, _cx| {
3525 let tree = tree.as_local().unwrap();
3526
3527 assert!(tree.repo_for("dir1/src/b.txt".as_ref()).is_none());
3528 });
3529 }
3530
3531 #[test]
3532 fn test_changed_repos() {
3533 fn fake_entry(git_dir_path: impl AsRef<Path>, scan_id: usize) -> RepositoryEntry {
3534 RepositoryEntry {
3535 scan_id,
3536 git_dir_path: git_dir_path.as_ref().into(),
3537 git_dir_entry_id: ProjectEntryId(0),
3538 work_directory: RepositoryWorkDirectory(
3539 Path::new(&format!("don't-care-{}", scan_id)).into(),
3540 ),
3541 branch: None,
3542 }
3543 }
3544
3545 let mut prev_repos = TreeMap::<RepositoryWorkDirectory, RepositoryEntry>::default();
3546 prev_repos.insert(
3547 RepositoryWorkDirectory(Path::new("don't-care-1").into()),
3548 fake_entry("/.git", 0),
3549 );
3550 prev_repos.insert(
3551 RepositoryWorkDirectory(Path::new("don't-care-2").into()),
3552 fake_entry("/a/.git", 0),
3553 );
3554 prev_repos.insert(
3555 RepositoryWorkDirectory(Path::new("don't-care-3").into()),
3556 fake_entry("/a/b/.git", 0),
3557 );
3558
3559 let mut new_repos = TreeMap::<RepositoryWorkDirectory, RepositoryEntry>::default();
3560 new_repos.insert(
3561 RepositoryWorkDirectory(Path::new("don't-care-4").into()),
3562 fake_entry("/a/.git", 1),
3563 );
3564 new_repos.insert(
3565 RepositoryWorkDirectory(Path::new("don't-care-5").into()),
3566 fake_entry("/a/b/.git", 0),
3567 );
3568 new_repos.insert(
3569 RepositoryWorkDirectory(Path::new("don't-care-6").into()),
3570 fake_entry("/a/c/.git", 0),
3571 );
3572
3573 let res = LocalWorktree::changed_repos(&prev_repos, &new_repos);
3574
3575 // Deletion retained
3576 assert!(res
3577 .iter()
3578 .find(|repo| repo.git_dir_path.as_ref() == Path::new("/.git") && repo.scan_id == 0)
3579 .is_some());
3580
3581 // Update retained
3582 assert!(res
3583 .iter()
3584 .find(|repo| repo.git_dir_path.as_ref() == Path::new("/a/.git") && repo.scan_id == 1)
3585 .is_some());
3586
3587 // Addition retained
3588 assert!(res
3589 .iter()
3590 .find(|repo| repo.git_dir_path.as_ref() == Path::new("/a/c/.git") && repo.scan_id == 0)
3591 .is_some());
3592
3593 // Nochange, not retained
3594 assert!(res
3595 .iter()
3596 .find(|repo| repo.git_dir_path.as_ref() == Path::new("/a/b/.git") && repo.scan_id == 0)
3597 .is_none());
3598 }
3599
3600 #[gpui::test]
3601 async fn test_write_file(cx: &mut TestAppContext) {
3602 let dir = temp_tree(json!({
3603 ".git": {},
3604 ".gitignore": "ignored-dir\n",
3605 "tracked-dir": {},
3606 "ignored-dir": {}
3607 }));
3608
3609 let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
3610
3611 let tree = Worktree::local(
3612 client,
3613 dir.path(),
3614 true,
3615 Arc::new(RealFs),
3616 Default::default(),
3617 &mut cx.to_async(),
3618 )
3619 .await
3620 .unwrap();
3621 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
3622 .await;
3623 tree.flush_fs_events(cx).await;
3624
3625 tree.update(cx, |tree, cx| {
3626 tree.as_local().unwrap().write_file(
3627 Path::new("tracked-dir/file.txt"),
3628 "hello".into(),
3629 Default::default(),
3630 cx,
3631 )
3632 })
3633 .await
3634 .unwrap();
3635 tree.update(cx, |tree, cx| {
3636 tree.as_local().unwrap().write_file(
3637 Path::new("ignored-dir/file.txt"),
3638 "world".into(),
3639 Default::default(),
3640 cx,
3641 )
3642 })
3643 .await
3644 .unwrap();
3645
3646 tree.read_with(cx, |tree, _| {
3647 let tracked = tree.entry_for_path("tracked-dir/file.txt").unwrap();
3648 let ignored = tree.entry_for_path("ignored-dir/file.txt").unwrap();
3649 assert!(!tracked.is_ignored);
3650 assert!(ignored.is_ignored);
3651 });
3652 }
3653
3654 #[gpui::test(iterations = 30)]
3655 async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
3656 let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
3657
3658 let fs = FakeFs::new(cx.background());
3659 fs.insert_tree(
3660 "/root",
3661 json!({
3662 "b": {},
3663 "c": {},
3664 "d": {},
3665 }),
3666 )
3667 .await;
3668
3669 let tree = Worktree::local(
3670 client,
3671 "/root".as_ref(),
3672 true,
3673 fs,
3674 Default::default(),
3675 &mut cx.to_async(),
3676 )
3677 .await
3678 .unwrap();
3679
3680 let mut snapshot1 = tree.update(cx, |tree, _| tree.as_local().unwrap().snapshot());
3681
3682 let entry = tree
3683 .update(cx, |tree, cx| {
3684 tree.as_local_mut()
3685 .unwrap()
3686 .create_entry("a/e".as_ref(), true, cx)
3687 })
3688 .await
3689 .unwrap();
3690 assert!(entry.is_dir());
3691
3692 cx.foreground().run_until_parked();
3693 tree.read_with(cx, |tree, _| {
3694 assert_eq!(tree.entry_for_path("a/e").unwrap().kind, EntryKind::Dir);
3695 });
3696
3697 let snapshot2 = tree.update(cx, |tree, _| tree.as_local().unwrap().snapshot());
3698 let update = snapshot2.build_update(&snapshot1, 0, 0, true);
3699 snapshot1.apply_remote_update(update).unwrap();
3700 assert_eq!(snapshot1.to_vec(true), snapshot2.to_vec(true),);
3701 }
3702
3703 #[gpui::test(iterations = 100)]
3704 async fn test_random_worktree_operations_during_initial_scan(
3705 cx: &mut TestAppContext,
3706 mut rng: StdRng,
3707 ) {
3708 let operations = env::var("OPERATIONS")
3709 .map(|o| o.parse().unwrap())
3710 .unwrap_or(5);
3711 let initial_entries = env::var("INITIAL_ENTRIES")
3712 .map(|o| o.parse().unwrap())
3713 .unwrap_or(20);
3714
3715 let root_dir = Path::new("/test");
3716 let fs = FakeFs::new(cx.background()) as Arc<dyn Fs>;
3717 fs.as_fake().insert_tree(root_dir, json!({})).await;
3718 for _ in 0..initial_entries {
3719 randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
3720 }
3721 log::info!("generated initial tree");
3722
3723 let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
3724 let worktree = Worktree::local(
3725 client.clone(),
3726 root_dir,
3727 true,
3728 fs.clone(),
3729 Default::default(),
3730 &mut cx.to_async(),
3731 )
3732 .await
3733 .unwrap();
3734
3735 let mut snapshot = worktree.update(cx, |tree, _| tree.as_local().unwrap().snapshot());
3736
3737 for _ in 0..operations {
3738 worktree
3739 .update(cx, |worktree, cx| {
3740 randomly_mutate_worktree(worktree, &mut rng, cx)
3741 })
3742 .await
3743 .log_err();
3744 worktree.read_with(cx, |tree, _| {
3745 tree.as_local().unwrap().snapshot.check_invariants()
3746 });
3747
3748 if rng.gen_bool(0.6) {
3749 let new_snapshot =
3750 worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
3751 let update = new_snapshot.build_update(&snapshot, 0, 0, true);
3752 snapshot.apply_remote_update(update.clone()).unwrap();
3753 assert_eq!(
3754 snapshot.to_vec(true),
3755 new_snapshot.to_vec(true),
3756 "incorrect snapshot after update {:?}",
3757 update
3758 );
3759 }
3760 }
3761
3762 worktree
3763 .update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
3764 .await;
3765 worktree.read_with(cx, |tree, _| {
3766 tree.as_local().unwrap().snapshot.check_invariants()
3767 });
3768
3769 let new_snapshot = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
3770 let update = new_snapshot.build_update(&snapshot, 0, 0, true);
3771 snapshot.apply_remote_update(update.clone()).unwrap();
3772 assert_eq!(
3773 snapshot.to_vec(true),
3774 new_snapshot.to_vec(true),
3775 "incorrect snapshot after update {:?}",
3776 update
3777 );
3778 }
3779
3780 #[gpui::test(iterations = 100)]
3781 async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng) {
3782 let operations = env::var("OPERATIONS")
3783 .map(|o| o.parse().unwrap())
3784 .unwrap_or(40);
3785 let initial_entries = env::var("INITIAL_ENTRIES")
3786 .map(|o| o.parse().unwrap())
3787 .unwrap_or(20);
3788
3789 let root_dir = Path::new("/test");
3790 let fs = FakeFs::new(cx.background()) as Arc<dyn Fs>;
3791 fs.as_fake().insert_tree(root_dir, json!({})).await;
3792 for _ in 0..initial_entries {
3793 randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
3794 }
3795 log::info!("generated initial tree");
3796
3797 let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
3798 let worktree = Worktree::local(
3799 client.clone(),
3800 root_dir,
3801 true,
3802 fs.clone(),
3803 Default::default(),
3804 &mut cx.to_async(),
3805 )
3806 .await
3807 .unwrap();
3808
3809 worktree
3810 .update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
3811 .await;
3812
3813 // After the initial scan is complete, the `UpdatedEntries` event can
3814 // be used to follow along with all changes to the worktree's snapshot.
3815 worktree.update(cx, |tree, cx| {
3816 let mut paths = tree
3817 .as_local()
3818 .unwrap()
3819 .paths()
3820 .cloned()
3821 .collect::<Vec<_>>();
3822
3823 cx.subscribe(&worktree, move |tree, _, event, _| {
3824 if let Event::UpdatedEntries(changes) = event {
3825 for (path, change_type) in changes.iter() {
3826 let path = path.clone();
3827 let ix = match paths.binary_search(&path) {
3828 Ok(ix) | Err(ix) => ix,
3829 };
3830 match change_type {
3831 PathChange::Added => {
3832 assert_ne!(paths.get(ix), Some(&path));
3833 paths.insert(ix, path);
3834 }
3835 PathChange::Removed => {
3836 assert_eq!(paths.get(ix), Some(&path));
3837 paths.remove(ix);
3838 }
3839 PathChange::Updated => {
3840 assert_eq!(paths.get(ix), Some(&path));
3841 }
3842 PathChange::AddedOrUpdated => {
3843 if paths[ix] != path {
3844 paths.insert(ix, path);
3845 }
3846 }
3847 }
3848 }
3849 let new_paths = tree.paths().cloned().collect::<Vec<_>>();
3850 assert_eq!(paths, new_paths, "incorrect changes: {:?}", changes);
3851 }
3852 })
3853 .detach();
3854 });
3855
3856 let mut snapshots = Vec::new();
3857 let mut mutations_len = operations;
3858 while mutations_len > 1 {
3859 randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
3860 let buffered_event_count = fs.as_fake().buffered_event_count().await;
3861 if buffered_event_count > 0 && rng.gen_bool(0.3) {
3862 let len = rng.gen_range(0..=buffered_event_count);
3863 log::info!("flushing {} events", len);
3864 fs.as_fake().flush_events(len).await;
3865 } else {
3866 randomly_mutate_fs(&fs, root_dir, 0.6, &mut rng).await;
3867 mutations_len -= 1;
3868 }
3869
3870 cx.foreground().run_until_parked();
3871 if rng.gen_bool(0.2) {
3872 log::info!("storing snapshot {}", snapshots.len());
3873 let snapshot =
3874 worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
3875 snapshots.push(snapshot);
3876 }
3877 }
3878
3879 log::info!("quiescing");
3880 fs.as_fake().flush_events(usize::MAX).await;
3881 cx.foreground().run_until_parked();
3882 let snapshot = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
3883 snapshot.check_invariants();
3884
3885 {
3886 let new_worktree = Worktree::local(
3887 client.clone(),
3888 root_dir,
3889 true,
3890 fs.clone(),
3891 Default::default(),
3892 &mut cx.to_async(),
3893 )
3894 .await
3895 .unwrap();
3896 new_worktree
3897 .update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
3898 .await;
3899 let new_snapshot =
3900 new_worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
3901 assert_eq!(snapshot.to_vec(true), new_snapshot.to_vec(true));
3902 }
3903
3904 for (i, mut prev_snapshot) in snapshots.into_iter().enumerate() {
3905 let include_ignored = rng.gen::<bool>();
3906 if !include_ignored {
3907 let mut entries_by_path_edits = Vec::new();
3908 let mut entries_by_id_edits = Vec::new();
3909 for entry in prev_snapshot
3910 .entries_by_id
3911 .cursor::<()>()
3912 .filter(|e| e.is_ignored)
3913 {
3914 entries_by_path_edits.push(Edit::Remove(PathKey(entry.path.clone())));
3915 entries_by_id_edits.push(Edit::Remove(entry.id));
3916 }
3917
3918 prev_snapshot
3919 .entries_by_path
3920 .edit(entries_by_path_edits, &());
3921 prev_snapshot.entries_by_id.edit(entries_by_id_edits, &());
3922 }
3923
3924 let update = snapshot.build_update(&prev_snapshot, 0, 0, include_ignored);
3925 prev_snapshot.apply_remote_update(update.clone()).unwrap();
3926 assert_eq!(
3927 prev_snapshot.to_vec(include_ignored),
3928 snapshot.to_vec(include_ignored),
3929 "wrong update for snapshot {i}. update: {:?}",
3930 update
3931 );
3932 }
3933 }
3934
3935 fn randomly_mutate_worktree(
3936 worktree: &mut Worktree,
3937 rng: &mut impl Rng,
3938 cx: &mut ModelContext<Worktree>,
3939 ) -> Task<Result<()>> {
3940 let worktree = worktree.as_local_mut().unwrap();
3941 let snapshot = worktree.snapshot();
3942 let entry = snapshot.entries(false).choose(rng).unwrap();
3943
3944 match rng.gen_range(0_u32..100) {
3945 0..=33 if entry.path.as_ref() != Path::new("") => {
3946 log::info!("deleting entry {:?} ({})", entry.path, entry.id.0);
3947 worktree.delete_entry(entry.id, cx).unwrap()
3948 }
3949 ..=66 if entry.path.as_ref() != Path::new("") => {
3950 let other_entry = snapshot.entries(false).choose(rng).unwrap();
3951 let new_parent_path = if other_entry.is_dir() {
3952 other_entry.path.clone()
3953 } else {
3954 other_entry.path.parent().unwrap().into()
3955 };
3956 let mut new_path = new_parent_path.join(gen_name(rng));
3957 if new_path.starts_with(&entry.path) {
3958 new_path = gen_name(rng).into();
3959 }
3960
3961 log::info!(
3962 "renaming entry {:?} ({}) to {:?}",
3963 entry.path,
3964 entry.id.0,
3965 new_path
3966 );
3967 let task = worktree.rename_entry(entry.id, new_path, cx).unwrap();
3968 cx.foreground().spawn(async move {
3969 task.await?;
3970 Ok(())
3971 })
3972 }
3973 _ => {
3974 let task = if entry.is_dir() {
3975 let child_path = entry.path.join(gen_name(rng));
3976 let is_dir = rng.gen_bool(0.3);
3977 log::info!(
3978 "creating {} at {:?}",
3979 if is_dir { "dir" } else { "file" },
3980 child_path,
3981 );
3982 worktree.create_entry(child_path, is_dir, cx)
3983 } else {
3984 log::info!("overwriting file {:?} ({})", entry.path, entry.id.0);
3985 worktree.write_file(entry.path.clone(), "".into(), Default::default(), cx)
3986 };
3987 cx.foreground().spawn(async move {
3988 task.await?;
3989 Ok(())
3990 })
3991 }
3992 }
3993 }
3994
3995 async fn randomly_mutate_fs(
3996 fs: &Arc<dyn Fs>,
3997 root_path: &Path,
3998 insertion_probability: f64,
3999 rng: &mut impl Rng,
4000 ) {
4001 let mut files = Vec::new();
4002 let mut dirs = Vec::new();
4003 for path in fs.as_fake().paths() {
4004 if path.starts_with(root_path) {
4005 if fs.is_file(&path).await {
4006 files.push(path);
4007 } else {
4008 dirs.push(path);
4009 }
4010 }
4011 }
4012
4013 if (files.is_empty() && dirs.len() == 1) || rng.gen_bool(insertion_probability) {
4014 let path = dirs.choose(rng).unwrap();
4015 let new_path = path.join(gen_name(rng));
4016
4017 if rng.gen() {
4018 log::info!(
4019 "creating dir {:?}",
4020 new_path.strip_prefix(root_path).unwrap()
4021 );
4022 fs.create_dir(&new_path).await.unwrap();
4023 } else {
4024 log::info!(
4025 "creating file {:?}",
4026 new_path.strip_prefix(root_path).unwrap()
4027 );
4028 fs.create_file(&new_path, Default::default()).await.unwrap();
4029 }
4030 } else if rng.gen_bool(0.05) {
4031 let ignore_dir_path = dirs.choose(rng).unwrap();
4032 let ignore_path = ignore_dir_path.join(&*GITIGNORE);
4033
4034 let subdirs = dirs
4035 .iter()
4036 .filter(|d| d.starts_with(&ignore_dir_path))
4037 .cloned()
4038 .collect::<Vec<_>>();
4039 let subfiles = files
4040 .iter()
4041 .filter(|d| d.starts_with(&ignore_dir_path))
4042 .cloned()
4043 .collect::<Vec<_>>();
4044 let files_to_ignore = {
4045 let len = rng.gen_range(0..=subfiles.len());
4046 subfiles.choose_multiple(rng, len)
4047 };
4048 let dirs_to_ignore = {
4049 let len = rng.gen_range(0..subdirs.len());
4050 subdirs.choose_multiple(rng, len)
4051 };
4052
4053 let mut ignore_contents = String::new();
4054 for path_to_ignore in files_to_ignore.chain(dirs_to_ignore) {
4055 writeln!(
4056 ignore_contents,
4057 "{}",
4058 path_to_ignore
4059 .strip_prefix(&ignore_dir_path)
4060 .unwrap()
4061 .to_str()
4062 .unwrap()
4063 )
4064 .unwrap();
4065 }
4066 log::info!(
4067 "creating gitignore {:?} with contents:\n{}",
4068 ignore_path.strip_prefix(&root_path).unwrap(),
4069 ignore_contents
4070 );
4071 fs.save(
4072 &ignore_path,
4073 &ignore_contents.as_str().into(),
4074 Default::default(),
4075 )
4076 .await
4077 .unwrap();
4078 } else {
4079 let old_path = {
4080 let file_path = files.choose(rng);
4081 let dir_path = dirs[1..].choose(rng);
4082 file_path.into_iter().chain(dir_path).choose(rng).unwrap()
4083 };
4084
4085 let is_rename = rng.gen();
4086 if is_rename {
4087 let new_path_parent = dirs
4088 .iter()
4089 .filter(|d| !d.starts_with(old_path))
4090 .choose(rng)
4091 .unwrap();
4092
4093 let overwrite_existing_dir =
4094 !old_path.starts_with(&new_path_parent) && rng.gen_bool(0.3);
4095 let new_path = if overwrite_existing_dir {
4096 fs.remove_dir(
4097 &new_path_parent,
4098 RemoveOptions {
4099 recursive: true,
4100 ignore_if_not_exists: true,
4101 },
4102 )
4103 .await
4104 .unwrap();
4105 new_path_parent.to_path_buf()
4106 } else {
4107 new_path_parent.join(gen_name(rng))
4108 };
4109
4110 log::info!(
4111 "renaming {:?} to {}{:?}",
4112 old_path.strip_prefix(&root_path).unwrap(),
4113 if overwrite_existing_dir {
4114 "overwrite "
4115 } else {
4116 ""
4117 },
4118 new_path.strip_prefix(&root_path).unwrap()
4119 );
4120 fs.rename(
4121 &old_path,
4122 &new_path,
4123 fs::RenameOptions {
4124 overwrite: true,
4125 ignore_if_exists: true,
4126 },
4127 )
4128 .await
4129 .unwrap();
4130 } else if fs.is_file(&old_path).await {
4131 log::info!(
4132 "deleting file {:?}",
4133 old_path.strip_prefix(&root_path).unwrap()
4134 );
4135 fs.remove_file(old_path, Default::default()).await.unwrap();
4136 } else {
4137 log::info!(
4138 "deleting dir {:?}",
4139 old_path.strip_prefix(&root_path).unwrap()
4140 );
4141 fs.remove_dir(
4142 &old_path,
4143 RemoveOptions {
4144 recursive: true,
4145 ignore_if_not_exists: true,
4146 },
4147 )
4148 .await
4149 .unwrap();
4150 }
4151 }
4152 }
4153
4154 fn gen_name(rng: &mut impl Rng) -> String {
4155 (0..6)
4156 .map(|_| rng.sample(rand::distributions::Alphanumeric))
4157 .map(char::from)
4158 .collect()
4159 }
4160
4161 impl LocalSnapshot {
4162 fn check_invariants(&self) {
4163 assert_eq!(
4164 self.entries_by_path
4165 .cursor::<()>()
4166 .map(|e| (&e.path, e.id))
4167 .collect::<Vec<_>>(),
4168 self.entries_by_id
4169 .cursor::<()>()
4170 .map(|e| (&e.path, e.id))
4171 .collect::<collections::BTreeSet<_>>()
4172 .into_iter()
4173 .collect::<Vec<_>>(),
4174 "entries_by_path and entries_by_id are inconsistent"
4175 );
4176
4177 let mut files = self.files(true, 0);
4178 let mut visible_files = self.files(false, 0);
4179 for entry in self.entries_by_path.cursor::<()>() {
4180 if entry.is_file() {
4181 assert_eq!(files.next().unwrap().inode, entry.inode);
4182 if !entry.is_ignored {
4183 assert_eq!(visible_files.next().unwrap().inode, entry.inode);
4184 }
4185 }
4186 }
4187
4188 assert!(files.next().is_none());
4189 assert!(visible_files.next().is_none());
4190
4191 let mut bfs_paths = Vec::new();
4192 let mut stack = vec![Path::new("")];
4193 while let Some(path) = stack.pop() {
4194 bfs_paths.push(path);
4195 let ix = stack.len();
4196 for child_entry in self.child_entries(path) {
4197 stack.insert(ix, &child_entry.path);
4198 }
4199 }
4200
4201 let dfs_paths_via_iter = self
4202 .entries_by_path
4203 .cursor::<()>()
4204 .map(|e| e.path.as_ref())
4205 .collect::<Vec<_>>();
4206 assert_eq!(bfs_paths, dfs_paths_via_iter);
4207
4208 let dfs_paths_via_traversal = self
4209 .entries(true)
4210 .map(|e| e.path.as_ref())
4211 .collect::<Vec<_>>();
4212 assert_eq!(dfs_paths_via_traversal, dfs_paths_via_iter);
4213
4214 for ignore_parent_abs_path in self.ignores_by_parent_abs_path.keys() {
4215 let ignore_parent_path =
4216 ignore_parent_abs_path.strip_prefix(&self.abs_path).unwrap();
4217 assert!(self.entry_for_path(&ignore_parent_path).is_some());
4218 assert!(self
4219 .entry_for_path(ignore_parent_path.join(&*GITIGNORE))
4220 .is_some());
4221 }
4222 }
4223
4224 fn to_vec(&self, include_ignored: bool) -> Vec<(&Path, u64, bool)> {
4225 let mut paths = Vec::new();
4226 for entry in self.entries_by_path.cursor::<()>() {
4227 if include_ignored || !entry.is_ignored {
4228 paths.push((entry.path.as_ref(), entry.inode, entry.is_ignored));
4229 }
4230 }
4231 paths.sort_by(|a, b| a.0.cmp(b.0));
4232 paths
4233 }
4234 }
4235}