use cargo_metadata::Package;
use semver::{Prerelease, Version};
use crate::Context;
#[derive(Copy, Clone)]
pub enum BumpSpec {
Auto,
Keep,
Patch,
Minor,
Major,
}
impl std::fmt::Display for BumpSpec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
BumpSpec::Auto => "auto",
BumpSpec::Keep => "no",
BumpSpec::Patch => "patch",
BumpSpec::Minor => "minor",
BumpSpec::Major => "major",
})
}
}
#[allow(clippy::ptr_arg)]
pub(crate) fn select_publishee_bump_spec(name: &String, ctx: &Context) -> BumpSpec {
if ctx.crate_names.contains(name) {
ctx.bump
} else {
ctx.bump_dependencies
}
}
fn bump_major_minor_patch(v: &mut semver::Version, bump_spec: BumpSpec) -> bool {
use BumpSpec::*;
match bump_spec {
Major => {
v.major += 1;
v.minor = 0;
v.patch = 0;
v.pre = Prerelease::EMPTY;
true
}
Minor => {
v.minor += 1;
v.patch = 0;
v.pre = Prerelease::EMPTY;
is_pre_release(v)
}
Patch => {
v.patch += 1;
v.pre = Prerelease::EMPTY;
false
}
Keep | Auto => unreachable!("BUG: auto mode or keep are unsupported"),
}
}
#[derive(Clone, Debug)]
pub struct Bump {
pub next_release: semver::Version,
pub package_version: semver::Version,
pub latest_release: Option<semver::Version>,
pub desired_release: semver::Version,
}
impl Bump {
pub(crate) fn next_release_changes_manifest(&self) -> bool {
self.next_release > self.package_version
}
pub(crate) fn is_breaking(&self) -> bool {
rhs_is_breaking_bump_for_lhs(&self.package_version, &self.next_release)
}
}
pub(crate) fn bump_package_with_spec(
package: &Package,
bump_spec: BumpSpec,
ctx: &Context,
bump_when_needed: bool,
) -> anyhow::Result<Bump> {
let mut v = package.version.clone();
use BumpSpec::*;
let package_version_must_be_breaking = match bump_spec {
Major | Minor | Patch => bump_major_minor_patch(&mut v, bump_spec),
Keep => false,
Auto => {
let segments = crate::git::history::crate_ref_segments(
package,
ctx,
ctx.history.as_ref().expect("BUG: assure history is set here"),
crate::git::history::SegmentScope::Unreleased,
)?;
assert_eq!(
segments.len(),
1,
"there should be exactly one section, the 'unreleased' one"
);
let unreleased = &segments[0];
if unreleased.history.is_empty() {
false
} else if unreleased.history.iter().any(|item| item.message.breaking) {
let is_breaking = if is_pre_release(&v) {
bump_major_minor_patch(&mut v, Minor)
} else {
bump_major_minor_patch(&mut v, Major)
};
assert!(is_breaking, "BUG: breaking changes areā¦breaking :D");
is_breaking
} else if unreleased
.history
.iter()
.any(|item| item.message.kind.map(|kind| kind == "feat").unwrap_or(false))
{
let is_breaking = if is_pre_release(&v) {
bump_major_minor_patch(&mut v, Patch)
} else {
bump_major_minor_patch(&mut v, Minor)
};
assert!(!is_breaking, "BUG: new features are never breaking");
is_breaking
} else {
let is_breaking = bump_major_minor_patch(&mut v, Patch);
assert!(!is_breaking, "BUG: patch releases are never breaking");
false
}
}
};
let desired_release = v;
let (latest_release, next_release) = match ctx.crates_index.crate_(&package.name) {
Some(published_crate) => {
let latest_release = semver::Version::parse(published_crate.most_recent_version().version())
.expect("valid version in crate index");
let next_release = if latest_release >= desired_release {
desired_release.clone()
} else {
let mut next_release = desired_release.clone();
if bump_when_needed && package.version > latest_release && desired_release != package.version {
if package_version_must_be_breaking {
if rhs_is_breaking_bump_for_lhs(&latest_release, &package.version) {
next_release = package.version.clone();
}
} else {
next_release = package.version.clone();
};
}
next_release
};
(Some(latest_release), next_release)
}
None => (
None,
if bump_when_needed {
package.version.clone()
} else {
desired_release.clone()
},
),
};
Ok(Bump {
next_release,
package_version: package.version.clone(),
desired_release,
latest_release,
})
}
pub(crate) fn bump_package(package: &Package, ctx: &Context, bump_when_needed: bool) -> anyhow::Result<Bump> {
let bump_spec = select_publishee_bump_spec(&package.name, ctx);
bump_package_with_spec(package, bump_spec, ctx, bump_when_needed)
}
pub(crate) fn is_pre_release(semver: &Version) -> bool {
crate::utils::is_pre_release_version(semver)
}
pub(crate) fn rhs_is_breaking_bump_for_lhs(lhs: &Version, rhs: &Version) -> bool {
rhs.major > lhs.major || rhs.minor > lhs.minor
}