use super::tree;
use crate::args::Args;
use crate::failure::Failure;
use crate::output::outln;
pub(super) fn run(cwd: &std::path::Path, a: &Args) -> Result<i32, Failure> {
let roots = super::resolve_roots(cwd)?;
if a.has("undo") {
return undo(&roots, a);
}
if let Some(refusal) = super::refuse_home_or_global_store(&roots) {
return Err(refusal);
}
apply(&roots, a)
}
fn apply(roots: &super::Roots, a: &Args) -> Result<i32, Failure> {
let below = tree::trees_below(&roots.here);
let join_spec = a.opt("join");
if !below.is_empty() {
return Err(match join_spec {
Some(spec) => super::claude_code::tree_below_join_refusal(&roots.here, &below, spec),
None => tree::tree_below_refusal(&below),
});
}
if let Some(spec) = join_spec {
return match tree::plan_join(roots, spec, a.opt("lane-name"))? {
Some((join_roots, plan)) => apply_writes(&join_roots, a, plan),
None => Ok(0),
};
}
let plan = tree::plan(roots, a)?;
apply_writes(roots, a, plan)
}
fn full_plan(here: &std::path::Path, plan: &tree::TreePlan) -> String {
let mut s = format!(" vivac init, in {}\n\n", here.display());
if let Some(name) = &plan.name_collision {
s.push_str(&super::claude_code::name_collision_paragraph(name));
}
s.push_str(&tree::opening_lines(plan));
s.push_str(&tree::closing_lines(plan));
s
}
fn apply_writes(roots: &super::Roots, a: &Args, plan: tree::TreePlan) -> Result<i32, Failure> {
let here = &roots.here;
let nothing_to_write = !plan.vivac_missing
&& !plan.gitignore_missing
&& plan.lane.unchanged
&& !plan.lane.needs_lock
&& plan.lane.stale_worktrees.is_empty();
let plan_text = full_plan(here, &plan);
if a.has("dry-run") {
outln!(
"{plan_text}{}\n Nothing written: --dry-run.",
plan.unknown_product_warning
);
if plan.log_tracked {
print!("{}", super::claude_code::tracked_git_warning());
}
if let Some(w) = &plan.above_warning {
print!("{w}");
}
return Ok(0);
}
if nothing_to_write {
tree::note_registry(roots);
outln!("{plan_text} Nothing to write: this project is already set up.");
if plan.log_tracked {
print!("{}", super::claude_code::tracked_git_warning());
}
if let Some(w) = &plan.above_warning {
print!("{w}");
}
return Ok(0);
}
if !a.has("yes") && !super::stdin_is_terminal() {
return Err(Failure::Model(super::init_no_terminal_text(a)));
}
print!("{plan_text}{}", plan.unknown_product_warning);
let proceed = a.has("yes") || super::ask("\n Write it? [y/N] ");
if !proceed {
outln!("\n Nothing written.");
return Ok(0);
}
let writes = tree::file_writes(roots, &plan);
super::commit(&writes)?;
tree::commit(roots, &plan, &writes)?;
tree::note_registry(roots);
tree::note_name(&plan);
print!("\n{}", written_text(&plan));
if plan.log_tracked {
print!("{}", super::claude_code::tracked_git_warning());
}
if let Some(w) = &plan.above_warning {
print!("{w}");
}
Ok(0)
}
fn written_text(plan: &tree::TreePlan) -> String {
let mut s = String::from(" Written.\n");
if plan.vivac_missing {
s.push_str("\n First node: vivac push \"<title>\" --why \"<reason>\"\n");
s.push_str(super::claude_code::MIGRATE_PARAGRAPHS);
} else {
let lane_declared = !plan.lane.unchanged || !plan.lane.stale_worktrees.is_empty();
s.push_str(&super::claude_code::tree_paragraph(
"init",
plan.gitignore_missing,
lane_declared,
plan.lane.needs_lock,
));
if plan.lane.is_new {
s.push_str(super::claude_code::JOIN_MIGRATE_PARAGRAPHS);
}
}
s
}
fn undo(roots: &super::Roots, a: &Args) -> Result<i32, Failure> {
let here = roots.here.as_path();
let undo_lane = tree::undo_lane(roots)?;
if undo_lane.raw.is_none() {
outln!(" Nothing to undo: this folder carries no lane vivac init wrote.");
return Ok(0);
}
let mut s = format!(" vivac init --undo, in {}\n\n", here.display());
s.push_str(&tree::vivac_dir_lines(&undo_lane));
s.push_str(&tree::undo_lane_lines(&undo_lane));
s.push('\n');
if a.has("dry-run") {
outln!("{s} Nothing written: --dry-run.");
return Ok(0);
}
if !undo_lane.removable {
outln!("{s} Nothing written: this lane has written to the tree.");
return Ok(0);
}
if !a.has("yes") && !super::stdin_is_terminal() {
return Err(Failure::Model(super::init_no_terminal_text(a)));
}
print!("{s}");
let proceed = a.has("yes") || super::ask(" Undo it? [y/N] ");
if !proceed {
outln!("\n Nothing written.");
return Ok(0);
}
let mut writes = vec![super::PlannedWrite::delete(
undo_lane.path.clone(),
undo_lane.raw.clone().unwrap_or_default(),
)];
if undo_lane.vivac_dir_removable {
let gitignore = undo_lane.vivac_dir.join(crate::store::GITIGNORE);
if let Ok(original) = std::fs::read(&gitignore) {
writes.push(super::PlannedWrite::delete(gitignore, original));
}
}
super::commit(&writes)?;
if undo_lane.vivac_dir_removable {
super::claude_code::remove_if_empty(Some(&undo_lane.vivac_dir));
}
outln!(" Undone. The tree in .vivac/ is untouched.");
Ok(0)
}