use std::{
ops::Deref,
path::{Path, PathBuf},
process::Command,
thread,
time::Duration,
};
pub(crate) struct LinkedWorktreeGuard {
repository: PathBuf,
path: PathBuf,
}
impl LinkedWorktreeGuard {
pub(crate) fn new(repository: &Path, path: PathBuf) -> Self {
assert_ne!(
repository, path,
"linked worktree cannot replace its repository"
);
assert_eq!(
repository.parent(),
path.parent(),
"fixture worktree must be a sibling of its owning repository"
);
Self {
repository: repository.to_path_buf(),
path,
}
}
pub(crate) fn path(&self) -> &Path {
&self.path
}
pub(crate) fn remove(&self) -> bool {
if !self.path.exists() {
let _ = Command::new("git")
.args(["worktree", "prune"])
.current_dir(&self.repository)
.status();
return true;
}
let _ = Command::new("git")
.args(["worktree", "remove", "--force"])
.arg(&self.path)
.current_dir(&self.repository)
.status()
.is_ok_and(|status| status.success());
let _ = Command::new("git")
.args(["worktree", "prune"])
.current_dir(&self.repository)
.status();
if !self.path.exists() {
return true;
}
for attempt in 0..100 {
match std::fs::remove_dir_all(&self.path) {
Ok(()) => break,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => break,
Err(_) if attempt < 99 => thread::sleep(Duration::from_millis(10)),
Err(_) => break,
}
}
let _ = Command::new("git")
.args(["worktree", "prune"])
.current_dir(&self.repository)
.status();
!self.path.exists()
}
}
impl Deref for LinkedWorktreeGuard {
type Target = Path;
fn deref(&self) -> &Self::Target {
&self.path
}
}
impl AsRef<Path> for LinkedWorktreeGuard {
fn as_ref(&self) -> &Path {
&self.path
}
}
impl Drop for LinkedWorktreeGuard {
fn drop(&mut self) {
if !self.remove() {
eprintln!(
"linked worktree fixture survived cleanup: {}",
self.path.display()
);
}
}
}