1use anyhow::Result;
2use std::process::Command;
3
4use crate::opener::augmented_path;
5
6pub struct HookContext {
7 pub owner: String,
8 pub repo: String,
9 pub issue: String,
10 pub branch: String,
11 pub worktree_path: String,
12}
13
14impl HookContext {
15 pub fn render(&self, template: &str) -> String {
16 template
17 .replace("{{owner}}", &self.owner)
18 .replace("{{repo}}", &self.repo)
19 .replace("{{issue}}", &self.issue)
20 .replace("{{branch}}", &self.branch)
21 .replace("{{worktree_path}}", &self.worktree_path)
22 }
23}
24
25pub fn run_hook(script: &str, ctx: &HookContext) -> Result<()> {
29 let rendered = ctx.render(script);
30
31 let tmp_path = std::env::temp_dir().join(format!("worktree-hook-{}.sh", std::process::id()));
32 std::fs::write(&tmp_path, rendered.as_bytes())?;
33
34 #[cfg(unix)]
35 {
36 use std::os::unix::fs::PermissionsExt;
37 std::fs::set_permissions(&tmp_path, std::fs::Permissions::from_mode(0o755))?;
38 }
39
40 let result = Command::new("sh")
41 .arg(&tmp_path)
42 .env("PATH", augmented_path())
43 .status();
44 let _ = std::fs::remove_file(&tmp_path);
45
46 match result {
47 Ok(status) if !status.success() => {
48 eprintln!("Warning: hook exited with status {:?}", status.code());
49 }
50 Err(e) => {
52 eprintln!("Warning: failed to run hook: {e}");
53 }
54 _ => {}
56 }
57
58 Ok(())
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64 fn ctx() -> HookContext {
65 HookContext {
66 owner: "acme".into(),
67 repo: "api".into(),
68 issue: "42".into(),
69 branch: "issue-42".into(),
70 worktree_path: "/tmp/wt".into(),
71 }
72 }
73 #[test]
74 fn test_render_all_placeholders() {
75 let out = ctx().render("{{owner}}/{{repo}}#{{issue}} {{branch}} {{worktree_path}}");
76 assert_eq!(out, "acme/api#42 issue-42 /tmp/wt");
77 }
78 #[test]
79 fn test_render_no_placeholders() {
80 assert_eq!(ctx().render("hello"), "hello");
81 }
82 #[test]
83 fn test_run_hook_success() {
84 run_hook("true", &ctx()).unwrap();
85 }
86 #[test]
87 fn test_run_hook_nonzero_exit() {
88 run_hook("exit 1", &ctx()).unwrap();
89 }
90}