diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-22 02:21:12 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-22 02:23:35 +0300 |
| commit | 0148c1379b76c5c0a9d05588c07dcb06ec32f93f (patch) | |
| tree | 554766d959c738ca2aa433c3d4b4a7de25568008 /src/workspace | |
| parent | a9ff4012bdca0dbdbc77bc6e53a430689fc899be (diff) | |
feat: add git worktree workspace management
refs #137
Diffstat (limited to 'src/workspace')
| -rw-r--r-- | src/workspace/git.rs | 95 |
1 files changed, 95 insertions, 0 deletions
diff --git a/src/workspace/git.rs b/src/workspace/git.rs index ca453003..d06c8538 100644 --- a/src/workspace/git.rs +++ b/src/workspace/git.rs @@ -1,5 +1,23 @@ use std::path::{Path, PathBuf}; +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GitSpaceMetadata { + pub key: String, + pub checkout_key: String, + pub label: String, + pub repo_root: PathBuf, + pub is_linked_worktree: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GitWorktreeInfo { + pub repo_root: PathBuf, + pub git_dir: PathBuf, + pub git_common_dir: PathBuf, + pub is_bare: bool, + pub is_linked_worktree: bool, +} + pub fn derive_label_from_cwd(cwd: &Path) -> String { if let Some(repo_root) = git_repo_root(cwd) { if let Some(name) = repo_root.file_name().and_then(|n| n.to_str()) { @@ -21,6 +39,83 @@ pub fn derive_label_from_cwd(cwd: &Path) -> String { .unwrap_or_else(|| cwd.display().to_string()) } +pub fn git_worktree_info(cwd: &Path) -> Option<GitWorktreeInfo> { + let repo_root = git_rev_parse(cwd, &["--show-toplevel"])?; + let git_dir = git_rev_parse(cwd, &["--path-format=absolute", "--git-dir"])?; + let git_common_dir = git_rev_parse(cwd, &["--path-format=absolute", "--git-common-dir"])?; + let is_bare = git_rev_parse(cwd, &["--is-bare-repository"])? == "true"; + let is_linked_worktree = + canonicalize_best_effort(&git_dir) != canonicalize_best_effort(&git_common_dir); + + Some(GitWorktreeInfo { + repo_root: PathBuf::from(repo_root), + git_dir: PathBuf::from(git_dir), + git_common_dir: PathBuf::from(git_common_dir), + is_bare, + is_linked_worktree, + }) +} + +fn git_rev_parse(cwd: &Path, args: &[&str]) -> Option<String> { + let output = std::process::Command::new("git") + .arg("-C") + .arg(cwd) + .arg("rev-parse") + .args(args) + .output() + .ok()?; + + if !output.status.success() { + return None; + } + + let stdout = String::from_utf8(output.stdout).ok()?; + Some(stdout.trim().to_string()) +} + +fn canonicalize_best_effort(path: &str) -> PathBuf { + std::fs::canonicalize(path).unwrap_or_else(|_| PathBuf::from(path)) +} + +pub fn git_space_metadata(cwd: &Path) -> Option<GitSpaceMetadata> { + let info = git_worktree_info(cwd)?; + if info.is_bare { + return None; + } + let key = canonicalize_best_effort_path(&info.git_common_dir) + .display() + .to_string(); + let checkout_key = canonicalize_best_effort_path(&info.repo_root) + .display() + .to_string(); + let label_path = if info + .git_common_dir + .file_name() + .and_then(|name| name.to_str()) + == Some(".git") + { + info.git_common_dir.parent().unwrap_or(&info.repo_root) + } else { + &info.repo_root + }; + let label = label_path + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or("repo") + .to_string(); + Some(GitSpaceMetadata { + key, + checkout_key, + label, + repo_root: info.repo_root, + is_linked_worktree: info.is_linked_worktree, + }) +} + +fn canonicalize_best_effort_path(path: &Path) -> PathBuf { + std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf()) +} + pub fn git_branch(cwd: &Path) -> Option<String> { let repo_root = git_repo_root(cwd)?; let git_dir = git_dir_for_repo_root(&repo_root)?; |
