2023-07-30 17:24:33 +02:00
|
|
|
use conventional_commit_parser::commit::{CommitType, ConventionalCommit};
|
|
|
|
use semver::Version;
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
|
|
pub enum VersionIncrement {
|
|
|
|
Major,
|
|
|
|
Minor,
|
|
|
|
Patch,
|
|
|
|
Prerelease,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl VersionIncrement {
|
|
|
|
pub fn from<C>(cur_version: &Version, commits: C) -> Option<Self>
|
|
|
|
where
|
|
|
|
C: IntoIterator,
|
|
|
|
C::Item: AsRef<str>,
|
|
|
|
{
|
|
|
|
let mut commits = commits.into_iter().peekable();
|
2023-07-31 13:34:23 +02:00
|
|
|
commits.peek()?;
|
2023-07-30 17:24:33 +02:00
|
|
|
if let Some(prerelease) = Self::is_prerelease(cur_version) {
|
|
|
|
return Some(prerelease);
|
|
|
|
}
|
|
|
|
|
|
|
|
let commits: Vec<ConventionalCommit> = Self::parse_commits::<C>(commits);
|
|
|
|
|
2023-07-31 13:34:23 +02:00
|
|
|
Some(Self::from_conventional_commits(commits))
|
2023-07-30 17:24:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn parse_commits<C>(
|
|
|
|
commits: std::iter::Peekable<<C as IntoIterator>::IntoIter>,
|
|
|
|
) -> Vec<ConventionalCommit>
|
|
|
|
where
|
|
|
|
C: IntoIterator,
|
|
|
|
C::Item: AsRef<str>,
|
|
|
|
{
|
|
|
|
commits
|
|
|
|
.filter_map(|c| conventional_commit_parser::parse(c.as_ref()).ok())
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find most significant change
|
|
|
|
fn from_conventional_commits(commits: Vec<ConventionalCommit>) -> VersionIncrement {
|
|
|
|
let found_breaking = || commits.iter().any(|c| c.is_breaking_change);
|
|
|
|
let found_feature = || {
|
|
|
|
commits
|
|
|
|
.iter()
|
|
|
|
.any(|c| matches!(c.commit_type, CommitType::Feature))
|
|
|
|
};
|
|
|
|
|
|
|
|
match (found_breaking(), found_feature()) {
|
|
|
|
(true, _) => Self::Major,
|
|
|
|
(_, true) => Self::Minor,
|
|
|
|
(_, false) => Self::Patch,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_prerelease(cur_version: &Version) -> Option<VersionIncrement> {
|
|
|
|
if !cur_version.pre.is_empty() {
|
|
|
|
return Some(Self::Prerelease);
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use crate::versioning::conventional_parse::VersionIncrement;
|
|
|
|
use semver::Version;
|
|
|
|
use tracing_test::traced_test;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[traced_test]
|
|
|
|
fn is_prerelease() {
|
2023-07-30 21:12:16 +02:00
|
|
|
let version = Version::parse("0.0.0-alpha.1").unwrap();
|
2023-07-30 17:24:33 +02:00
|
|
|
|
|
|
|
let commits = vec![
|
|
|
|
"feat: something",
|
|
|
|
"fix: something",
|
|
|
|
"feat(something): something",
|
|
|
|
"feat(breaking): some
|
|
|
|
|
|
|
|
BREAKING CHANGE: something",
|
|
|
|
];
|
|
|
|
|
|
|
|
let actual = VersionIncrement::from(&version, commits).unwrap();
|
|
|
|
assert_eq!(VersionIncrement::Prerelease, actual);
|
|
|
|
}
|
|
|
|
#[test]
|
|
|
|
#[traced_test]
|
|
|
|
fn is_patch() {
|
2023-07-30 21:12:16 +02:00
|
|
|
let version = Version::parse("0.0.1").unwrap();
|
2023-07-30 17:24:33 +02:00
|
|
|
|
|
|
|
let commits = vec![
|
|
|
|
"fix: something",
|
|
|
|
"fix: something",
|
|
|
|
"fix: something",
|
|
|
|
"fix: something",
|
|
|
|
"fix: something",
|
|
|
|
"fix: something",
|
|
|
|
];
|
|
|
|
|
|
|
|
let actual = VersionIncrement::from(&version, commits).unwrap();
|
|
|
|
assert_eq!(VersionIncrement::Patch, actual);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[traced_test]
|
|
|
|
fn is_minor() {
|
2023-07-30 21:12:16 +02:00
|
|
|
let version = Version::parse("0.1.0").unwrap();
|
2023-07-30 17:24:33 +02:00
|
|
|
|
|
|
|
let commits = vec![
|
|
|
|
"feat: something",
|
|
|
|
"feat: something",
|
|
|
|
"fix: something",
|
|
|
|
"fix: something",
|
|
|
|
"fix: something",
|
|
|
|
"fix: something",
|
|
|
|
];
|
|
|
|
|
|
|
|
let actual = VersionIncrement::from(&version, commits).unwrap();
|
|
|
|
assert_eq!(VersionIncrement::Minor, actual);
|
|
|
|
}
|
|
|
|
#[test]
|
|
|
|
#[traced_test]
|
|
|
|
fn is_major() {
|
2023-07-30 21:12:16 +02:00
|
|
|
let version = Version::parse("0.1.0").unwrap();
|
2023-07-30 17:24:33 +02:00
|
|
|
|
|
|
|
let commits = vec![
|
|
|
|
"feat: something",
|
|
|
|
"feat: something
|
|
|
|
|
|
|
|
BREAKING CHANGE: something",
|
|
|
|
"fix: something",
|
|
|
|
"fix: something",
|
|
|
|
"fix: something",
|
|
|
|
"fix: something",
|
|
|
|
];
|
|
|
|
|
|
|
|
let actual = VersionIncrement::from(&version, commits).unwrap();
|
|
|
|
assert_eq!(VersionIncrement::Major, actual);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[traced_test]
|
|
|
|
fn chore_is_patch() {
|
2023-07-30 21:12:16 +02:00
|
|
|
let version = Version::parse("0.1.0").unwrap();
|
2023-07-30 17:24:33 +02:00
|
|
|
|
|
|
|
let commits = vec!["chore: something"];
|
|
|
|
|
|
|
|
let actual = VersionIncrement::from(&version, commits).unwrap();
|
|
|
|
assert_eq!(VersionIncrement::Patch, actual);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[traced_test]
|
|
|
|
fn refactor_is_patch() {
|
2023-07-30 21:12:16 +02:00
|
|
|
let version = Version::parse("0.1.0").unwrap();
|
2023-07-30 17:24:33 +02:00
|
|
|
|
|
|
|
let commits = vec!["refactor: something"];
|
|
|
|
|
|
|
|
let actual = VersionIncrement::from(&version, commits).unwrap();
|
|
|
|
assert_eq!(VersionIncrement::Patch, actual);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[traced_test]
|
|
|
|
fn unknown_commits_are_patch() {
|
2023-07-30 21:12:16 +02:00
|
|
|
let version = Version::parse("0.1.0").unwrap();
|
2023-07-30 17:24:33 +02:00
|
|
|
|
|
|
|
let commits = vec!["blablabla some commit"];
|
|
|
|
|
|
|
|
let actual = VersionIncrement::from(&version, commits).unwrap();
|
|
|
|
assert_eq!(VersionIncrement::Patch, actual);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[traced_test]
|
|
|
|
fn nothing_returns_none() {
|
2023-07-30 21:12:16 +02:00
|
|
|
let version = Version::parse("0.1.0").unwrap();
|
2023-07-30 17:24:33 +02:00
|
|
|
|
|
|
|
let commits: Vec<&str> = Vec::new();
|
|
|
|
|
|
|
|
let actual = VersionIncrement::from(&version, commits).is_none();
|
2023-07-31 13:34:23 +02:00
|
|
|
assert!(actual);
|
2023-07-30 17:24:33 +02:00
|
|
|
}
|
|
|
|
}
|