diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-07 02:14:39 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-07 02:14:39 +0300 |
| commit | 643860d5e3cabc19faaf9aa1c5e1cf671ee9e0c2 (patch) | |
| tree | ad121db49a8b482c95f47cbb2ebce4004fdc59b0 /src | |
| parent | a51902ede97afd84b469f838f25ab79287bd375d (diff) | |
fix: avoid spawning git for branch lookup
Diffstat (limited to 'src')
| -rw-r--r-- | src/workspace.rs | 87 |
1 files changed, 71 insertions, 16 deletions
diff --git a/src/workspace.rs b/src/workspace.rs index 9a679040..02f971b0 100644 --- a/src/workspace.rs +++ b/src/workspace.rs @@ -686,26 +686,31 @@ pub fn derive_label_from_cwd(cwd: &Path) -> String { } pub fn git_branch(cwd: &Path) -> Option<String> { - git_repo_root(cwd)?; - - let output = std::process::Command::new("git") - .arg("-C") - .arg(cwd) - .args(["rev-parse", "--abbrev-ref", "HEAD"]) - .output() - .ok()?; + let repo_root = git_repo_root(cwd)?; + let git_dir = git_dir_for_repo_root(&repo_root)?; + let head = std::fs::read_to_string(git_dir.join("HEAD")).ok()?; + parse_git_head_branch(&head) +} - if !output.status.success() { - return None; +fn git_dir_for_repo_root(repo_root: &Path) -> Option<PathBuf> { + let git_path = repo_root.join(".git"); + if git_path.is_dir() { + return Some(git_path); } - let stdout = String::from_utf8(output.stdout).ok()?; - let branch = stdout.trim(); - if branch.is_empty() || branch == "HEAD" { - None + let gitdir = std::fs::read_to_string(&git_path).ok()?; + let relative = gitdir.trim().strip_prefix("gitdir:")?.trim(); + let resolved = Path::new(relative); + Some(if resolved.is_absolute() { + resolved.to_path_buf() } else { - Some(branch.to_string()) - } + repo_root.join(resolved) + }) +} + +fn parse_git_head_branch(head: &str) -> Option<String> { + let branch = head.trim().strip_prefix("ref: refs/heads/")?; + (!branch.is_empty()).then(|| branch.to_string()) } fn git_repo_root(start: &Path) -> Option<PathBuf> { @@ -833,6 +838,21 @@ mod tests { use super::*; use crate::detect::{Agent, AgentState}; + fn temp_test_dir(name: &str) -> PathBuf { + let unique = format!( + "herdr-workspace-tests-{}-{}-{}", + name, + std::process::id(), + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos() + ); + let path = std::env::temp_dir().join(unique); + std::fs::create_dir_all(&path).unwrap(); + path + } + #[test] fn aggregate_state_all_unknown() { let ws = Workspace::test_new("test"); @@ -893,6 +913,41 @@ mod tests { } #[test] + fn git_branch_reads_head_from_standard_repo() { + let root = temp_test_dir("standard-repo"); + std::fs::create_dir_all(root.join(".git")).unwrap(); + std::fs::write(root.join(".git/HEAD"), "ref: refs/heads/main\n").unwrap(); + + assert_eq!(git_branch(&root).as_deref(), Some("main")); + + std::fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn git_branch_reads_head_from_worktree_gitdir_file() { + let root = temp_test_dir("worktree"); + let worktree_git_dir = root.join(".bare/worktrees/feature"); + std::fs::create_dir_all(&worktree_git_dir).unwrap(); + std::fs::write(root.join(".git"), "gitdir: .bare/worktrees/feature\n").unwrap(); + std::fs::write(worktree_git_dir.join("HEAD"), "ref: refs/heads/feature\n").unwrap(); + + assert_eq!(git_branch(&root).as_deref(), Some("feature")); + + std::fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn git_branch_returns_none_for_detached_head() { + let root = temp_test_dir("detached-head"); + std::fs::create_dir_all(root.join(".git")).unwrap(); + std::fs::write(root.join(".git/HEAD"), "3e1b9a8d\n").unwrap(); + + assert_eq!(git_branch(&root), None); + + std::fs::remove_dir_all(root).unwrap(); + } + + #[test] fn pane_details_hide_plain_shells() { let mut ws = Workspace::test_new("test"); let root_pane = ws.tabs[0].root_pane; |
