From 2d43ad12e6358c9a9b4c67bec201a6e13c09894e Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Thu, 5 Dec 2024 18:55:40 +0100 Subject: [PATCH] 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 --- crates/worktree/src/worktree.rs | 33 ++++++++++++++++++++++----- crates/worktree/src/worktree_tests.rs | 22 ++++++++++++++---- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index a9762b942b6ee61cd984ac351e79c29d82ac173c..86981687ce2a1937bf98d5283a91bbb48b32d2f8 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -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 = 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> { + 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 { 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(); diff --git a/crates/worktree/src/worktree_tests.rs b/crates/worktree/src/worktree_tests.rs index fbedd896e319e7d6b182d8660c3c24cc36770103..121caf0b7b2fc1bc1798d4b423681bea8ca13c8a 100644 --- a/crates/worktree/src/worktree_tests.rs +++ b/crates/worktree/src/worktree_tests.rs @@ -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 ); }