Skip to main content

git_claw/workflow/
nav.rs

1//! Navigation and editor integration workflows (`git claw cd` and `git claw open`).
2
3use crate::infra::git::{resolve_git_common_dir, resolve_toplevel};
4use crate::infra::registry::SlotRegistry;
5use crate::workflow::error::WorkflowError;
6use std::path::{Path, PathBuf};
7use std::process::Command;
8
9pub struct CdOptions<'a> {
10    pub name: Option<&'a str>,
11    pub repo_root: Option<&'a Path>,
12}
13
14pub struct OpenOptions<'a> {
15    pub name: Option<&'a str>,
16    pub editor: Option<&'a str>,
17    pub repo_root: Option<&'a Path>,
18}
19
20/// Resolves the worktree path from name or current working directory.
21fn resolve_target_worktree_path(
22    name: Option<&str>,
23    repo_root: Option<&Path>,
24) -> Result<PathBuf, WorkflowError> {
25    let toplevel = resolve_toplevel(repo_root)?;
26    let common_dir = resolve_git_common_dir(Some(&toplevel))?;
27
28    let registry_path = common_dir.join("claw/slots.json");
29    let registry = SlotRegistry::load_or_empty(&registry_path)?;
30
31    match name {
32        Some(target_name) => {
33            let slot = registry
34                .find_by_name(target_name)
35                .ok_or_else(|| WorkflowError::WorktreeNotFound(target_name.to_string()))?;
36            Ok(PathBuf::from(&slot.path))
37        }
38        None => {
39            let cwd = std::env::current_dir().unwrap_or_default();
40            let canonical_cwd = cwd.canonicalize().unwrap_or(cwd);
41            let found = registry.slots.iter().find(|s| {
42                let p = PathBuf::from(&s.path);
43                let canonical_p = p.canonicalize().unwrap_or(p);
44                canonical_cwd.starts_with(&canonical_p)
45            });
46
47            match found {
48                Some(s) => Ok(PathBuf::from(&s.path)),
49                None => Err(WorkflowError::CannotInferWorktree(
50                    canonical_cwd.to_string_lossy().to_string(),
51                )),
52            }
53        }
54    }
55}
56
57/// Resolves and prints the raw worktree path to stdout for shell integration (`cd $(git claw cd <name>)`).
58pub fn cd_worktree(options: CdOptions<'_>) -> Result<PathBuf, WorkflowError> {
59    let path = resolve_target_worktree_path(options.name, options.repo_root)?;
60    println!("{}", path.display());
61    Ok(path)
62}
63
64/// Launches the configured editor targeting the worktree directory.
65pub fn open_worktree(options: OpenOptions<'_>) -> Result<(), WorkflowError> {
66    let path = resolve_target_worktree_path(options.name, options.repo_root)?;
67
68    let editor_cmd = match options.editor {
69        Some(e) => e.to_string(),
70        None => std::env::var("EDITOR")
71            .or_else(|_| std::env::var("VISUAL"))
72            .unwrap_or_else(|_| "code".to_string()),
73    };
74
75    let status = Command::new(&editor_cmd).arg(&path).status().map_err(|e| {
76        WorkflowError::CommandExecutionFailed(format!(
77            "failed to launch editor '{}': {}",
78            editor_cmd, e
79        ))
80    })?;
81
82    if !status.success() {
83        return Err(WorkflowError::CommandExecutionFailed(format!(
84            "editor '{}' exited with non-zero status",
85            editor_cmd
86        )));
87    }
88
89    Ok(())
90}