#![expect(unused, reason = "extraction; PHASE-03 prunes")]
use super::allowlist::{
Allowlist, allowlist_violations, is_withheld, parse_allowlist, select_copies,
};
use super::marker::{DISPATCH_WORKER_AGENT_TYPE, marker_present, write_marker};
use super::shared::{
gather_fork_worktree, gather_tree_clean, is_linked_worktree, matches, resolve_commit,
resolve_common_dir, target_dir_for_branch,
};
use crate::fsutil::{self, CopyOutcome};
use crate::git;
use crate::root;
use anyhow::{Context, bail};
use std::fs;
use std::io::{self, ErrorKind, Write};
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Merge {
Ok,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum LandRefusal {
TreeUnclean,
NoSuchFork,
WorktreeGone,
DispatchFork,
MergeConflict,
WedgedMerge,
InconsistentMergeState,
}
impl LandRefusal {
pub(crate) fn token(self) -> &'static str {
match self {
LandRefusal::TreeUnclean => "tree-unclean",
LandRefusal::NoSuchFork => "no-such-fork",
LandRefusal::WorktreeGone => "worktree-gone",
LandRefusal::DispatchFork => "dispatch-fork",
LandRefusal::MergeConflict => "merge-conflict",
LandRefusal::WedgedMerge => "wedged-merge",
LandRefusal::InconsistentMergeState => "inconsistent-merge-state",
}
}
}
pub(crate) fn no_such_fork_message(fork: &str) -> String {
format!(
"land-refused: {token} — no branch `{fork}`; `--fork` expects a branch name, \
not a worktree path (list branches with `git branch`)",
token = LandRefusal::NoSuchFork.token(),
)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ForkState {
pub(crate) exists: bool,
pub(crate) has_live_worktree: bool,
pub(crate) bears_marker: bool,
}
pub(crate) fn classify_land(
tree_status_clean: bool,
_head: &str,
fork_state: ForkState,
) -> Result<Merge, LandRefusal> {
if !tree_status_clean {
return Err(LandRefusal::TreeUnclean);
}
if !fork_state.exists {
return Err(LandRefusal::NoSuchFork);
}
if !fork_state.has_live_worktree {
return Err(LandRefusal::WorktreeGone);
}
if fork_state.bears_marker {
return Err(LandRefusal::DispatchFork);
}
Ok(Merge::Ok)
}
pub(crate) fn run_land(path: Option<PathBuf>, fork: &str) -> anyhow::Result<()> {
let root = root::find(path, &root::default_markers())?;
let tree_clean = gather_tree_clean(&root)?;
let exists = git::git_opt(
&root,
&[
"rev-parse",
"--verify",
"--quiet",
&format!("refs/heads/{fork}^{{commit}}"),
],
)?
.is_some();
let fork_wt = gather_fork_worktree(&root, fork)?;
let has_live_worktree = fork_wt.is_some();
let bears_marker = fork_wt.as_deref().is_some_and(marker_present);
let head = git::git_text(&root, &["rev-parse", "--abbrev-ref", "HEAD"])?;
let fork_state = ForkState {
exists,
has_live_worktree,
bears_marker,
};
match classify_land(tree_clean, &head, fork_state) {
Err(LandRefusal::NoSuchFork) => bail!("{}", no_such_fork_message(fork)),
Err(refusal) => bail!("land-refused: {}", refusal.token()),
Ok(Merge::Ok) => {}
}
let merged = git::git_opt(&root, &["merge", "--no-ff", "--no-edit", fork])?;
if merged.is_some() {
writeln!(
io::stdout(),
"landed {fork}: --no-ff merge onto coordination HEAD"
)?;
return Ok(());
}
let mid_merge =
git::git_opt(&root, &["rev-parse", "--verify", "--quiet", "MERGE_HEAD"])?.is_some();
if !mid_merge {
bail!(
"land-refused: {}",
LandRefusal::InconsistentMergeState.token()
);
}
let unmerged = git::git_text(&root, &["diff", "--name-only", "--diff-filter=U"])?;
let aborted = git::git_opt(&root, &["merge", "--abort"])?;
if aborted.is_some() {
bail!("land-refused: {}", LandRefusal::MergeConflict.token());
}
bail!(
"land-refused: {token} — `git merge --abort` failed; MERGE_HEAD is present and the tree is NOT clean. Unmerged paths:\n{unmerged}\nManual remedy: resolve in place and `git commit`, or `git merge --abort` / `git reset --hard {head}` from the coordination root.",
token = LandRefusal::WedgedMerge.token(),
)
}