Skip to main content

git_worktree_manager/
hooks.rs

1//! Lifecycle hooks executed by gw at key worktree transitions.
2//!
3//! Configured via `hooks.post_new` / `hooks.pre_rm` keys in
4//! `~/.config/git-worktree-manager/config.json` or `.cwconfig.json`.
5
6use std::path::Path;
7use std::process::Command;
8
9use crate::error::Result;
10
11/// Run the configured hook for `event` (one of `"post_new"`, `"pre_rm"`),
12/// resolving the hook command from the layered config rooted at the main
13/// repo (resolved from `cwd` via `git::get_main_repo_root`, falling back to
14/// `cwd` if that fails). The hook runs with `cwd` as its working directory.
15///
16/// No-op (returns `Ok(())`) when the event name is unknown or the hook is
17/// unset. Hook is run as `sh -c <cmd>`. A non-zero exit propagates as
18/// `CwError::Other`.
19pub fn run_event(event: &str, cwd: &Path) -> Result<()> {
20    // Worktrees are siblings of the main repo (default ../<repo>-<branch>),
21    // so walking up from `cwd` would never find the main repo's .cwconfig.json.
22    // Resolve to the main repo root for config lookup; `cwd` is kept as the
23    // shell's working directory so hooks run inside the worktree they pertain to.
24    let config_root =
25        crate::git::get_main_repo_root(Some(cwd)).unwrap_or_else(|_| cwd.to_path_buf());
26    let cfg = crate::config::load_effective_config(&config_root)?;
27    let cmd = match event {
28        "post_new" => cfg.hooks.post_new,
29        "pre_rm" => cfg.hooks.pre_rm,
30        _ => return Ok(()),
31    };
32    let Some(cmd) = cmd else {
33        return Ok(());
34    };
35    // sh -c lets users write pipes/conditionals like "npm install && npm test".
36    // stderr is inherited from Command::status() so users see hook output directly.
37    let status = Command::new("sh")
38        .arg("-c")
39        .arg(&cmd)
40        .current_dir(cwd)
41        .status()?;
42    if !status.success() {
43        return Err(crate::error::CwError::Other(format!(
44            "hook '{}' (`{}`) exited with {}",
45            event,
46            cmd.chars().take(60).collect::<String>(),
47            status.code().unwrap_or(-1)
48        )));
49    }
50    Ok(())
51}