git: Make worktrees work for bare git repositories (#21596)

Fixes #21210 by ensuring that Zed can open worktrees of bare git repositories.

Co-authored-by: Peter Tripp <peter@zed.dev>
This commit is contained in:
Thorsten Ball 2024-12-05 18:55:40 +01:00 committed by GitHub
parent 6ebd6c2893
commit 2d43ad12e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 44 additions and 11 deletions

View file

@ -3110,12 +3110,8 @@ impl BackgroundScannerState {
let repository = fs.open_repo(&dot_git_abs_path)?;
let actual_repo_path = repository.path();
let actual_dot_git_dir_abs_path: Arc<Path> = Arc::from(
actual_repo_path
.ancestors()
.find(|ancestor| ancestor.file_name() == Some(&*DOT_GIT))?,
);
let actual_dot_git_dir_abs_path = smol::block_on(find_git_dir(&actual_repo_path, fs))?;
watcher.add(&actual_repo_path).log_err()?;
let dot_git_worktree_abs_path = if actual_dot_git_dir_abs_path.as_ref() == dot_git_abs_path
@ -3161,6 +3157,31 @@ impl BackgroundScannerState {
}
}
async fn is_git_dir(path: &Path, fs: &dyn Fs) -> bool {
if path.file_name() == Some(&*DOT_GIT) {
return true;
}
// If we're in a bare repository, we are not inside a `.git` folder. In a
// bare repository, the root folder contains what would normally be in the
// `.git` folder.
let head_metadata = fs.metadata(&path.join("HEAD")).await;
if !matches!(head_metadata, Ok(Some(_))) {
return false;
}
let config_metadata = fs.metadata(&path.join("config")).await;
matches!(config_metadata, Ok(Some(_)))
}
async fn find_git_dir(path: &Path, fs: &dyn Fs) -> Option<Arc<Path>> {
for ancestor in path.ancestors() {
if is_git_dir(ancestor, fs).await {
return Some(Arc::from(ancestor));
}
}
None
}
async fn build_gitignore(abs_path: &Path, fs: &dyn Fs) -> Result<Gitignore> {
let contents = fs.load(abs_path).await?;
let parent = abs_path.parent().unwrap_or_else(|| Path::new("/"));
@ -3967,7 +3988,7 @@ impl BackgroundScanner {
} else if fsmonitor_parse_state == Some(FsMonitorParseState::Cookies) && file_name == Some(*FSMONITOR_DAEMON) {
fsmonitor_parse_state = Some(FsMonitorParseState::FsMonitor);
false
} else if fsmonitor_parse_state != Some(FsMonitorParseState::FsMonitor) && file_name == Some(*DOT_GIT) {
} else if fsmonitor_parse_state != Some(FsMonitorParseState::FsMonitor) && smol::block_on(is_git_dir(ancestor, self.fs.as_ref())) {
true
} else {
fsmonitor_parse_state.take();

View file

@ -12,7 +12,13 @@ use pretty_assertions::assert_eq;
use rand::prelude::*;
use serde_json::json;
use settings::{Settings, SettingsStore};
use std::{env, fmt::Write, mem, path::Path, sync::Arc};
use std::{
env,
fmt::Write,
mem,
path::{Path, PathBuf},
sync::Arc,
};
use util::{test::temp_tree, ResultExt};
#[gpui::test]
@ -532,14 +538,20 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) {
assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 1);
});
let path = PathBuf::from("/root/one/node_modules/c/lib");
// No work happens when files and directories change within an unloaded directory.
let prev_fs_call_count = fs.read_dir_call_count() + fs.metadata_call_count();
fs.create_dir("/root/one/node_modules/c/lib".as_ref())
.await
.unwrap();
// When we open a directory, we check each ancestor whether it's a git
// repository. That means we have an fs.metadata call per ancestor that we
// need to subtract here.
let ancestors = path.ancestors().count();
fs.create_dir(path.as_ref()).await.unwrap();
cx.executor().run_until_parked();
assert_eq!(
fs.read_dir_call_count() + fs.metadata_call_count() - prev_fs_call_count,
fs.read_dir_call_count() + fs.metadata_call_count() - prev_fs_call_count - ancestors,
0
);
}