Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: handle General/Complex Versioning in --bump #2889

Merged
merged 6 commits into from
Nov 29, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 65 additions & 37 deletions src/toolset/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub use tool_request_set::{ToolRequestSet, ToolRequestSetBuilder};
pub use tool_source::ToolSource;
pub use tool_version::{ResolveOptions, ToolVersion};
pub use tool_version_list::ToolVersionList;
use versions::{Version, Versioning};
use versions::{Mess, Version, Versioning};
use xx::regex;

mod builder;
Expand Down Expand Up @@ -764,53 +764,61 @@ pub fn is_outdated_version(current: &str, latest: &str) -> bool {
/// used with `mise outdated --bump` to determine what new semver range to use
/// given old: "20" and new: "21.2.3", return Some("21")
fn check_semver_bump(old: &str, new: &str) -> Option<String> {
if !old.contains('.') && !new.contains('.') {
return Some(new.to_string());
}
let old_v = Versioning::new(old);
let new_v = Versioning::new(new);
let chunkify = |v: &Versioning| {
let mut chunks = vec![];
while let Some(chunk) = v.nth(chunks.len()) {
chunks.push(chunk);
}
chunks
};
if let (Some(old), Some(new)) = (old_v, new_v) {
let old = chunkify(&old);
let new = chunkify(&new);
if old.len() > new.len() {
if let Some(("prefix", old_)) = old.split_once(':') {
return check_semver_bump(old_, new);
}
let old_chunks = chunkify_version(old);
let new_chunks = chunkify_version(new);
if !old_chunks.is_empty() && !new_chunks.is_empty() {
if old_chunks.len() > new_chunks.len() {
warn!(
"something weird happened with versioning, old: {old}, new: {new}, skipping",
old = old
.iter()
.map(|c| c.to_string())
.collect::<Vec<_>>()
.join("."),
new = new
.iter()
.map(|c| c.to_string())
.collect::<Vec<_>>()
.join("."),
"something weird happened with versioning, old: {old:?}, new: {new:?}",
old = old_chunks,
new = new_chunks,
);
return None;
}
let bump = new.into_iter().take(old.len()).collect::<Vec<_>>();
if bump == old {
let bump = new_chunks
.into_iter()
.take(old_chunks.len())
.collect::<Vec<_>>();
if bump == old_chunks {
None
} else {
Some(
bump.iter()
.map(|c| c.to_string())
.collect::<Vec<_>>()
.join("."),
)
Some(bump.join(""))
}
} else {
Some(new.to_string())
}
}

/// split a version number into chunks
/// given v: "1.2-3a4" return ["1", ".2", "-3", "a4"]
fn chunkify_version(v: &str) -> Vec<String> {
fn chunkify(m: &Mess, sep0: &str, chunks: &mut Vec<String>) {
for (i, chunk) in m.chunks.iter().enumerate() {
let sep = if i == 0 { sep0 } else { "." };
chunks.push(format!("{}{}", sep, chunk));
}
if let Some((next_sep, next_mess)) = &m.next {
chunkify(next_mess, next_sep.to_string().as_ref(), chunks)
}
}

let mut chunks = vec![];
// don't parse "latest", otherwise bump from latest to any version would have one chunk only
if v != "latest" {
if let Some(v) = Versioning::new(v) {
let m = match v {
Versioning::Ideal(sem_ver) => sem_ver.to_mess(),
Versioning::General(version) => version.to_mess(),
Versioning::Complex(mess) => mess,
};
chunkify(&m, "", &mut chunks);
}
}
chunks
}

#[derive(Debug, Serialize, Clone, Tabled)]
pub struct OutdatedInfo {
pub name: String,
Expand Down Expand Up @@ -921,10 +929,30 @@ mod tests {
check_semver_bump("20.0.0", "20.0.1"),
Some("20.0.1".to_string())
);
std::assert_eq!(
check_semver_bump("20.0.1", "20.1"),
Some("20.1".to_string())
);
std::assert_eq!(
check_semver_bump("2024-09-16", "2024-10-21"),
Some("2024-10-21".to_string())
);
std::assert_eq!(
check_semver_bump("20.0a1", "20.0a2"),
Some("20.0a2".to_string())
);
std::assert_eq!(check_semver_bump("v20", "v20.0.0"), None);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you actually seeing v prefixed versions somewhere? we should be removing that prefix everywhere we can by this point

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're in my config so I thought it'd be nice if mise upgrade --bump didn't choke on them. I can remove them manually but it'd be nice if others didn't need to.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But you've got a point that the test cases don't reflect reality. There might be a "v" in old but not in new.

std::assert_eq!(check_semver_bump("v20.0", "v20.0.0"), None);
std::assert_eq!(check_semver_bump("v20.0.0", "v20.0.0"), None);
std::assert_eq!(check_semver_bump("v20", "v21.0.0"), Some("v21".to_string()));
std::assert_eq!(
check_semver_bump("v20.0.0", "v20.0.1"),
Some("v20.0.1".to_string())
);
std::assert_eq!(
check_semver_bump("latest", "20.0.0"),
Some("20.0.0".to_string())
);
}

#[test]
Expand Down