WIP, re-doing fs and fake git repos

Mikayla Maki created

Change summary

Cargo.lock                     |   1 
crates/git/Cargo.toml          |   2 
crates/git/src/repository.rs   | 123 ++++++++++++++++++++++++++++++-----
crates/language/src/buffer.rs  |   1 
crates/project/src/fs.rs       |  10 ++
crates/project/src/project.rs  |   8 -
crates/project/src/worktree.rs |  50 +++++++-------
7 files changed, 145 insertions(+), 50 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2230,6 +2230,7 @@ name = "git"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "async-trait",
  "clock",
  "git2",
  "lazy_static",

crates/git/Cargo.toml 🔗

@@ -17,6 +17,8 @@ util = { path = "../util" }
 log = { version = "0.4.16", features = ["kv_unstable_serde"] }
 smol = "1.2"
 parking_lot = "0.11.1"
+async-trait = "0.1"
+
 
 [dev-dependencies]
 unindent = "0.1.7"

crates/git/src/repository.rs 🔗

@@ -4,8 +4,29 @@ use parking_lot::Mutex;
 use std::{path::Path, sync::Arc};
 use util::ResultExt;
 
+#[async_trait::async_trait]
+pub trait GitRepository: Send + Sync + std::fmt::Debug {
+    fn manages(&self, path: &Path) -> bool;
+
+    fn in_dot_git(&self, path: &Path) -> bool;
+
+    fn content_path(&self) -> &Path;
+
+    fn git_dir_path(&self) -> &Path;
+
+    fn scan_id(&self) -> usize;
+
+    fn set_scan_id(&mut self, scan_id: usize);
+
+    fn git_repo(&self) -> Arc<Mutex<LibGitRepository>>;
+
+    fn boxed_clone(&self) -> Box<dyn GitRepository>;
+
+    async fn load_head_text(&self, relative_file_path: &Path) -> Option<String>;
+}
+
 #[derive(Clone)]
-pub struct GitRepository {
+pub struct RealGitRepository {
     // Path to folder containing the .git file or directory
     content_path: Arc<Path>,
     // Path to the actual .git folder.
@@ -15,50 +36,48 @@ pub struct GitRepository {
     libgit_repository: Arc<Mutex<LibGitRepository>>,
 }
 
-impl GitRepository {
-    pub fn open(dotgit_path: &Path) -> Option<GitRepository> {
+impl RealGitRepository {
+    pub fn open(dotgit_path: &Path) -> Option<Box<dyn GitRepository>> {
         LibGitRepository::open(&dotgit_path)
             .log_err()
-            .and_then(|libgit_repository| {
-                Some(Self {
+            .and_then::<Box<dyn GitRepository>, _>(|libgit_repository| {
+                Some(Box::new(Self {
                     content_path: libgit_repository.workdir()?.into(),
                     git_dir_path: dotgit_path.canonicalize().log_err()?.into(),
                     scan_id: 0,
                     libgit_repository: Arc::new(parking_lot::Mutex::new(libgit_repository)),
-                })
+                }))
             })
     }
+}
 
-    pub fn manages(&self, path: &Path) -> bool {
+#[async_trait::async_trait]
+impl GitRepository for RealGitRepository {
+    fn manages(&self, path: &Path) -> bool {
         path.canonicalize()
             .map(|path| path.starts_with(&self.content_path))
             .unwrap_or(false)
     }
 
-    pub fn in_dot_git(&self, path: &Path) -> bool {
+    fn in_dot_git(&self, path: &Path) -> bool {
         path.canonicalize()
             .map(|path| path.starts_with(&self.git_dir_path))
             .unwrap_or(false)
     }
 
-    pub fn content_path(&self) -> &Path {
+    fn content_path(&self) -> &Path {
         self.content_path.as_ref()
     }
 
-    pub fn git_dir_path(&self) -> &Path {
+    fn git_dir_path(&self) -> &Path {
         self.git_dir_path.as_ref()
     }
 
-    pub fn scan_id(&self) -> usize {
+    fn scan_id(&self) -> usize {
         self.scan_id
     }
 
-    pub fn set_scan_id(&mut self, scan_id: usize) {
-        println!("setting scan id");
-        self.scan_id = scan_id;
-    }
-
-    pub async fn load_head_text(&self, relative_file_path: &Path) -> Option<String> {
+    async fn load_head_text(&self, relative_file_path: &Path) -> Option<String> {
         fn logic(repo: &LibGitRepository, relative_file_path: &Path) -> Result<Option<String>> {
             let object = repo
                 .head()?
@@ -81,9 +100,21 @@ impl GitRepository {
         }
         None
     }
+
+    fn git_repo(&self) -> Arc<Mutex<LibGitRepository>> {
+        self.libgit_repository.clone()
+    }
+
+    fn set_scan_id(&mut self, scan_id: usize) {
+        self.scan_id = scan_id;
+    }
+
+    fn boxed_clone(&self) -> Box<dyn GitRepository> {
+        Box::new(self.clone())
+    }
 }
 
-impl std::fmt::Debug for GitRepository {
+impl std::fmt::Debug for RealGitRepository {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("GitRepository")
             .field("content_path", &self.content_path)
@@ -93,3 +124,59 @@ impl std::fmt::Debug for GitRepository {
             .finish()
     }
 }
+
+#[derive(Debug, Clone)]
+pub struct FakeGitRepository {
+    content_path: Arc<Path>,
+    git_dir_path: Arc<Path>,
+    scan_id: usize,
+}
+
+impl FakeGitRepository {
+    pub fn open(dotgit_path: &Path, scan_id: usize) -> Box<dyn GitRepository> {
+        Box::new(FakeGitRepository {
+            content_path: dotgit_path.parent().unwrap().into(),
+            git_dir_path: dotgit_path.into(),
+            scan_id,
+        })
+    }
+}
+
+#[async_trait::async_trait]
+impl GitRepository for FakeGitRepository {
+    fn manages(&self, path: &Path) -> bool {
+        path.starts_with(self.content_path())
+    }
+
+    fn in_dot_git(&self, path: &Path) -> bool {
+        path.starts_with(self.git_dir_path())
+    }
+
+    fn content_path(&self) -> &Path {
+        &self.content_path
+    }
+
+    fn git_dir_path(&self) -> &Path {
+        &self.git_dir_path
+    }
+
+    fn scan_id(&self) -> usize {
+        self.scan_id
+    }
+
+    async fn load_head_text(&self, _: &Path) -> Option<String> {
+        unimplemented!()
+    }
+
+    fn git_repo(&self) -> Arc<Mutex<LibGitRepository>> {
+        unimplemented!()
+    }
+
+    fn set_scan_id(&mut self, scan_id: usize) {
+        self.scan_id = scan_id;
+    }
+
+    fn boxed_clone(&self) -> Box<dyn GitRepository> {
+        Box::new(self.clone())
+    }
+}

crates/language/src/buffer.rs 🔗

@@ -672,7 +672,6 @@ impl Buffer {
     }
 
     pub fn git_diff_recalc(&mut self, cx: &mut ModelContext<Self>) {
-        println!("recalc");
         if self.git_diff_status.update_in_progress {
             self.git_diff_status.update_requested = true;
             return;

crates/project/src/fs.rs 🔗

@@ -1,6 +1,7 @@
 use anyhow::{anyhow, Result};
 use fsevent::EventStream;
 use futures::{future::BoxFuture, Stream, StreamExt};
+use git::repository::{FakeGitRepository, GitRepository, RealGitRepository};
 use language::LineEnding;
 use smol::io::{AsyncReadExt, AsyncWriteExt};
 use std::{
@@ -43,6 +44,7 @@ pub trait Fs: Send + Sync {
         path: &Path,
         latency: Duration,
     ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>>;
+    async fn open_repo(&self, abs_dot_git: &Path) -> Option<Box<dyn GitRepository>>;
     fn is_fake(&self) -> bool;
     #[cfg(any(test, feature = "test-support"))]
     fn as_fake(&self) -> &FakeFs;
@@ -236,6 +238,10 @@ impl Fs for RealFs {
         })))
     }
 
+    fn open_repo(&self, abs_dot_git: &Path) -> Option<Box<dyn GitRepository>> {
+        RealGitRepository::open(&abs_dot_git)
+    }
+
     fn is_fake(&self) -> bool {
         false
     }
@@ -847,6 +853,10 @@ impl Fs for FakeFs {
         }))
     }
 
+    fn open_repo(&self, abs_dot_git: &Path) -> Option<Box<dyn GitRepository>> {
+        Some(FakeGitRepository::open(abs_dot_git.into(), 0))
+    }
+
     fn is_fake(&self) -> bool {
         true
     }

crates/project/src/project.rs 🔗

@@ -4537,7 +4537,6 @@ impl Project {
             cx.subscribe(worktree, |this, worktree, event, cx| match event {
                 worktree::Event::UpdatedEntries => this.update_local_worktree_buffers(worktree, cx),
                 worktree::Event::UpdatedGitRepositories(updated_repos) => {
-                    println!("{updated_repos:#?}");
                     this.update_local_worktree_buffers_git_repos(updated_repos, cx)
                 }
             })
@@ -4649,7 +4648,7 @@ impl Project {
 
     fn update_local_worktree_buffers_git_repos(
         &mut self,
-        repos: &[GitRepository],
+        repos: &[Box<dyn GitRepository>],
         cx: &mut ModelContext<Self>,
     ) {
         //TODO: Produce protos
@@ -4662,18 +4661,15 @@ impl Project {
                 };
                 let path = file.path().clone();
                 let abs_path = file.abs_path(cx);
-                println!("got file");
 
                 let repo = match repos.iter().find(|repo| repo.manages(&abs_path)) {
-                    Some(repo) => repo.clone(),
+                    Some(repo) => repo.boxed_clone(),
                     None => return,
                 };
-                println!("got repo");
 
                 cx.spawn(|_, mut cx| async move {
                     let head_text = repo.load_head_text(&path).await;
                     buffer.update(&mut cx, |buffer, cx| {
-                        println!("got calling update");
                         buffer.update_head_text(head_text, cx);
                     });
                 })

crates/project/src/worktree.rs 🔗

@@ -100,7 +100,7 @@ pub struct Snapshot {
 pub struct LocalSnapshot {
     abs_path: Arc<Path>,
     ignores_by_parent_abs_path: HashMap<Arc<Path>, (Arc<Gitignore>, usize)>,
-    git_repositories: Vec<GitRepository>,
+    git_repositories: Vec<Box<dyn GitRepository>>,
     removed_entry_ids: HashMap<u64, ProjectEntryId>,
     next_entry_id: Arc<AtomicUsize>,
     snapshot: Snapshot,
@@ -115,7 +115,7 @@ impl Clone for LocalSnapshot {
             git_repositories: self
                 .git_repositories
                 .iter()
-                .map(|repo| repo.clone())
+                .map(|repo| repo.boxed_clone())
                 .collect(),
             removed_entry_ids: self.removed_entry_ids.clone(),
             next_entry_id: self.next_entry_id.clone(),
@@ -157,7 +157,7 @@ struct ShareState {
 
 pub enum Event {
     UpdatedEntries,
-    UpdatedGitRepositories(Vec<GitRepository>),
+    UpdatedGitRepositories(Vec<Box<dyn GitRepository>>),
 }
 
 impl Entity for Worktree {
@@ -581,16 +581,13 @@ impl LocalWorktree {
     }
 
     fn list_updated_repos(
-        old_repos: &[GitRepository],
-        new_repos: &[GitRepository],
-    ) -> Vec<GitRepository> {
-        println!("old repos: {:#?}", old_repos);
-        println!("new repos: {:#?}", new_repos);
-
+        old_repos: &[Box<dyn GitRepository>],
+        new_repos: &[Box<dyn GitRepository>],
+    ) -> Vec<Box<dyn GitRepository>> {
         fn diff<'a>(
-            a: &'a [GitRepository],
-            b: &'a [GitRepository],
-            updated: &mut HashMap<&'a Path, GitRepository>,
+            a: &'a [Box<dyn GitRepository>],
+            b: &'a [Box<dyn GitRepository>],
+            updated: &mut HashMap<&'a Path, Box<dyn GitRepository>>,
         ) {
             for a_repo in a {
                 let matched = b.iter().find(|b_repo| {
@@ -599,17 +596,17 @@ impl LocalWorktree {
                 });
 
                 if matched.is_some() {
-                    updated.insert(a_repo.git_dir_path(), a_repo.clone());
+                    updated.insert(a_repo.git_dir_path(), a_repo.boxed_clone());
                 }
             }
         }
 
-        let mut updated = HashMap::<&Path, GitRepository>::default();
+        let mut updated = HashMap::<&Path, Box<dyn GitRepository>>::default();
 
         diff(old_repos, new_repos, &mut updated);
         diff(new_repos, old_repos, &mut updated);
 
-        dbg!(updated.into_values().collect())
+        updated.into_values().collect()
     }
 
     pub fn scan_complete(&self) -> impl Future<Output = ()> {
@@ -1364,23 +1361,22 @@ impl LocalSnapshot {
     }
 
     // Gives the most specific git repository for a given path
-    pub(crate) fn repo_for(&self, path: &Path) -> Option<GitRepository> {
+    pub(crate) fn repo_for(&self, path: &Path) -> Option<Box<dyn GitRepository>> {
         self.git_repositories
             .iter()
             .rev() //git_repository is ordered lexicographically
             .find(|repo| repo.manages(&self.abs_path.join(path)))
-            .map(|repo| repo.clone())
+            .map(|repo| repo.boxed_clone())
     }
 
-    pub(crate) fn in_dot_git(&mut self, path: &Path) -> Option<&mut GitRepository> {
-        println!("chechking {path:?}");
+    pub(crate) fn in_dot_git(&mut self, path: &Path) -> Option<&mut Box<dyn GitRepository>> {
         self.git_repositories
             .iter_mut()
             .rev() //git_repository is ordered lexicographically
             .find(|repo| repo.in_dot_git(&self.abs_path.join(path)))
     }
 
-    pub(crate) fn tracks_filepath(&self, repo: &GitRepository, file_path: &Path) -> bool {
+    pub(crate) fn _tracks_filepath(&self, repo: &dyn GitRepository, file_path: &Path) -> bool {
         // Depends on git_repository_for_file_path returning the most specific git repository for a given path
         self.repo_for(&self.abs_path.join(file_path))
             .map_or(false, |r| r.git_dir_path() == repo.git_dir_path())
@@ -1522,6 +1518,7 @@ impl LocalSnapshot {
         parent_path: Arc<Path>,
         entries: impl IntoIterator<Item = Entry>,
         ignore: Option<Arc<Gitignore>>,
+        fs: &dyn Fs,
     ) {
         let mut parent_entry = if let Some(parent_entry) =
             self.entries_by_path.get(&PathKey(parent_path.clone()), &())
@@ -1553,7 +1550,7 @@ impl LocalSnapshot {
                 .git_repositories
                 .binary_search_by_key(&abs_path.as_path(), |repo| repo.git_dir_path())
             {
-                if let Some(repository) = GitRepository::open(&abs_path) {
+                if let Some(repository) = fs.open_repo(abs_path.as_path()) {
                     self.git_repositories.insert(ix, repository);
                 }
             }
@@ -2402,9 +2399,12 @@ impl BackgroundScanner {
             new_entries.push(child_entry);
         }
 
-        self.snapshot
-            .lock()
-            .populate_dir(job.path.clone(), new_entries, new_ignore);
+        self.snapshot.lock().populate_dir(
+            job.path.clone(),
+            new_entries,
+            new_ignore,
+            self.fs.as_ref(),
+        );
         for new_job in new_jobs {
             job.scan_queue.send(new_job).await.unwrap();
         }
@@ -2602,7 +2602,7 @@ impl BackgroundScanner {
         let new_repos = snapshot
             .git_repositories
             .iter()
-            .cloned()
+            .map(|repo| repo.boxed_clone())
             .filter(|repo| git::libgit::Repository::open(repo.git_dir_path()).is_ok())
             .collect();