1use std::path::{Path, PathBuf};
7use std::process::Command;
8
9use tempfile::TempDir;
10use tracing::warn;
11
12use crate::error::{JjHooksError, Result};
13
14pub struct Worktree {
15 git_dir: PathBuf,
16 dir: TempDir,
17 removed: bool,
18}
19
20impl Worktree {
21 pub fn create(git_dir: &Path, commit: &str) -> Result<Self> {
23 let dir = TempDir::with_prefix("jj-hooks-worktree-")?;
24
25 let output = Command::new("git")
26 .arg(format!("--git-dir={}", git_dir.display()))
27 .args(["worktree", "add", "--detach", "--quiet"])
28 .arg(dir.path())
29 .arg(commit)
30 .output()?;
31
32 if !output.status.success() {
33 return Err(JjHooksError::JjFailed {
34 status: output.status.code().unwrap_or(-1),
35 stderr: format!(
36 "git worktree add failed: {}",
37 String::from_utf8_lossy(&output.stderr)
38 ),
39 });
40 }
41
42 Ok(Self {
43 git_dir: git_dir.to_owned(),
44 dir,
45 removed: false,
46 })
47 }
48
49 pub fn path(&self) -> &Path {
50 self.dir.path()
51 }
52
53 pub fn git_dir(&self) -> &Path {
54 &self.git_dir
55 }
56
57 fn remove(&mut self) -> std::io::Result<()> {
58 if self.removed {
59 return Ok(());
60 }
61 let output = Command::new("git")
62 .arg(format!("--git-dir={}", self.git_dir.display()))
63 .args(["worktree", "remove", "--force"])
64 .arg(self.dir.path())
65 .output()?;
66
67 if !output.status.success() {
68 return Err(std::io::Error::other(format!(
69 "git worktree remove failed: {}",
70 String::from_utf8_lossy(&output.stderr)
71 )));
72 }
73
74 self.removed = true;
75 Ok(())
76 }
77}
78
79impl Drop for Worktree {
80 fn drop(&mut self) {
81 if let Err(e) = self.remove() {
82 warn!(
83 "failed to clean up worktree at {}: {e}",
84 self.dir.path().display()
85 );
86 }
87 }
88}