use std::path::{Path, PathBuf};
use anyhow::{Result, bail};
#[derive(Debug, PartialEq, Eq)]
pub struct WorktreeCheckout {
pub path: PathBuf,
pub branch: String,
}
pub fn worktree_checkouts(workdir: &Path) -> Result<Vec<WorktreeCheckout>> {
let stdout = super::run_git_stdout(workdir, &["worktree", "list", "--porcelain"])?;
Ok(parse_worktree_list(&stdout))
}
fn parse_worktree_list(porcelain: &str) -> Vec<WorktreeCheckout> {
let mut result = Vec::new();
let mut path: Option<PathBuf> = None;
let mut branch: Option<String> = None;
let mut skip = false;
for line in porcelain.lines().chain(std::iter::once("")) {
if line.is_empty() {
if let (Some(p), Some(b)) = (path.take(), branch.take())
&& !skip
{
result.push(WorktreeCheckout { path: p, branch: b });
}
skip = false;
} else if let Some(p) = line.strip_prefix("worktree ") {
path = Some(PathBuf::from(p));
} else if let Some(b) = line.strip_prefix("branch refs/heads/") {
branch = Some(b.to_string());
} else if line == "bare" || line.starts_with("prunable") {
skip = true;
}
}
result
}
pub fn ensure_not_checked_out_elsewhere(workdir: &Path, branches: &[String]) -> Result<()> {
if branches.is_empty() {
return Ok(());
}
let current = workdir
.canonicalize()
.unwrap_or_else(|_| workdir.to_path_buf());
let mut listed_as = super::absolute_git_dir(workdir)?;
if listed_as.file_name().is_some_and(|name| name == ".git") {
listed_as.pop();
}
let listed_as = listed_as.canonicalize().unwrap_or(listed_as);
let mut blocked = Vec::new();
for checkout in worktree_checkouts(workdir)? {
if !branches.contains(&checkout.branch) {
continue;
}
if !checkout.path.exists() {
continue;
}
let canonical = checkout
.path
.canonicalize()
.unwrap_or_else(|_| checkout.path.clone());
if canonical == current || canonical == listed_as {
continue;
}
blocked.push(checkout);
}
match blocked.as_slice() {
[] => Ok(()),
[one] => bail!(
"Cannot rewrite branch `{}` — it is checked out at `{}`\n\
That worktree's index and files would stay at the old tip. Check out \
another branch there (or remove the worktree), then retry",
one.branch,
one.path.display()
),
many => {
let list: Vec<String> = many
.iter()
.map(|c| format!(" `{}` at `{}`", c.branch, c.path.display()))
.collect();
bail!(
"Cannot rewrite branches that are checked out in other worktrees:\n{}\n\
Their indexes and files would stay at the old tips. Check out other \
branches there (or remove the worktrees), then retry",
list.join("\n")
)
}
}
}
#[cfg(test)]
#[path = "git_worktree_test.rs"]
mod tests;