use camino::Utf8Path;
use crate::diagnostic::{Diagnostic, Reason};
use crate::error::RkError;
fn run(root: &Utf8Path, args: &[&str]) -> Result<String, RkError> {
let mut command = std::process::Command::new(crate::probes::git_cliff_bin());
for var in crate::maintenance::GIT_HOOK_VARS {
command.env_remove(var);
}
let output = command
.args(args)
.current_dir(root.as_std_path())
.output()
.map_err(|source| {
RkError::refusal(
Diagnostic::new(
Reason::PrerequisiteUnmet,
format!("git-cliff did not run: {source}"),
)
.expected("git-cliff on PATH at the version versions.toml pins")
.action("install git-cliff at its pin, or put it in the recorded development environment")
.target_state("unchanged"),
)
})?;
if !output.status.success() {
return Err(RkError::subprocess(
Diagnostic::new(
Reason::SubprocessFailed,
format!(
"git-cliff {} failed: {}",
args.join(" "),
super::last_line(&output.stderr)
),
)
.expected("git-cliff to read the landed cliff.toml and the history"),
));
}
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
}
#[must_use]
pub fn pin() -> Option<String> {
crate::registry::version_of("git-cliff")
}
pub fn require_pin(root: &Utf8Path) -> Result<String, RkError> {
let pinned = pin().unwrap_or_default();
let answer = run(root, &["--version"])?;
let found = crate::probes::parse_cli_version(&answer);
let wanted = crate::probes::parse_cli_version(&pinned);
if found.is_none() || found != wanted {
return Err(RkError::refusal(
Diagnostic::new(
Reason::ToolOffPin,
format!(
"git-cliff reports {:?}, and versions.toml pins {pinned}",
answer.trim()
),
)
.expected(format!("git-cliff {pinned}"))
.action(format!(
"install git-cliff {pinned}, or move the recorded development environment to it"
))
.target_state("unchanged"),
));
}
Ok(pinned)
}
fn include(paths: &[String]) -> Vec<String> {
paths
.iter()
.flat_map(|path| {
[
"--include-path".to_owned(),
super::packed::cliff_pattern(path),
]
})
.collect()
}
fn run_on(root: &Utf8Path, args: &[&str], paths: &[String]) -> Result<String, RkError> {
let filter = include(paths);
let mut all: Vec<&str> = args.to_vec();
all.extend(filter.iter().map(String::as_str));
run(root, &all)
}
pub fn bumped(root: &Utf8Path, paths: &[String]) -> Result<String, RkError> {
let answer = run_on(root, &["--bumped-version"], paths)?;
Ok(answer
.lines()
.rev()
.find(|line| !line.trim().is_empty())
.unwrap_or_default()
.trim()
.to_owned())
}
pub fn changelog(root: &Utf8Path, tag: &str, paths: &[String]) -> Result<(), RkError> {
let write = if root.join("CHANGELOG.md").exists() {
"--prepend"
} else {
"-o"
};
run_on(
root,
&["--unreleased", "--tag", tag, write, "CHANGELOG.md"],
paths,
)
.map(drop)
}
pub fn notes(root: &Utf8Path, tag: &str, out: &Utf8Path, paths: &[String]) -> Result<(), RkError> {
run_on(
root,
&["--unreleased", "--tag", tag, "-o", out.as_str()],
paths,
)
.map(drop)
}