use std::io::Write; use crate::{ bat, changelog::write::{Components, Linkables}, command::changelog::Options, git, traverse::dependency, utils::will, version::BumpSpec, ChangeLog,}; pub fn changelog(opts: Options, crates: Vec<String>) -> anyhow::Result<()> { let Options { generator_segments, dependencies, dry_run, preview, no_links, .. } = opts; let bump_spec = dependencies.then(|| BumpSpec::Auto).unwrap_or(BumpSpec::Keep); let force_history_segmentation = false; let ctx = crate::Context::new(crates.clone(), force_history_segmentation, bump_spec, bump_spec)?; let crates: Vec<_> = { crate::traverse::dependencies( &ctx, crate::traverse::Options { allow_auto_publish_of_stable_crates: true, bump_when_needed: true, isolate_dependencies_from_breaking_changes: true, traverse_graph: dependencies, }, )? .into_iter() .filter_map(|d| match d.mode { dependency::Mode::ToBePublished { .. } => Some(d.package), dependency::Mode::NotForPublishing { .. } => { if crates.contains(&d.package.name) { log::info!( "Skipping '{}' as it won't be published.{}", d.package.name, (!dependencies) .then(|| " Try not to specify --no-dependencies/--only.") .unwrap_or("") ); } None } }) .collect() }; assure_working_tree_is_unchanged(opts)?; let history = match git::history::collect(&ctx.repo)? { None => return Ok(()), Some(history) => history, }; let bat = (dry_run && preview).then(bat::Support::new); let mut pending_changes = Vec::new(); let linkables = if dry_run || no_links { Linkables::AsText } else { crate::git::remote_url()? .map(|url| Linkables::AsLinks { repository_url: url.into(), }) .unwrap_or(Linkables::AsText) }; let mut num_crates = 0; for (idx, package) in crates.iter().enumerate() { num_crates += 1; let crate::changelog::init::Outcome { log, mut lock, state, .. } = ChangeLog::for_package_with_write_lock(package, &history, &ctx, generator_segments)?; log::info!( "{} write {} sections to {} ({})", will(dry_run), log.sections.len(), lock.resource_path() .strip_prefix(&ctx.root) .expect("contained in workspace") .display(), state.as_str(), ); lock.with_mut(|file| { let mut buf = String::new(); log.write_to( &mut buf, &linkables, if dry_run { Components::SECTION_TITLE } else { Components::all() }, ) .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?; file.write_all(buf.as_bytes()) })?; if let Some(bat) = bat.as_ref() { bat.display_to_tty( lock.lock_path(), lock.resource_path().strip_prefix(&ctx.root.to_path_buf())?, format!("PREVIEW {} / {}, press Ctrl+C to cancel", idx + 1, crates.len()), )?; } if !dry_run { pending_changes.push(lock); } } if num_crates == 0 { anyhow::bail!( "The given crate{} {} didn't change and no changelog could be generated.", (ctx.crate_names.len() != 1).then(|| "s").unwrap_or(""), ctx.crate_names .iter() .map(|c| format!("'{}'", c)) .collect::<Vec<_>>() .join(", ") ) } let num_changes = pending_changes.len(); for change in pending_changes { change.commit()?; } if num_changes != 0 { log::info!("Wrote {} changelogs", num_changes); } Ok(())} fn assure_working_tree_is_unchanged(options: Options) -> anyhow::Result<()> { if options.allow_dirty { Ok(()) } else { crate::git::assure_clean_working_tree().or_else(|err| if options.dry_run { log::warn!("The working tree has changes which will prevent changelog updates with --write unless --allow-dirty is also specified. The latter isn't recommended."); Ok(()) } else { Err(err) }) }}
use std::io::Write;
use crate::{
bat,
changelog::write::{Components, Linkables},
command::changelog::Options,
git,
traverse::dependency,
utils::will,
version::BumpSpec,
ChangeLog,
};
pub fn changelog(opts: Options, crates: Vec<String>) -> anyhow::Result<()> {
let Options {
generator_segments,
dependencies,
dry_run,
preview,
no_links,
..
} = opts;
let bump_spec = dependencies.then(|| BumpSpec::Auto).unwrap_or(BumpSpec::Keep);
let force_history_segmentation = false;
let ctx = crate::Context::new(crates.clone(), force_history_segmentation, bump_spec, bump_spec)?;
let crates: Vec<_> = {
crate::traverse::dependencies(
&ctx,
crate::traverse::Options {
allow_auto_publish_of_stable_crates: true,
bump_when_needed: true,
isolate_dependencies_from_breaking_changes: true,
traverse_graph: dependencies,
},
)?
.into_iter()
.filter_map(|d| match d.mode {
dependency::Mode::ToBePublished { .. } => Some(d.package),
dependency::Mode::NotForPublishing { .. } => {
if crates.contains(&d.package.name) {
log::info!(
"Skipping '{}' as it won't be published.{}",
d.package.name,
(!dependencies)
.then(|| " Try not to specify --no-dependencies/--only.")
.unwrap_or("")
);
}
None
}
})
.collect()
};
assure_working_tree_is_unchanged(opts)?;
let history = match git::history::collect(&ctx.repo)? {
None => return Ok(()),
Some(history) => history,
};
let bat = (dry_run && preview).then(bat::Support::new);
let mut pending_changes = Vec::new();
let linkables = if dry_run || no_links {
Linkables::AsText
} else {
crate::git::remote_url()?
.map(|url| Linkables::AsLinks {
repository_url: url.into(),
})
.unwrap_or(Linkables::AsText)
};
let mut num_crates = 0;
for (idx, package) in crates.iter().enumerate() {
num_crates += 1;
let crate::changelog::init::Outcome {
log, mut lock, state, ..
} = ChangeLog::for_package_with_write_lock(package, &history, &ctx, generator_segments)?;
log::info!(
"{} write {} sections to {} ({})",
will(dry_run),
log.sections.len(),
lock.resource_path()
.strip_prefix(&ctx.root)
.expect("contained in workspace")
.display(),
state.as_str(),
);
lock.with_mut(|file| {
let mut buf = String::new();
log.write_to(
&mut buf,
&linkables,
if dry_run {
Components::SECTION_TITLE
} else {
Components::all()
},
)
.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;
file.write_all(buf.as_bytes())
})?;
if let Some(bat) = bat.as_ref() {
bat.display_to_tty(
lock.lock_path(),
lock.resource_path().strip_prefix(&ctx.root.to_path_buf())?,
format!("PREVIEW {} / {}, press Ctrl+C to cancel", idx + 1, crates.len()),
)?;
}
if !dry_run {
pending_changes.push(lock);
}
}
if num_crates == 0 {
anyhow::bail!(
"The given crate{} {} didn't change and no changelog could be generated.",
(ctx.crate_names.len() != 1).then(|| "s").unwrap_or(""),
ctx.crate_names
.iter()
.map(|c| format!("'{}'", c))
.collect::<Vec<_>>()
.join(", ")
)
}
let num_changes = pending_changes.len();
for change in pending_changes {
change.commit()?;
}
if num_changes != 0 {
log::info!("Wrote {} changelogs", num_changes);
}
Ok(())
}
fn assure_working_tree_is_unchanged(options: Options) -> anyhow::Result<()> {
if options.allow_dirty {
Ok(())
} else {
crate::git::assure_clean_working_tree().or_else(|err|
if options.dry_run {
log::warn!("The working tree has changes which will prevent changelog updates with --write unless --allow-dirty is also specified. The latter isn't recommended.");
Ok(())
} else {
Err(err)
})
}
}