git: add workspace coordination forms and conversation trailers

This commit is contained in:
2026-11-07 00:44:49 +00:00
parent aaf6d9b54f
commit 54229adad2
8 changed files with 770 additions and 67 deletions
+35 -8
View File
@@ -14,6 +14,7 @@ use std::{
/// Attach a linked worktree, or adopt existing bytes onto an unborn branch.
#[derive(serde::Serialize, serde::Deserialize)]
pub(crate) struct AddParams {
pub detached: Option<bool>,
/// Repository name or absolute worktree/bare path under /repos/ or /plugins/.
pub repo: String,
/// Absolute worktree path under /plugins/ or /repos/, without dot segments.
@@ -150,10 +151,28 @@ pub(crate) struct AttachParams {
/// Existing short local branch name.
pub branch: String,
}
pub(crate) fn prune(repo: &gix::Repository) -> Result<()> {
let base = repo.common_dir().join("worktrees");
if !base.exists() {
return Ok(());
}
for entry in fs::read_dir(base).map_err(|e| error("worktree prune", e))? {
let dir = entry.map_err(|e| error("worktree prune", e))?.path();
if dir.join("locked").exists() {
continue;
}
let gitdir = read(&dir.join("gitdir"))?;
if !Path::new(gitdir.trim()).exists() {
fs::remove_dir_all(dir).map_err(|e| error("worktree prune", e))?;
}
}
Ok(())
}
pub(crate) fn attach(repo: &gix::Repository, p: AttachParams) -> Result<WorktreeInfo> {
add_with(
repo,
AddParams {
detached: None,
repo: p.repo,
path: p.path,
branch: p.branch,
@@ -170,20 +189,24 @@ fn add_with(repo: &gix::Repository, p: AddParams, keep_files: bool) -> Result<Wo
let path = absolute(&p.path)?;
let common = canonical(repo.common_dir())?;
separate(&path, &common)?;
let detached = p.detached.unwrap_or(false);
let branch = branches::branch_name(&p.branch)?;
ensure_available(repo, &branch)?;
if !detached {
ensure_available(repo, &branch)?;
}
if let Some(current_path) = repo.workdir() {
if crate::head_info(repo)?.branch.as_deref() == Some(&branch) {
if !detached && crate::head_info(repo)?.branch.as_deref() == Some(&branch) {
return Err(error(
"branch checked out",
format!("{branch} is checked out at {}", current_path.display()),
));
}
}
let exists = repo
.try_find_reference(branch.as_str())
.map_err(|e| error("worktree branch", e))?
.is_some();
let exists = !detached
&& repo
.try_find_reference(branch.as_str())
.map_err(|e| error("worktree branch", e))?
.is_some();
let adopt = p.adopt.unwrap_or(false);
if adopt && (exists || p.from.is_some()) {
return Err(error(
@@ -296,8 +319,12 @@ fn add_with(repo: &gix::Repository, p: AddParams, keep_files: bool) -> Result<Wo
&linked,
branches::CheckoutParams {
repo: p.path.clone(),
target: p.branch.clone(),
create: Some(!exists),
target: if detached {
id.to_string()
} else {
p.branch.clone()
},
create: Some(!exists && !detached),
force: None,
},
)?