diff --git a/src/bin/upgrade/upgrade.rs b/src/bin/upgrade/upgrade.rs index f85817cc0f..c2d54c0c5b 100644 --- a/src/bin/upgrade/upgrade.rs +++ b/src/bin/upgrade/upgrade.rs @@ -6,7 +6,7 @@ use std::path::PathBuf; use anyhow::Context as _; use cargo_edit::{ CargoResult, CertsSource, CrateSpec, Dependency, IndexCache, LocalManifest, RustVersion, - Source, get_compatible_dependency, get_latest_dependency, registry_url, set_dep_version, + Source, find_compatible_version, find_latest_version, registry_url, set_dep_version, shell_note, shell_status, shell_warn, shell_write_stdout, }; use clap::Args; @@ -290,31 +290,31 @@ fn exec(args: UpgradeArgs) -> CargoResult<()> { // we're offline. let registry_url = registry_url(&manifest_path, dependency.registry())?; let index = index.index(®istry_url)?; + let krate = index.krate(&dependency.name)?; + let versions = krate + .as_ref() + .map(|k| k.versions.as_slice()) + .unwrap_or_default(); + let is_prerelease = old_version_req.contains('-'); + let latest_compatible = VersionReq::parse(&old_version_req) .ok() .and_then(|old_version_req| { - get_compatible_dependency( - &dependency.name, - &old_version_req, - rust_version, - index, - ) - .ok() + find_compatible_version(versions, &old_version_req, rust_version) }) .map(|d| { d.version() .expect("registry packages always have a version") .to_owned() }); - let is_prerelease = old_version_req.contains('-'); - let latest_version = - get_latest_dependency(&dependency.name, is_prerelease, rust_version, index) - .map(|d| { - d.version() - .expect("registry packages always have a version") - .to_owned() - }) - .ok(); + + let latest_version = find_latest_version(versions, is_prerelease, rust_version) + .map(|d| { + d.version() + .expect("registry packages always have a version") + .to_owned() + }); + let latest_incompatible = if latest_version != latest_compatible { latest_version } else { diff --git a/src/errors.rs b/src/errors.rs index bc8c274928..fd504a0b64 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -66,10 +66,6 @@ impl From for CliError { } } -pub(crate) fn no_crate_err(name: impl Display) -> Error { - anyhow::format_err!("The crate `{}` could not be found in registry index.", name) -} - pub(crate) fn non_existent_table_err(table: impl Display) -> Error { anyhow::format_err!("The table `{}` could not be found.", table) } diff --git a/src/fetch.rs b/src/fetch.rs index 572b823621..7a31b8c0a6 100644 --- a/src/fetch.rs +++ b/src/fetch.rs @@ -1,61 +1,6 @@ -use super::AnyIndexCache; use super::Dependency; use super::RegistrySource; use super::VersionExt; -use super::errors::*; - -/// Query latest version from a registry index -/// -/// The registry argument must be specified for crates -/// from alternative registries. -/// -/// The latest version will be returned as a `Dependency`. This will fail, when -/// -/// - there is no Internet connection and offline is false. -/// - summaries in registry index with an incorrect format. -/// - a crate with the given name does not exist on the registry. -pub fn get_latest_dependency( - crate_name: &str, - flag_allow_prerelease: bool, - rust_version: Option, - index: &mut AnyIndexCache, -) -> CargoResult { - if crate_name.is_empty() { - anyhow::bail!("Found empty crate name"); - } - - let crate_versions = fuzzy_query_registry_index(crate_name, index)?; - - let dep = read_latest_version(&crate_versions, flag_allow_prerelease, rust_version)?; - - if dep.name != crate_name { - eprintln!("WARN: Added `{}` instead of `{}`", dep.name, crate_name); - } - - Ok(dep) -} - -/// Find the highest version compatible with a version req -pub fn get_compatible_dependency( - crate_name: &str, - version_req: &semver::VersionReq, - rust_version: Option, - index: &mut AnyIndexCache, -) -> CargoResult { - if crate_name.is_empty() { - anyhow::bail!("Found empty crate name"); - } - - let crate_versions = fuzzy_query_registry_index(crate_name, index)?; - - let dep = read_compatible_version(&crate_versions, version_req, rust_version)?; - - if dep.name != crate_name { - eprintln!("WARN: Added `{}` instead of `{}`", dep.name, crate_name); - } - - Ok(dep) -} /// Simplified represetation of `package.rust-version` #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] @@ -137,266 +82,62 @@ impl From<&'_ semver::Version> for RustVersion { } } -#[derive(Debug)] -struct CrateVersion { - name: String, - version: semver::Version, - rust_version: Option, - yanked: bool, -} - -/// Fuzzy query crate from registry index -fn fuzzy_query_registry_index( - crate_name: impl Into, - index: &mut AnyIndexCache, -) -> CargoResult> { - let crate_name = crate_name.into(); - let mut names = gen_fuzzy_crate_names(crate_name.clone())?; - if let Some(index) = names.iter().position(|x| *x == crate_name) { - // ref: https://github.com/killercup/cargo-edit/pull/317#discussion_r307365704 - names.swap(index, 0); - } - - for the_name in names { - let krate = match index.krate(&the_name) { - Ok(Some(krate)) => krate, - _ => continue, - }; - return krate - .versions - .iter() - .map(|v| { - Ok(CrateVersion { - name: v.name.to_string(), - version: v.version.as_str().parse()?, - rust_version: v.rust_version.as_ref().map(|r| r.parse()).transpose()?, - yanked: v.yanked, - }) - }) - .collect(); - } - Err(no_crate_err(crate_name)) -} - -/// Generate all similar crate names -/// -/// Examples: -/// -/// | input | output | -/// | ----- | ------ | -/// | cargo | cargo | -/// | cargo-edit | cargo-edit, cargo_edit | -/// | parking_lot_core | parking_lot_core, parking_lot-core, parking-lot_core, parking-lot-core | -fn gen_fuzzy_crate_names(crate_name: String) -> CargoResult> { - const PATTERN: [u8; 2] = [b'-', b'_']; - - let wildcard_indexs = crate_name - .bytes() - .enumerate() - .filter(|(_, item)| PATTERN.contains(item)) - .map(|(index, _)| index) - .take(10) - .collect::>(); - if wildcard_indexs.is_empty() { - return Ok(vec![crate_name]); - } - - let mut result = vec![]; - let mut bytes = crate_name.into_bytes(); - for mask in 0..2u128.pow(wildcard_indexs.len() as u32) { - for (mask_index, wildcard_index) in wildcard_indexs.iter().enumerate() { - let mask_value = (mask >> mask_index) & 1 == 1; - if mask_value { - bytes[*wildcard_index] = b'-'; - } else { - bytes[*wildcard_index] = b'_'; - } - } - result.push(String::from_utf8(bytes.clone()).unwrap()); - } - Ok(result) -} - // Checks whether a version object is a stable release -fn version_is_stable(version: &CrateVersion) -> bool { - !version.version.is_prerelease() +fn version_is_stable(version: &semver::Version) -> bool { + !version.is_prerelease() } /// Read latest version from Versions structure -fn read_latest_version( - versions: &[CrateVersion], +pub fn find_latest_version( + versions: &[tame_index::IndexVersion], flag_allow_prerelease: bool, rust_version: Option, -) -> CargoResult { - let latest = versions +) -> Option { + let (latest, _) = versions .iter() - .filter(|&v| flag_allow_prerelease || version_is_stable(v)) - .filter(|&v| !v.yanked) - .filter(|&v| { + .filter_map(|k| Some((k, k.version.parse::().ok()?))) + .filter(|(_, v)| flag_allow_prerelease || version_is_stable(v)) + .filter(|(k, _)| !k.yanked) + .filter(|(k, _)| { rust_version .and_then(|rust_version| { - v.rust_version - .map(|v_rust_version| v_rust_version <= rust_version) + k.rust_version + .as_ref() + .and_then(|k_rust_version| k_rust_version.parse::().ok()) + .map(|k_rust_version| k_rust_version <= rust_version) }) .unwrap_or(true) }) - .max_by_key(|&v| v.version.clone()) - .ok_or_else(|| { - anyhow::format_err!( - "No available versions exist. Either all were yanked \ - or only prerelease versions exist. Trying with the \ - --allow-prerelease flag might solve the issue." - ) - })?; + .max_by_key(|(_, v)| v.clone())?; let name = &latest.name; let version = latest.version.to_string(); - Ok(Dependency::new(name).set_source(RegistrySource::new(version))) + Some(Dependency::new(name).set_source(RegistrySource::new(version))) } -fn read_compatible_version( - versions: &[CrateVersion], +pub fn find_compatible_version( + versions: &[tame_index::IndexVersion], version_req: &semver::VersionReq, rust_version: Option, -) -> CargoResult { - let latest = versions +) -> Option { + let (latest, _) = versions .iter() - .filter(|&v| version_req.matches(&v.version)) - .filter(|&v| !v.yanked) - .filter(|&v| { + .filter_map(|k| Some((k, k.version.parse::().ok()?))) + .filter(|(_, v)| version_req.matches(v)) + .filter(|(k, _)| !k.yanked) + .filter(|(k, _)| { rust_version .and_then(|rust_version| { - v.rust_version - .map(|v_rust_version| v_rust_version <= rust_version) + k.rust_version + .as_ref() + .and_then(|k_rust_version| k_rust_version.parse::().ok()) + .map(|k_rust_version| k_rust_version <= rust_version) }) .unwrap_or(true) }) - .max_by_key(|&v| v.version.clone()) - .ok_or_else(|| { - anyhow::format_err!( - "No available versions exist. Either all were yanked \ - or only prerelease versions exist. Trying with the \ - --allow-prerelease flag might solve the issue." - ) - })?; + .max_by_key(|(_, v)| v.clone())?; let name = &latest.name; let version = latest.version.to_string(); - Ok(Dependency::new(name).set_source(RegistrySource::new(version))) -} - -#[test] -fn test_gen_fuzzy_crate_names() { - fn test_helper(input: &str, expect: &[&str]) { - let mut actual = gen_fuzzy_crate_names(input.to_string()).unwrap(); - actual.sort(); - - let mut expect = expect.iter().map(|x| x.to_string()).collect::>(); - expect.sort(); - - assert_eq!(actual, expect); - } - - test_helper("", &[""]); - test_helper("-", &["_", "-"]); - test_helper("DCjanus", &["DCjanus"]); - test_helper("DC-janus", &["DC-janus", "DC_janus"]); - test_helper( - "DC-_janus", - &["DC__janus", "DC_-janus", "DC-_janus", "DC--janus"], - ); -} - -#[test] -fn get_latest_stable_version() { - let versions = vec![ - CrateVersion { - name: "foo".into(), - version: "0.6.0-alpha".parse().unwrap(), - rust_version: None, - yanked: false, - }, - CrateVersion { - name: "foo".into(), - version: "0.5.0".parse().unwrap(), - rust_version: None, - yanked: false, - }, - ]; - assert_eq!( - read_latest_version(&versions, false, None) - .unwrap() - .version() - .unwrap(), - "0.5.0" - ); -} - -#[test] -fn get_latest_unstable_or_stable_version() { - let versions = vec![ - CrateVersion { - name: "foo".into(), - version: "0.6.0-alpha".parse().unwrap(), - rust_version: None, - yanked: false, - }, - CrateVersion { - name: "foo".into(), - version: "0.5.0".parse().unwrap(), - rust_version: None, - yanked: false, - }, - ]; - assert_eq!( - read_latest_version(&versions, true, None) - .unwrap() - .version() - .unwrap(), - "0.6.0-alpha" - ); -} - -#[test] -fn get_latest_version_with_yanked() { - let versions = vec![ - CrateVersion { - name: "treexml".into(), - version: "0.3.1".parse().unwrap(), - rust_version: None, - yanked: true, - }, - CrateVersion { - name: "true".into(), - version: "0.3.0".parse().unwrap(), - rust_version: None, - yanked: false, - }, - ]; - assert_eq!( - read_latest_version(&versions, false, None) - .unwrap() - .version() - .unwrap(), - "0.3.0" - ); -} - -#[test] -fn get_no_latest_version_from_json_when_all_are_yanked() { - let versions = vec![ - CrateVersion { - name: "treexml".into(), - version: "0.3.1".parse().unwrap(), - rust_version: None, - yanked: true, - }, - CrateVersion { - name: "true".into(), - version: "0.3.0".parse().unwrap(), - rust_version: None, - yanked: true, - }, - ]; - assert!(read_latest_version(&versions, false, None).is_err()); + Some(Dependency::new(name).set_source(RegistrySource::new(version))) } diff --git a/src/index.rs b/src/index.rs index 1c189f22b6..74a9f56589 100644 --- a/src/index.rs +++ b/src/index.rs @@ -157,7 +157,9 @@ impl LocalIndex { .strip_prefix("index") .map_err(|_err| anyhow::format_err!("invalid index path {entry_path:?}"))?; let entry_path = self.root.join(rel_path); - let entry = std::fs::read(&entry_path)?; + let Ok(entry) = std::fs::read(&entry_path) else { + return Ok(None); + }; let results = IndexKrate::from_slice(&entry)?; Ok(Some(results)) } diff --git a/src/lib.rs b/src/lib.rs index 3025c675bf..dd96b2b46f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,7 +38,7 @@ pub use dependency::PathSource; pub use dependency::RegistrySource; pub use dependency::Source; pub use errors::*; -pub use fetch::{RustVersion, get_compatible_dependency, get_latest_dependency}; +pub use fetch::{RustVersion, find_compatible_version, find_latest_version}; pub use index::*; pub use manifest::{LocalManifest, Manifest, find, get_dep_version, set_dep_version}; pub use metadata::manifest_from_pkgid;