diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 9af1d92dea..b38521756a 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -135,6 +135,7 @@ pub trait Fs: Send + Sync { Arc, ); + fn home_dir(&self) -> Option; fn open_repo(&self, abs_dot_git: &Path) -> Option>; fn is_fake(&self) -> bool; async fn is_case_sensitive(&self) -> Result; @@ -813,6 +814,10 @@ impl Fs for RealFs { temp_dir.close()?; case_sensitive } + + fn home_dir(&self) -> Option { + Some(paths::home_dir().clone()) + } } #[cfg(not(any(target_os = "linux", target_os = "freebsd")))] @@ -846,6 +851,7 @@ struct FakeFsState { metadata_call_count: usize, read_dir_call_count: usize, moves: std::collections::HashMap, + home_dir: Option, } #[cfg(any(test, feature = "test-support"))] @@ -1031,6 +1037,7 @@ impl FakeFs { read_dir_call_count: 0, metadata_call_count: 0, moves: Default::default(), + home_dir: None, }), }); @@ -1524,6 +1531,10 @@ impl FakeFs { fn simulate_random_delay(&self) -> impl futures::Future { self.executor.simulate_random_delay() } + + pub fn set_home_dir(&self, home_dir: PathBuf) { + self.state.lock().home_dir = Some(home_dir); + } } #[cfg(any(test, feature = "test-support"))] @@ -2079,6 +2090,10 @@ impl Fs for FakeFs { fn as_fake(&self) -> Arc { self.this.upgrade().unwrap() } + + fn home_dir(&self) -> Option { + self.state.lock().home_dir.clone() + } } fn chunks(rope: &Rope, line_ending: LineEnding) -> impl Iterator { diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index 0d2ec99e9c..647d783075 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -4292,7 +4292,11 @@ impl BackgroundScanner { let mut containing_git_repository = None; for (index, ancestor) in root_abs_path.as_path().ancestors().enumerate() { if index != 0 { - if let Ok(ignore) = + if Some(ancestor) == self.fs.home_dir().as_deref() { + // Unless $HOME is itself the worktree root, don't consider it as a + // containing git repository---expensive and likely unwanted. + break; + } else if let Ok(ignore) = build_gitignore(&ancestor.join(*GITIGNORE), self.fs.as_ref()).await { self.state diff --git a/crates/worktree/src/worktree_tests.rs b/crates/worktree/src/worktree_tests.rs index cd2c2e051d..d889cc71cb 100644 --- a/crates/worktree/src/worktree_tests.rs +++ b/crates/worktree/src/worktree_tests.rs @@ -2241,6 +2241,73 @@ async fn test_rename_work_directory(cx: &mut TestAppContext) { }); } +#[gpui::test] +async fn test_home_dir_as_git_repository(cx: &mut TestAppContext) { + init_test(cx); + cx.executor().allow_parking(); + let fs = FakeFs::new(cx.background_executor.clone()); + fs.insert_tree( + "/root", + json!({ + "home": { + ".git": {}, + "project": { + "a.txt": "A" + }, + }, + }), + ) + .await; + fs.set_home_dir(Path::new(path!("/root/home")).to_owned()); + + let tree = Worktree::local( + Path::new(path!("/root/home/project")), + true, + fs.clone(), + Default::default(), + &mut cx.to_async(), + ) + .await + .unwrap(); + + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + tree.flush_fs_events(cx).await; + + tree.read_with(cx, |tree, _cx| { + let tree = tree.as_local().unwrap(); + + let repo = tree.repository_for_path(path!("a.txt").as_ref()); + assert!(repo.is_none()); + }); + + let home_tree = Worktree::local( + Path::new(path!("/root/home")), + true, + fs.clone(), + Default::default(), + &mut cx.to_async(), + ) + .await + .unwrap(); + + cx.read(|cx| home_tree.read(cx).as_local().unwrap().scan_complete()) + .await; + home_tree.flush_fs_events(cx).await; + + home_tree.read_with(cx, |home_tree, _cx| { + let home_tree = home_tree.as_local().unwrap(); + + let repo = home_tree.repository_for_path(path!("project/a.txt").as_ref()); + assert_eq!( + repo.map(|repo| &repo.work_directory), + Some(&WorkDirectory::InProject { + relative_path: Path::new("").into() + }) + ); + }) +} + #[gpui::test] async fn test_git_repository_for_path(cx: &mut TestAppContext) { init_test(cx);