use crate::adopt;
use crate::config::EffectiveConfig;
use crate::edge::Edge;
use crate::git;
use crate::hooks::Hooks;
use crate::lifecycle::Engine;
use crate::log::{self, Level, Log};
use crate::plugin::Subprocess;
use crate::registry::Registry;
use crate::seed;
use crate::substrate;
use crate::verb::Verb;
use crate::wire::{Binding, OpContext};
use std::io;
use std::path::Path;
pub fn prime(edge: &Edge, args: &[String]) -> io::Result<()> {
let opts = parse_prime(args, &edge.default_actor)?;
let clone = edge.xdg.clone_dir(&edge.invocation_path);
let (landing, store) = (clone.landing(), clone.store());
if is_landing(&landing) {
seed::rebind(&landing, edge.exe_dir.as_deref())?;
} else {
substrate::found_landing(&landing, &edge.xdg, edge.exe_dir.as_deref(), &opts.actor)?;
}
if opts.stealth {
crate::conf::declare_stealth(&landing, &opts.actor)?;
}
if let Some(center) = &opts.center {
crate::conf::bind_task_remote(&clone, &landing, &opts.actor, center)?;
}
let install = opts.install.as_ref().or(opts.center.as_ref());
if let Some(center) = install {
adopt::adopt(edge, &landing, &store, &opts.actor, center)?;
}
let remote = opts.remote.or(opts.install);
let (binding, level) = bind(edge, &landing, &store, remote, None)?;
prime_chain(edge, &landing, &store, &opts.actor, binding.clone(), level)?;
run_chain(edge, &landing, &store, Verb::Sync, &opts.actor, binding, level)
}
fn prime_chain(edge: &Edge, landing: &Path, store: &Path, actor: &str, binding: Binding, level: Level) -> io::Result<()> {
let clone = edge.xdg.clone_dir(&edge.invocation_path);
let user_config = edge.xdg.user_config();
let hooks = Hooks::effective(landing, &user_config)?;
let reg = Registry::at(landing);
let pre = hooks.resolve(®, Verb::Prime.token(), "pre");
let post = hooks.resolve(®, Verb::Prime.token(), "post");
let before = EffectiveConfig::resolve(landing, &user_config)?.tasks_branch;
let mut step = || -> io::Result<Option<String>> {
let name = EffectiveConfig::resolve(landing, &user_config)?.tasks_branch;
substrate::materialize(landing, store, &name, actor)?;
Ok((name != before).then_some(name))
};
let log = Log::new(clone.op_log(), level, Verb::Prime, log::wall);
let plugins = Subprocess::new(OpContext::diffless(actor.to_string(), binding), &log, edge.depth);
let anvil = git::Git::at(store);
Engine::new(&anvil, &plugins, &log)
.prime(landing, store, &pre, &post, &mut step)
.map_err(|e| io::Error::other(e.to_string()))
}
pub fn sync(edge: &Edge, args: &[String]) -> io::Result<()> {
let opts = parse_sync(args, &edge.default_actor)?;
let clone = edge.xdg.clone_dir(&edge.invocation_path);
let (landing, store) = (clone.landing(), clone.store());
if !is_landing(&landing) {
return Err(io::Error::other("no balls checkout here — run `bl prime` first"));
}
let (binding, level) = bind(edge, &landing, &store, opts.remote, opts.branch)?;
run_chain(edge, &landing, &store, Verb::Sync, &opts.actor, binding, level)
}
pub(crate) fn bind(edge: &Edge, landing: &Path, store: &Path, cli_remote: Option<String>, target: Option<String>) -> io::Result<(Binding, Level)> {
let user_config = edge.xdg.user_config();
let binding_path = edge.xdg.clone_dir(&edge.invocation_path).binding();
let cfg = EffectiveConfig::resolve(landing, &user_config)?;
let level = Level::parse(edge.log_level.as_deref().unwrap_or(&cfg.log_level))?;
let (remote, stealth) = crate::config::remote_ladder(cli_remote, landing, &binding_path, &user_config)?;
let tasks_branch = target.unwrap_or(cfg.tasks_branch);
Ok((binding(landing, store, &edge.invocation_path, remote, stealth, tasks_branch), level))
}
fn run_chain(edge: &Edge, landing: &Path, store: &Path, op: Verb, actor: &str, binding: Binding, level: Level) -> io::Result<()> {
let clone = edge.xdg.clone_dir(&edge.invocation_path);
let hooks = Hooks::effective(landing, &edge.xdg.user_config())?;
let reg = Registry::at(landing);
let pre = hooks.resolve(®, op.token(), "pre");
let post = hooks.resolve(®, op.token(), "post");
let ctx = OpContext::diffless(actor.to_string(), binding);
let log = Log::new(clone.op_log(), level, op, log::wall);
let plugins = Subprocess::new(ctx, &log, edge.depth);
let anvil = git::Git::at(store);
Engine::new(&anvil, &plugins, &log)
.diffless(op, store, &pre, &post)
.map_err(|e| io::Error::other(e.to_string()))
}
fn is_landing(landing: &Path) -> bool {
landing.join("config").is_dir()
}
pub(crate) fn binding(landing: &Path, store: &Path, invocation: &Path, remote: Option<String>, stealth: bool, tasks_branch: String) -> Binding {
Binding {
remote,
stealth, tasks_branch,
store: store.to_string_lossy().into_owned(),
landing: landing.to_string_lossy().into_owned(),
invocation_path: invocation.to_string_lossy().into_owned(),
}
}
#[path = "checkout_args.rs"]
mod args;
use args::{parse_prime, parse_sync};
pub(crate) use args::value;
#[cfg(test)]
#[path = "checkout_tests.rs"]
mod tests;