From 7aaaeb8f3e5c7032e3d138aab67a230ba00586fa Mon Sep 17 00:00:00 2001 From: omchillure Date: Tue, 28 Apr 2026 09:48:36 +0530 Subject: [PATCH 1/3] Fix git worktree popup popup no worktree when opened in a project --- crates/git/src/repository.rs | 134 ++++++++++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 3 deletions(-) diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index c436ffbf6dfb8d..3e907d59f0a600 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -378,7 +378,9 @@ pub fn parse_worktrees_from_str>(raw_worktrees: T) -> Vec Result { + let repository = self.repository.lock(); + let working_directory = repository + .workdir() + .unwrap_or_else(|| repository.path()) + .to_path_buf(); + Ok(GitBinary::new( + self.any_git_binary_path.clone(), + working_directory, + repository.path().to_path_buf(), + self.executor.clone(), + self.is_trusted(), + )) + } + fn edit_ref(&self, edit: RefEdit) -> BoxFuture<'_, Result<()>> { let git_binary = self.git_binary(); self.executor @@ -1830,7 +1847,7 @@ impl GitRepository for RealGitRepository { } fn worktrees(&self) -> BoxFuture<'_, Result>> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_for_worktree_list(); self.executor .spawn(async move { let git = git_binary?; @@ -3662,7 +3679,7 @@ fn checkpoint_author_envs() -> HashMap { #[cfg(test)] mod tests { - use std::fs; + use std::{fs, process::Command}; use super::*; use gpui::TestAppContext; @@ -3674,6 +3691,21 @@ mod tests { } } + fn run_git(cwd: &Path, args: &[&str]) { + let output = Command::new("git") + .current_dir(cwd) + .args(args) + .output() + .expect("git command should run"); + assert!( + output.status.success(), + "git {:?} failed\nstdout: {}\nstderr: {}", + args, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + } + #[gpui::test] async fn test_build_command_untrusted_includes_both_safety_args(cx: &mut TestAppContext) { cx.executor().allow_parking(); @@ -4201,6 +4233,19 @@ mod tests { assert!(!result[1].is_main); assert!(!result[1].is_bare); + // Bare repo entry without HEAD (as emitted by `git worktree list --porcelain`) + let input = "worktree /home/user/bare.git\nbare\n\n\ + worktree /home/user/project\nHEAD def456\nbranch refs/heads/main\n\n"; + let result = parse_worktrees_from_str(input); + assert_eq!(result.len(), 2); + assert_eq!(result[0].path, PathBuf::from("/home/user/bare.git")); + assert!(result[0].is_main); + assert!(result[0].is_bare); + assert_eq!(result[1].path, PathBuf::from("/home/user/project")); + assert_eq!(result[1].ref_name, Some("refs/heads/main".into())); + assert!(!result[1].is_main); + assert!(!result[1].is_bare); + // Extra porcelain lines (locked, prunable) should be ignored let input = "worktree /home/user/project\nHEAD abc123\nbranch refs/heads/main\n\n\ worktree /home/user/locked-wt\nHEAD def456\nbranch refs/heads/locked-branch\nlocked\n\n\ @@ -4314,6 +4359,89 @@ mod tests { ); } + #[gpui::test] + async fn test_list_worktrees_from_bare_repository_gitfile(cx: &mut TestAppContext) { + disable_git_global_config(); + cx.executor().allow_parking(); + + let temp_dir = tempfile::tempdir().unwrap(); + let seed_dir = temp_dir.path().join("seed"); + let repo_root = temp_dir.path().join("repo-root"); + let bare_dir = repo_root.join(".bare"); + fs::create_dir_all(&repo_root).unwrap(); + + run_git(temp_dir.path(), &["init", "-b", "main", "seed"]); + fs::write(seed_dir.join("README.md"), "content").unwrap(); + run_git(&seed_dir, &["add", "README.md"]); + run_git( + &seed_dir, + &[ + "-c", + "user.email=test@example.com", + "-c", + "user.name=Test", + "commit", + "-m", + "Initial commit", + ], + ); + + run_git( + &repo_root, + &[ + "clone", + "--bare", + seed_dir.to_str().unwrap(), + bare_dir.to_str().unwrap(), + ], + ); + fs::write(repo_root.join(".git"), "gitdir: ./.bare\n").unwrap(); + + for branch in ["feature-a", "feature-b"] { + run_git( + &repo_root, + &["--git-dir=.bare", "branch", branch, "main"], + ); + } + for name in ["main", "feature-a", "feature-b"] { + run_git( + &repo_root, + &["--git-dir=.bare", "worktree", "add", name, name], + ); + } + + let repo = RealGitRepository::new( + &repo_root.join(".git"), + None, + Some("git".into()), + cx.executor(), + ) + .unwrap(); + + let worktrees = repo.worktrees().await.unwrap(); + assert_eq!(worktrees.len(), 4); + + let bare = worktrees + .iter() + .find(|w| w.is_bare) + .expect("bare worktree entry"); + assert!(bare.is_main); + assert_eq!(bare.path, bare_dir); + + for name in ["main", "feature-a", "feature-b"] { + let worktree = worktrees + .iter() + .find(|w| w.path == repo_root.join(name)) + .unwrap_or_else(|| panic!("missing worktree {name}")); + assert_eq!( + worktree.ref_name.as_ref().map(|r| r.as_ref()), + Some(format!("refs/heads/{name}").as_str()) + ); + assert!(!worktree.is_main); + assert!(!worktree.is_bare); + } + } + #[gpui::test] async fn test_remove_worktree(cx: &mut TestAppContext) { disable_git_global_config(); From e76ec8e3da55977273af107cc54a47a687492827 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 4 May 2026 17:47:29 -0700 Subject: [PATCH 2/3] Enable many git commands to be run in bare repo --- crates/git/src/repository.rs | 243 ++++++++--------------------------- 1 file changed, 57 insertions(+), 186 deletions(-) diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index 3e907d59f0a600..726508f68e94e6 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -378,9 +378,7 @@ pub fn parse_worktrees_from_str>(raw_worktrees: T) -> Vec Result { + fn git_binary_in_worktree(&self) -> Result { Ok(GitBinary::new( self.any_git_binary_path.clone(), self.working_directory() @@ -1118,27 +1116,27 @@ impl RealGitRepository { )) } - fn git_binary_for_worktree_list(&self) -> Result { + fn git_binary(&self) -> GitBinary { let repository = self.repository.lock(); let working_directory = repository .workdir() .unwrap_or_else(|| repository.path()) .to_path_buf(); - Ok(GitBinary::new( + GitBinary::new( self.any_git_binary_path.clone(), working_directory, repository.path().to_path_buf(), self.executor.clone(), self.is_trusted(), - )) + ) } fn edit_ref(&self, edit: RefEdit) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git = self.git_binary(); self.executor .spawn(async move { let args = edit.into_args(); - git_binary?.run(&args).await?; + git.run(&args).await?; Ok(()) }) .boxed() @@ -1148,10 +1146,10 @@ impl RealGitRepository { if let Some(output) = self.any_git_binary_help_output.lock().clone() { return output; } - let git_binary = self.git_binary(); + let git = self.git_binary(); let output: SharedString = self .executor - .spawn(async move { git_binary?.run(&["help", "-a"]).await }) + .spawn(async move { git.run(&["help", "-a"]).await }) .await .unwrap_or_default() .into(); @@ -1235,10 +1233,9 @@ impl GitRepository for RealGitRepository { } fn show(&self, commit: String) -> BoxFuture<'_, Result> { - let git_binary = self.git_binary(); + let git = self.git_binary(); self.executor .spawn(async move { - let git = git_binary?; let output = git .build_command(&[ "show", @@ -1273,9 +1270,8 @@ impl GitRepository for RealGitRepository { if self.repository.lock().workdir().is_none() { return future::ready(Err(anyhow!("no working directory"))).boxed(); } - let git_binary = self.git_binary(); + let git = self.git_binary(); cx.background_spawn(async move { - let git = git_binary?; let show_output = git .build_command(&[ "show", @@ -1405,7 +1401,7 @@ impl GitRepository for RealGitRepository { mode: ResetMode, env: Arc>, ) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); async move { let mode_flag = match mode { ResetMode::Mixed => "--mixed", @@ -1434,7 +1430,7 @@ impl GitRepository for RealGitRepository { paths: Vec, env: Arc>, ) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); async move { if paths.is_empty() { return Ok(()); @@ -1590,10 +1586,9 @@ impl GitRepository for RealGitRepository { env: Arc>, is_executable: bool, ) -> BoxFuture<'_, anyhow::Result<()>> { - let git_binary = self.git_binary(); + let git = self.git_binary(); self.executor .spawn(async move { - let git = git_binary?; let mode = if is_executable { "100755" } else { "100644" }; if let Some(content) = content { @@ -1657,10 +1652,9 @@ impl GitRepository for RealGitRepository { } fn revparse_batch(&self, revs: Vec) -> BoxFuture<'_, Result>>> { - let git_binary = self.git_binary(); + let git = self.git_binary(); self.executor .spawn(async move { - let git = git_binary?; let mut process = git .build_command(&["cat-file", "--batch-check=%(objectname)"]) .stdin(Stdio::piped()) @@ -1711,7 +1705,7 @@ impl GitRepository for RealGitRepository { } fn status(&self, path_prefixes: &[RepoPath]) -> Task> { - let git = match self.git_binary() { + let git = match self.git_binary_in_worktree() { Ok(git) => git, Err(e) => return Task::ready(Err(e)), }; @@ -1730,7 +1724,7 @@ impl GitRepository for RealGitRepository { } fn diff_tree(&self, request: DiffTreeType) -> BoxFuture<'_, Result> { - let git = match self.git_binary() { + let git = match self.git_binary_in_worktree() { Ok(git) => git, Err(e) => return Task::ready(Err(e)).boxed(), }; @@ -1768,7 +1762,7 @@ impl GitRepository for RealGitRepository { } fn stash_entries(&self) -> BoxFuture<'_, Result> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); self.executor .spawn(async move { let git = git_binary?; @@ -1788,7 +1782,7 @@ impl GitRepository for RealGitRepository { } fn branches(&self) -> BoxFuture<'_, Result>> { - let git_binary = self.git_binary(); + let git = self.git_binary(); self.executor .spawn(async move { let fields = [ @@ -1810,7 +1804,6 @@ impl GitRepository for RealGitRepository { "--format", &fields, ]; - let git = git_binary?; let output = git.build_command(&args).output().await?; anyhow::ensure!( @@ -1847,10 +1840,9 @@ impl GitRepository for RealGitRepository { } fn worktrees(&self) -> BoxFuture<'_, Result>> { - let git_binary = self.git_binary_for_worktree_list(); + let git = self.git_binary(); self.executor .spawn(async move { - let git = git_binary?; let output = git .build_command(&["worktree", "list", "--porcelain"]) .output() @@ -1871,7 +1863,7 @@ impl GitRepository for RealGitRepository { target: CreateWorktreeTarget, path: PathBuf, ) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git = self.git_binary(); let mut args = vec![OsString::from("worktree"), OsString::from("add")]; match &target { @@ -1903,7 +1895,6 @@ impl GitRepository for RealGitRepository { self.executor .spawn(async move { std::fs::create_dir_all(path.parent().unwrap_or(&path))?; - let git = git_binary?; let output = git.build_command(&args).output().await?; if output.status.success() { Ok(()) @@ -1916,7 +1907,7 @@ impl GitRepository for RealGitRepository { } fn remove_worktree(&self, path: PathBuf, force: bool) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git = self.git_binary(); self.executor .spawn(async move { @@ -1926,14 +1917,14 @@ impl GitRepository for RealGitRepository { } args.push("--".into()); args.push(path.as_os_str().into()); - git_binary?.run(&args).await?; + git.run(&args).await?; anyhow::Ok(()) }) .boxed() } fn rename_worktree(&self, old_path: PathBuf, new_path: PathBuf) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git = self.git_binary(); self.executor .spawn(async move { @@ -1944,7 +1935,7 @@ impl GitRepository for RealGitRepository { old_path.as_os_str().into(), new_path.as_os_str().into(), ]; - git_binary?.run(&args).await?; + git.run(&args).await?; anyhow::Ok(()) }) .boxed() @@ -1978,7 +1969,7 @@ impl GitRepository for RealGitRepository { fn change_branch(&self, name: String) -> BoxFuture<'_, Result<()>> { let repo = self.repository.clone(); - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); let branch = self.executor.spawn(async move { let repo = repo.lock(); let branch = if let Ok(branch) = repo.find_branch(&name, BranchType::Local) { @@ -2024,7 +2015,7 @@ impl GitRepository for RealGitRepository { name: String, base_branch: Option, ) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); self.executor .spawn(async move { @@ -2042,7 +2033,7 @@ impl GitRepository for RealGitRepository { } fn rename_branch(&self, branch: String, new_name: String) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); self.executor .spawn(async move { @@ -2055,7 +2046,7 @@ impl GitRepository for RealGitRepository { } fn delete_branch(&self, is_remote: bool, name: String) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); self.executor .spawn(async move { @@ -2073,7 +2064,7 @@ impl GitRepository for RealGitRepository { content: Rope, line_ending: LineEnding, ) -> BoxFuture<'_, Result> { - let git = self.git_binary(); + let git = self.git_binary_in_worktree(); self.executor .spawn(async move { @@ -2083,7 +2074,7 @@ impl GitRepository for RealGitRepository { } fn diff(&self, diff: DiffType) -> BoxFuture<'_, Result> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); self.executor .spawn(async move { let git = git_binary?; @@ -2114,7 +2105,7 @@ impl GitRepository for RealGitRepository { path_prefixes: &[RepoPath], ) -> BoxFuture<'_, Result> { let path_prefixes = path_prefixes.to_vec(); - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); self.executor .spawn(async move { @@ -2144,7 +2135,7 @@ impl GitRepository for RealGitRepository { paths: Vec, env: Arc>, ) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); self.executor .spawn(async move { if !paths.is_empty() { @@ -2171,7 +2162,7 @@ impl GitRepository for RealGitRepository { paths: Vec, env: Arc>, ) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); self.executor .spawn(async move { @@ -2200,7 +2191,7 @@ impl GitRepository for RealGitRepository { paths: Vec, env: Arc>, ) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); self.executor .spawn(async move { let git = git_binary?; @@ -2226,7 +2217,7 @@ impl GitRepository for RealGitRepository { index: Option, env: Arc>, ) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); self.executor .spawn(async move { let git = git_binary?; @@ -2251,7 +2242,7 @@ impl GitRepository for RealGitRepository { index: Option, env: Arc>, ) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); self.executor .spawn(async move { let git = git_binary?; @@ -2276,7 +2267,7 @@ impl GitRepository for RealGitRepository { index: Option, env: Arc>, ) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); self.executor .spawn(async move { let git = git_binary?; @@ -2304,7 +2295,7 @@ impl GitRepository for RealGitRepository { ask_pass: AskPassDelegate, env: Arc>, ) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); let executor = self.executor.clone(); // Note: Do not spawn this command on the background thread, it might pop open the credential helper // which we want to block on. @@ -2350,11 +2341,11 @@ impl GitRepository for RealGitRepository { } fn repair_worktrees(&self) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git = self.git_binary(); self.executor .spawn(async move { let args: Vec = vec!["worktree".into(), "repair".into()]; - git_binary?.run(&args).await?; + git.run(&args).await?; Ok(()) }) .boxed() @@ -2486,10 +2477,9 @@ impl GitRepository for RealGitRepository { } fn get_push_remote(&self, branch: String) -> BoxFuture<'_, Result>> { - let git_binary = self.git_binary(); + let git = self.git_binary(); self.executor .spawn(async move { - let git = git_binary?; let output = git .build_command(&["rev-parse", "--abbrev-ref"]) .arg(format!("{branch}@{{push}}")) @@ -2511,10 +2501,9 @@ impl GitRepository for RealGitRepository { } fn get_branch_remote(&self, branch: String) -> BoxFuture<'_, Result>> { - let git_binary = self.git_binary(); + let git = self.git_binary(); self.executor .spawn(async move { - let git = git_binary?; let output = git .build_command(&["config", "--get"]) .arg(format!("branch.{branch}.remote")) @@ -2533,10 +2522,9 @@ impl GitRepository for RealGitRepository { } fn get_all_remotes(&self) -> BoxFuture<'_, Result>> { - let git_binary = self.git_binary(); + let git = self.git_binary(); self.executor .spawn(async move { - let git = git_binary?; let output = git.build_command(&["remote", "-v"]).output().await?; anyhow::ensure!( @@ -2586,7 +2574,7 @@ impl GitRepository for RealGitRepository { } fn check_for_pushed_commit(&self) -> BoxFuture<'_, Result>> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); self.executor .spawn(async move { let git = git_binary?; @@ -2640,7 +2628,7 @@ impl GitRepository for RealGitRepository { } fn checkpoint(&self) -> BoxFuture<'static, Result> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); self.executor .spawn(async move { let mut git = git_binary?.envs(checkpoint_author_envs()); @@ -2669,7 +2657,7 @@ impl GitRepository for RealGitRepository { } fn restore_checkpoint(&self, checkpoint: GitRepositoryCheckpoint) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); self.executor .spawn(async move { let git = git_binary?; @@ -2699,7 +2687,7 @@ impl GitRepository for RealGitRepository { } fn create_archive_checkpoint(&self) -> BoxFuture<'_, Result<(String, String)>> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); self.executor .spawn(async move { let mut git = git_binary?.envs(checkpoint_author_envs()); @@ -2757,7 +2745,7 @@ impl GitRepository for RealGitRepository { staged_sha: String, unstaged_sha: String, ) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); self.executor .spawn(async move { let git = git_binary?; @@ -2787,7 +2775,7 @@ impl GitRepository for RealGitRepository { left: GitRepositoryCheckpoint, right: GitRepositoryCheckpoint, ) -> BoxFuture<'_, Result> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); self.executor .spawn(async move { let git = git_binary?; @@ -2821,7 +2809,7 @@ impl GitRepository for RealGitRepository { base_checkpoint: GitRepositoryCheckpoint, target_checkpoint: GitRepositoryCheckpoint, ) -> BoxFuture<'_, Result> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); self.executor .spawn(async move { let git = git_binary?; @@ -2841,11 +2829,9 @@ impl GitRepository for RealGitRepository { &self, include_remote_name: bool, ) -> BoxFuture<'_, Result>> { - let git_binary = self.git_binary(); + let git = self.git_binary(); self.executor .spawn(async move { - let git = git_binary?; - let strip_prefix = if include_remote_name { "refs/remotes/" } else { @@ -2894,7 +2880,7 @@ impl GitRepository for RealGitRepository { hook: RunHook, env: Arc>, ) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); let repository = self.repository.clone(); let help_output = self.any_git_binary_help_output(); @@ -2947,11 +2933,9 @@ impl GitRepository for RealGitRepository { log_order: LogOrder, request_tx: Sender>>, ) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git = self.git_binary(); async move { - let git = git_binary?; - let mut git_log_command = vec![ "log", GRAPH_COMMIT_FORMAT, @@ -3029,11 +3013,9 @@ impl GitRepository for RealGitRepository { search_args: SearchCommitArgs, request_tx: Sender, ) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git = self.git_binary(); async move { - let git = git_binary?; - let mut args = vec!["log", SEARCH_COMMIT_FORMAT, log_source.get_arg()?]; args.push("--fixed-strings"); @@ -3083,7 +3065,7 @@ impl GitRepository for RealGitRepository { } fn commit_data_reader(&self) -> Result { - let git_binary = self.git_binary()?; + let git_binary = self.git_binary(); let (request_tx, request_rx) = async_channel::bounded::(64); @@ -3679,7 +3661,7 @@ fn checkpoint_author_envs() -> HashMap { #[cfg(test)] mod tests { - use std::{fs, process::Command}; + use std::fs; use super::*; use gpui::TestAppContext; @@ -3691,21 +3673,6 @@ mod tests { } } - fn run_git(cwd: &Path, args: &[&str]) { - let output = Command::new("git") - .current_dir(cwd) - .args(args) - .output() - .expect("git command should run"); - assert!( - output.status.success(), - "git {:?} failed\nstdout: {}\nstderr: {}", - args, - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - ); - } - #[gpui::test] async fn test_build_command_untrusted_includes_both_safety_args(cx: &mut TestAppContext) { cx.executor().allow_parking(); @@ -4233,19 +4200,6 @@ mod tests { assert!(!result[1].is_main); assert!(!result[1].is_bare); - // Bare repo entry without HEAD (as emitted by `git worktree list --porcelain`) - let input = "worktree /home/user/bare.git\nbare\n\n\ - worktree /home/user/project\nHEAD def456\nbranch refs/heads/main\n\n"; - let result = parse_worktrees_from_str(input); - assert_eq!(result.len(), 2); - assert_eq!(result[0].path, PathBuf::from("/home/user/bare.git")); - assert!(result[0].is_main); - assert!(result[0].is_bare); - assert_eq!(result[1].path, PathBuf::from("/home/user/project")); - assert_eq!(result[1].ref_name, Some("refs/heads/main".into())); - assert!(!result[1].is_main); - assert!(!result[1].is_bare); - // Extra porcelain lines (locked, prunable) should be ignored let input = "worktree /home/user/project\nHEAD abc123\nbranch refs/heads/main\n\n\ worktree /home/user/locked-wt\nHEAD def456\nbranch refs/heads/locked-branch\nlocked\n\n\ @@ -4359,89 +4313,6 @@ mod tests { ); } - #[gpui::test] - async fn test_list_worktrees_from_bare_repository_gitfile(cx: &mut TestAppContext) { - disable_git_global_config(); - cx.executor().allow_parking(); - - let temp_dir = tempfile::tempdir().unwrap(); - let seed_dir = temp_dir.path().join("seed"); - let repo_root = temp_dir.path().join("repo-root"); - let bare_dir = repo_root.join(".bare"); - fs::create_dir_all(&repo_root).unwrap(); - - run_git(temp_dir.path(), &["init", "-b", "main", "seed"]); - fs::write(seed_dir.join("README.md"), "content").unwrap(); - run_git(&seed_dir, &["add", "README.md"]); - run_git( - &seed_dir, - &[ - "-c", - "user.email=test@example.com", - "-c", - "user.name=Test", - "commit", - "-m", - "Initial commit", - ], - ); - - run_git( - &repo_root, - &[ - "clone", - "--bare", - seed_dir.to_str().unwrap(), - bare_dir.to_str().unwrap(), - ], - ); - fs::write(repo_root.join(".git"), "gitdir: ./.bare\n").unwrap(); - - for branch in ["feature-a", "feature-b"] { - run_git( - &repo_root, - &["--git-dir=.bare", "branch", branch, "main"], - ); - } - for name in ["main", "feature-a", "feature-b"] { - run_git( - &repo_root, - &["--git-dir=.bare", "worktree", "add", name, name], - ); - } - - let repo = RealGitRepository::new( - &repo_root.join(".git"), - None, - Some("git".into()), - cx.executor(), - ) - .unwrap(); - - let worktrees = repo.worktrees().await.unwrap(); - assert_eq!(worktrees.len(), 4); - - let bare = worktrees - .iter() - .find(|w| w.is_bare) - .expect("bare worktree entry"); - assert!(bare.is_main); - assert_eq!(bare.path, bare_dir); - - for name in ["main", "feature-a", "feature-b"] { - let worktree = worktrees - .iter() - .find(|w| w.path == repo_root.join(name)) - .unwrap_or_else(|| panic!("missing worktree {name}")); - assert_eq!( - worktree.ref_name.as_ref().map(|r| r.as_ref()), - Some(format!("refs/heads/{name}").as_str()) - ); - assert!(!worktree.is_main); - assert!(!worktree.is_bare); - } - } - #[gpui::test] async fn test_remove_worktree(cx: &mut TestAppContext) { disable_git_global_config(); From f820cc9a8055077a09d6d2a18a6dd0e0fc291dee Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 4 May 2026 21:59:15 -0700 Subject: [PATCH 3/3] Make fetch and load_commit work without a worktree --- crates/git/src/repository.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index ab96f1c466be62..99fe99a4883585 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -27,7 +27,6 @@ use std::process::ExitStatus; use std::str::FromStr; use std::{ cmp::Ordering, - future, path::{Path, PathBuf}, sync::Arc, }; @@ -1271,9 +1270,6 @@ impl GitRepository for RealGitRepository { } fn load_commit(&self, commit: String, cx: AsyncApp) -> BoxFuture<'_, Result> { - if self.repository.lock().workdir().is_none() { - return future::ready(Err(anyhow!("no working directory"))).boxed(); - } let git = self.git_binary(); cx.background_spawn(async move { let show_output = git @@ -2459,7 +2455,7 @@ impl GitRepository for RealGitRepository { env: Arc>, cx: AsyncApp, ) -> BoxFuture<'_, Result> { - let working_directory = self.working_directory(); + let working_directory = self.working_directory().unwrap_or(self.path()); let git_directory = self.path(); let remote_name = format!("{}", fetch_options); let git_binary_path = self.system_git_binary_path.clone(); @@ -2469,7 +2465,6 @@ impl GitRepository for RealGitRepository { // which we want to block on. async move { let git_binary_path = git_binary_path.context("git not found on $PATH, can't fetch")?; - let working_directory = working_directory?; let git = GitBinary::new( git_binary_path, working_directory,