git: Don't consider $HOME as containing git repository unless it's opened directly (#25948)
When a worktree is created, we walk up the ancestors of the root path trying to find a git repository. In particular, if your `$HOME` is a git repository and you open some subdirectory of `$HOME` that's *not* a git repository, we end up scanning `$HOME` and everything under it looking for changed and untracked files, which is often pretty slow. Consistency here is not very useful and leads to a bad experience. This PR adds a special case to not consider `$HOME` as a containing git repository, unless you ask for it by doing the equivalent of `zed ~`. Release Notes: - Changed the behavior of git features to not treat `$HOME` as a git repository unless opened directly
This commit is contained in:
parent
9e2b7bc5dc
commit
dc3158c8ce
3 changed files with 87 additions and 1 deletions
|
@ -135,6 +135,7 @@ pub trait Fs: Send + Sync {
|
||||||
Arc<dyn Watcher>,
|
Arc<dyn Watcher>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
fn home_dir(&self) -> Option<PathBuf>;
|
||||||
fn open_repo(&self, abs_dot_git: &Path) -> Option<Arc<dyn GitRepository>>;
|
fn open_repo(&self, abs_dot_git: &Path) -> Option<Arc<dyn GitRepository>>;
|
||||||
fn is_fake(&self) -> bool;
|
fn is_fake(&self) -> bool;
|
||||||
async fn is_case_sensitive(&self) -> Result<bool>;
|
async fn is_case_sensitive(&self) -> Result<bool>;
|
||||||
|
@ -813,6 +814,10 @@ impl Fs for RealFs {
|
||||||
temp_dir.close()?;
|
temp_dir.close()?;
|
||||||
case_sensitive
|
case_sensitive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn home_dir(&self) -> Option<PathBuf> {
|
||||||
|
Some(paths::home_dir().clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
|
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
|
||||||
|
@ -846,6 +851,7 @@ struct FakeFsState {
|
||||||
metadata_call_count: usize,
|
metadata_call_count: usize,
|
||||||
read_dir_call_count: usize,
|
read_dir_call_count: usize,
|
||||||
moves: std::collections::HashMap<u64, PathBuf>,
|
moves: std::collections::HashMap<u64, PathBuf>,
|
||||||
|
home_dir: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
@ -1031,6 +1037,7 @@ impl FakeFs {
|
||||||
read_dir_call_count: 0,
|
read_dir_call_count: 0,
|
||||||
metadata_call_count: 0,
|
metadata_call_count: 0,
|
||||||
moves: Default::default(),
|
moves: Default::default(),
|
||||||
|
home_dir: None,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1524,6 +1531,10 @@ impl FakeFs {
|
||||||
fn simulate_random_delay(&self) -> impl futures::Future<Output = ()> {
|
fn simulate_random_delay(&self) -> impl futures::Future<Output = ()> {
|
||||||
self.executor.simulate_random_delay()
|
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"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
@ -2079,6 +2090,10 @@ impl Fs for FakeFs {
|
||||||
fn as_fake(&self) -> Arc<FakeFs> {
|
fn as_fake(&self) -> Arc<FakeFs> {
|
||||||
self.this.upgrade().unwrap()
|
self.this.upgrade().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn home_dir(&self) -> Option<PathBuf> {
|
||||||
|
self.state.lock().home_dir.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn chunks(rope: &Rope, line_ending: LineEnding) -> impl Iterator<Item = &str> {
|
fn chunks(rope: &Rope, line_ending: LineEnding) -> impl Iterator<Item = &str> {
|
||||||
|
|
|
@ -4292,7 +4292,11 @@ impl BackgroundScanner {
|
||||||
let mut containing_git_repository = None;
|
let mut containing_git_repository = None;
|
||||||
for (index, ancestor) in root_abs_path.as_path().ancestors().enumerate() {
|
for (index, ancestor) in root_abs_path.as_path().ancestors().enumerate() {
|
||||||
if index != 0 {
|
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
|
build_gitignore(&ancestor.join(*GITIGNORE), self.fs.as_ref()).await
|
||||||
{
|
{
|
||||||
self.state
|
self.state
|
||||||
|
|
|
@ -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]
|
#[gpui::test]
|
||||||
async fn test_git_repository_for_path(cx: &mut TestAppContext) {
|
async fn test_git_repository_for_path(cx: &mut TestAppContext) {
|
||||||
init_test(cx);
|
init_test(cx);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue