mod origin;
pub use origin::Origin;
use origin::{commit_message, materialize};
use super::checkout;
use super::{GitRunner, TEMPLATE, descriptions};
use crate::workspace;
use std::io;
use std::path::Path;
#[derive(Debug, PartialEq, Eq)]
pub enum Pass {
Landed,
Declined {
target: String,
},
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Layout(#[from] workspace::LayoutError),
#[error("config authoring I/O: {0}")]
Io(#[source] io::Error),
#[error("config authoring git: {0}")]
Git(#[source] io::Error),
#[error("descriptions-always: {0}")]
Descriptions(#[source] descriptions::Error),
#[error("edit step: {0}")]
Edit(#[source] io::Error),
#[error(transparent)]
Facts(#[from] crate::facts::OverCap),
#[error("pass --from <source> or --orphan, not both")]
Conflict,
#[error(transparent)]
NoSuchLineage(#[from] workspace::UnknownLineage),
}
pub fn from_cli(
workspace: &Path,
data_root: &Path,
name: Option<&str>,
from: Option<&str>,
orphan: bool,
edit: impl FnOnce(&Path) -> io::Result<()>,
git: &dyn GitRunner,
) -> Result<Pass, Error> {
let origin = match (from, orphan) {
(Some(source), false) => Origin::Fork { source },
(None, true) => Origin::Orphan,
(None, false) => Origin::Advance,
(Some(_), true) => return Err(Error::Conflict),
};
let name = name.unwrap_or(workspace::DEFAULT_CONFIG_NAME);
author(workspace, data_root, name, origin, edit, git)
}
pub fn author(
workspace: &Path,
data_root: &Path,
name: &str,
origin: Origin,
edit: impl FnOnce(&Path) -> io::Result<()>,
git: &dyn GitRunner,
) -> Result<Pass, Error> {
workspace::require(workspace)?;
require_source(workspace, &origin, git)?;
let repo = workspace::repo_git(workspace);
let author = checkout::path(workspace);
let target = origin::target_ref(name, &origin);
checkout::heal(git, &repo, &author).map_err(Error::Io)?;
let checkout = materialize(git, &repo, &target, &author, &origin)?;
std::fs::create_dir_all(&author).map_err(Error::Io)?;
if matches!(origin, Origin::Orphan) {
TEMPLATE.extract(&author).map_err(Error::Io)?;
}
edit(&author).map_err(Error::Edit)?;
crate::facts::require_within_cap(&author)?;
descriptions::snapshot(data_root, &author).map_err(Error::Descriptions)?;
if !super::commit_checkout(git, &author, &commit_message(name, &origin)).map_err(Error::Git)? {
return Ok(Pass::Declined { target });
}
checkout.landed().map_err(Error::Git)?;
Ok(Pass::Landed)
}
fn require_source(workspace: &Path, origin: &Origin, git: &dyn GitRunner) -> Result<(), Error> {
let Origin::Fork { source } = origin else {
return Ok(());
};
workspace::require_lineage(workspace, source, git).map_err(Error::NoSuchLineage)
}
#[cfg(test)]
mod tests;
#[cfg(test)]
mod tests_descriptions;
#[cfg(test)]
mod tests_facts;
#[cfg(test)]
mod tests_lineage;
#[cfg(test)]
mod tests_stub;
#[cfg(test)]
mod tests_teardown;