aboutsummaryrefslogtreecommitdiff
path: root/src/workspace
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-22 02:21:12 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-22 02:23:35 +0300
commit0148c1379b76c5c0a9d05588c07dcb06ec32f93f (patch)
tree554766d959c738ca2aa433c3d4b4a7de25568008 /src/workspace
parenta9ff4012bdca0dbdbc77bc6e53a430689fc899be (diff)
feat: add git worktree workspace management
refs #137
Diffstat (limited to 'src/workspace')
-rw-r--r--src/workspace/git.rs95
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)?;