#![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};
const DOCTRINE_PREFIX: &str = ".doctrine/";
const CLAUDE_PREFIX: &str = ".claude/";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Apply {
Ok,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Refusal {
HeadMoved,
TreeUnclean,
MultiCommit,
DoctrineTouch,
ClaudeTouch,
}
impl Refusal {
pub(crate) fn token(self) -> &'static str {
match self {
Refusal::HeadMoved => "head-moved",
Refusal::TreeUnclean => "tree-unclean",
Refusal::MultiCommit => "multi-commit",
Refusal::DoctrineTouch => "doctrine-touch",
Refusal::ClaudeTouch => "claude-touch",
}
}
}
pub(crate) fn classify_import(
head_at_base: bool,
tree_clean: bool,
single_commit: bool,
delta_paths: &[String],
) -> Result<Apply, Refusal> {
if !head_at_base {
return Err(Refusal::HeadMoved);
}
if !tree_clean {
return Err(Refusal::TreeUnclean);
}
if !single_commit {
return Err(Refusal::MultiCommit);
}
for path in delta_paths {
if path.starts_with(DOCTRINE_PREFIX) {
return Err(Refusal::DoctrineTouch);
}
if path.starts_with(CLAUDE_PREFIX) {
return Err(Refusal::ClaudeTouch);
}
}
Ok(Apply::Ok)
}
pub(crate) fn run_import(path: Option<PathBuf>, base: &str, fork: &str) -> anyhow::Result<()> {
let root = root::find(path, &root::default_markers())?;
let base_sha = resolve_commit(&root, base)?;
let head_sha = resolve_commit(&root, "HEAD")?;
let head_at_base = matches(&base_sha, &head_sha);
let tree_clean = gather_tree_clean(&root)?;
let fork_parent = git::git_opt(
&root,
&["rev-parse", "--verify", &format!("{fork}^^{{commit}}")],
)?;
let single_commit = fork_parent
.as_deref()
.is_some_and(|p| matches(p, &base_sha));
let diff = git::git_text(
&root,
&[
"-c",
"core.quotePath=false",
"diff",
"--name-only",
"--no-renames",
&format!("{base}..{fork}"),
],
)?;
let delta_paths: Vec<String> = diff.lines().map(str::to_owned).collect();
match classify_import(head_at_base, tree_clean, single_commit, &delta_paths) {
Err(refusal) => bail!("import-refused: {}", refusal.token()),
Ok(Apply::Ok) => {}
}
let patch = git::git_bytes(&root, &["diff", "--no-renames", &format!("{base}..{fork}")])?;
git::git_apply_index(&root, &patch)
.with_context(|| format!("git apply --3way --index {base}..{fork}"))?;
writeln!(
io::stdout(),
"imported {base}..{fork}: delta staged (uncommitted)"
)?;
Ok(())
}