Skip to main content

worktree_io/
hooks.rs

1use anyhow::Result;
2use std::process::Command;
3
4pub struct HookContext {
5    pub owner: String,
6    pub repo: String,
7    pub issue: String,
8    pub branch: String,
9    pub worktree_path: String,
10}
11
12impl HookContext {
13    fn render(&self, template: &str) -> String {
14        template
15            .replace("{{owner}}", &self.owner)
16            .replace("{{repo}}", &self.repo)
17            .replace("{{issue}}", &self.issue)
18            .replace("{{branch}}", &self.branch)
19            .replace("{{worktree_path}}", &self.worktree_path)
20    }
21}
22
23/// Render `script` with `ctx`, write to a temp file, and execute it.
24/// Stdout and stderr are forwarded to the caller's terminal.
25/// A non-zero exit code prints a warning but does not return an error.
26pub fn run_hook(script: &str, ctx: &HookContext) -> Result<()> {
27    let rendered = ctx.render(script);
28
29    let tmp_path = std::env::temp_dir().join(format!("worktree-hook-{}.sh", std::process::id()));
30    std::fs::write(&tmp_path, rendered.as_bytes())?;
31
32    #[cfg(unix)]
33    {
34        use std::os::unix::fs::PermissionsExt;
35        std::fs::set_permissions(&tmp_path, std::fs::Permissions::from_mode(0o755))?;
36    }
37
38    let result = Command::new("sh").arg(&tmp_path).status();
39    let _ = std::fs::remove_file(&tmp_path);
40
41    match result {
42        Ok(status) if !status.success() => {
43            eprintln!("Warning: hook exited with status {:?}", status.code());
44        }
45        Err(e) => {
46            eprintln!("Warning: failed to run hook: {e}");
47        }
48        _ => {}
49    }
50
51    Ok(())
52}