253 lines · 8.8 KB · Rust
RawDownload
use anyhow::bail;use cargo_metadata::Package;use semver::{BuildMetadata, Prerelease, Version}; use super::Context;use crate::command::release::{BumpSpec, Options}; #[allow(clippy::ptr_arg)]pub(crate) fn select_publishee_bump_spec(name: &String, ctx: &Context) -> BumpSpec {    if ctx.base.crate_names.contains(name) {        ctx.bump    } else {        ctx.bump_dependencies    }} /// Returns true if this would be a breaking change for `v`.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"),    }} pub(crate) fn bump(    publishee: &Package,    bump_spec: BumpSpec,    ctx: &Context,    Options { bump_when_needed, .. }: &Options,) -> anyhow::Result<Version> {    let mut v = publishee.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(                publishee,                &ctx.base,                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() {                log::info!(                    "{}: no changes since the last release. Version unchanged",                    publishee.name                );                false            } else if unreleased.history.iter().any(|item| item.message.breaking) {                let (is_breaking, level) = if is_pre_release(&v) {                    (bump_major_minor_patch(&mut v, Minor), "minor")                } else {                    (bump_major_minor_patch(&mut v, Major), "major")                };                assert!(is_breaking, "BUG: breaking changes are…breaking :D");                log::info!(                    "Auto-bumped '{}' {} version to {} from {} to signal breaking changes.",                    publishee.name,                    level,                    v,                    publishee.version                );                is_breaking            } else if unreleased                .history                .iter()                .any(|item| item.message.kind.map(|kind| kind == "feat").unwrap_or(false))            {                let (is_breaking, level) = if is_pre_release(&v) {                    (bump_major_minor_patch(&mut v, Patch), "patch")                } else {                    (bump_major_minor_patch(&mut v, Minor), "minor")                };                assert!(!is_breaking, "BUG: new features are never breaking");                log::info!(                    "Auto-bumped '{}' {} version to {} from {} to signal new features due to 'feat:' in commit message.",                    publishee.name,                    level,                    v,                    publishee.version                );                is_breaking            } else {                let is_breaking = bump_major_minor_patch(&mut v, Patch);                assert!(!is_breaking, "BUG: patch releases are never breaking");                log::info!(                    "Auto-bumped '{}' patch version to {} from {}.",                    publishee.name,                    v,                    publishee.version                );                false            }        }    };     let new_version = v;    let verbose = true;    let assume_it_will_be_published_for_log_messages = true;    smallest_necessary_version_relative_to_crates_index(        publishee,        new_version,        ctx,        *bump_when_needed,        verbose,        assume_it_will_be_published_for_log_messages,        package_version_must_be_breaking,    )} fn smallest_necessary_version_relative_to_crates_index(    package: &Package,    mut new_version: Version,    ctx: &Context,    bump_when_needed: bool,    verbose: bool,    will_be_published: bool,    package_version_must_be_breaking: bool,) -> anyhow::Result<Version> {    match ctx.crates_index.crate_(&package.name) {        Some(published_crate) => {            let latest_published_version = semver::Version::parse(published_crate.latest_version().version())?;            if latest_published_version >= new_version {                bail!(                    "Latest published version of '{}' is {}, the new version is {}. Consider using --bump <level> or --bump-dependencies <level> or update the index with --update-crates-index.",                    package.name,                    published_crate.latest_version().version(),                    new_version                );            }            if bump_when_needed && package.version > latest_published_version && new_version != package.version {                let verbose = if package_version_must_be_breaking {                    if rhs_is_breaking_bump_for_lhs(&latest_published_version, &package.version) {                        new_version = package.version.clone();                        verbose                    } else {                        false                    }                } else {                    new_version = package.version.clone();                    verbose                };                if new_version > package.version {                    if verbose {                        log::info!(                            "Using manifest version {} of crate '{}' instead of new version {} as it is sufficient to succeed latest published version {}.",                            package.version,                            package.name,                            new_version,                            latest_published_version                        );                    }                } else if verbose {                    log::info!(                        "Using manifest version {} of crate '{}' as it is sufficient to succeed latest published version {}.",                        package.version,                        package.name,                        latest_published_version                    );                }            }        }        None => {            if bump_when_needed && new_version > package.version {                if verbose {                    log::info!(                        "Using current version {} of crate {} instead of bumped one {}.",                        package.version,                        package.name,                        new_version                    );                }                new_version = package.version.clone();            }            if verbose && will_be_published {                log::info!("Congratulations for the new release of '{}' 🎉", package.name);            }        }    };    Ok(new_version)} pub(crate) fn is_pre_release(semver: &Version) -> bool {    crate::utils::is_pre_release_version(semver)} pub(crate) fn conservative_dependent_version(    publishee: &Package,    new_publishee_version: &str,    dependent: &Package,    ctx: &Context,    bump_when_needed: bool,    verbose: bool,) -> Option<Version> {    let new_publishee_version: Version = new_publishee_version.parse().expect("new versions are always valid");    if !rhs_is_breaking_bump_for_lhs(&publishee.version, &new_publishee_version) {        return None;    }    let new_dependent_version = breaking_version_bump(&dependent.version);    smallest_necessary_version_relative_to_crates_index(        dependent,        new_dependent_version,        ctx,        bump_when_needed,        verbose,        false,        true,    )    .ok()} fn breaking_version_bump(v: &Version) -> Version {    let (major, minor, patch) = match (v.major, v.minor, v.patch) {        (0, 0, patch) => (0, 0, patch + 1),        (0, minor, _) => (0, minor + 1, 0),        (major, minor, _) => (major, minor + 1, 0),    };    Version {        major,        minor,        patch,        pre: Prerelease::EMPTY,        build: BuildMetadata::EMPTY,    }} pub(crate) fn rhs_is_breaking_bump_for_lhs(lhs: &Version, rhs: &Version) -> bool {    rhs.major > lhs.major || rhs.minor > lhs.minor}