Compare commits

...

3 Commits

8 changed files with 2181 additions and 50 deletions

View File

@ -1,8 +1,11 @@
use std::collections::HashMap;
use crate::command_prelude::*;
use anyhow::anyhow;
use cargo::ops::{self, UpdateOptions};
use cargo::util::print_available_packages;
use tracing::trace;
pub fn cli() -> Command {
subcommand("update")
@ -92,28 +95,39 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
let update_opts = UpdateOptions {
recursive: args.flag("recursive"),
precise: args.get_one::<String>("precise").map(String::as_str),
breaking: args.flag("breaking"),
to_update,
dry_run: args.dry_run(),
workspace: args.flag("workspace"),
gctx,
};
if args.flag("breaking") {
gctx.cli_unstable()
.fail_if_stable_opt("--breaking", 12425)?;
let breaking_update = update_opts.breaking
|| (update_opts.precise.is_some() && gctx.cli_unstable().unstable_options);
let upgrades = ops::upgrade_manifests(&mut ws, &update_opts.to_update)?;
ops::resolve_ws(&ws, update_opts.dry_run)?;
ops::write_manifest_upgrades(&ws, &upgrades, update_opts.dry_run)?;
if update_opts.dry_run {
update_opts
.gctx
.shell()
.warn("aborting update due to dry run")?;
// We are using the term "upgrade" here, which is the typical case, but
// it can also be a downgrade. In general, it is a change to a version
// req matching a precise version.
let upgrades = if breaking_update {
if update_opts.breaking {
gctx.cli_unstable()
.fail_if_stable_opt("--breaking", 12425)?;
}
trace!("allowing breaking updates");
ops::upgrade_manifests(&mut ws, &update_opts.to_update, &update_opts.precise)?
} else {
ops::update_lockfile(&ws, &update_opts)?;
HashMap::new()
};
ops::update_lockfile(&ws, &update_opts, &upgrades)?;
ops::write_manifest_upgrades(&ws, &upgrades, update_opts.dry_run)?;
if update_opts.dry_run {
update_opts
.gctx
.shell()
.warn("aborting update due to dry run")?;
}
Ok(())

View File

@ -14,6 +14,7 @@ use crate::util::toml_mut::manifest::LocalManifest;
use crate::util::toml_mut::upgrade::upgrade_requirement;
use crate::util::{style, OptVersionReq};
use crate::util::{CargoResult, VersionExt};
use anyhow::Context;
use itertools::Itertools;
use semver::{Op, Version, VersionReq};
use std::cmp::Ordering;
@ -26,6 +27,7 @@ pub struct UpdateOptions<'a> {
pub gctx: &'a GlobalContext,
pub to_update: Vec<String>,
pub precise: Option<&'a str>,
pub breaking: bool,
pub recursive: bool,
pub dry_run: bool,
pub workspace: bool,
@ -49,7 +51,11 @@ pub fn generate_lockfile(ws: &Workspace<'_>) -> CargoResult<()> {
Ok(())
}
pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoResult<()> {
pub fn update_lockfile(
ws: &Workspace<'_>,
opts: &UpdateOptions<'_>,
upgrades: &UpgradeMap,
) -> CargoResult<()> {
if opts.recursive && opts.precise.is_some() {
anyhow::bail!("cannot specify both recursive and precise simultaneously")
}
@ -165,7 +171,12 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
.filter(|s| !s.is_registry())
.collect();
let keep = |p: &PackageId| !to_avoid_sources.contains(&p.source_id()) && !to_avoid.contains(p);
let keep = |p: &PackageId| {
(!to_avoid_sources.contains(&p.source_id()) && !to_avoid.contains(p))
// In case of `--breaking`, we want to keep all packages unchanged that
// didn't get upgraded.
|| (opts.breaking && !upgrades.contains_key(&(p.name().to_string(), p.source_id())))
};
let mut resolve = ops::resolve_with_previous(
&mut registry,
@ -185,11 +196,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
opts.precise.is_some(),
&mut registry,
)?;
if opts.dry_run {
opts.gctx
.shell()
.warn("not updating lockfile due to dry run")?;
} else {
if !opts.dry_run {
ops::write_pkg_lockfile(ws, &mut resolve)?;
}
Ok(())
@ -217,6 +224,7 @@ pub fn print_lockfile_changes(
pub fn upgrade_manifests(
ws: &mut Workspace<'_>,
to_update: &Vec<String>,
precise: &Option<&str>,
) -> CargoResult<UpgradeMap> {
let gctx = ws.gctx();
let mut upgrades = HashMap::new();
@ -245,6 +253,7 @@ pub fn upgrade_manifests(
upgrade_dependency(
&gctx,
&to_update,
precise,
&mut registry,
&mut upgrades,
&mut upgrade_messages,
@ -259,6 +268,7 @@ pub fn upgrade_manifests(
fn upgrade_dependency(
gctx: &GlobalContext,
to_update: &Vec<PackageIdSpec>,
precise: &Option<&str>,
registry: &mut PackageRegistry<'_>,
upgrades: &mut UpgradeMap,
upgrade_messages: &mut HashSet<String>,
@ -316,7 +326,7 @@ fn upgrade_dependency(
let query =
crate::core::dependency::Dependency::parse(name, None, dependency.source_id().clone())?;
let possibilities = {
let possibilities = if precise.is_none() {
loop {
match registry.query_vec(&query, QueryKind::Exact) {
std::task::Poll::Ready(res) => {
@ -325,6 +335,8 @@ fn upgrade_dependency(
std::task::Poll::Pending => registry.block_until_ready()?,
}
}
} else {
Vec::new()
};
let latest = if !possibilities.is_empty() {
@ -338,17 +350,23 @@ fn upgrade_dependency(
None
};
let Some(latest) = latest else {
let new_version = if let Some(precise) = precise {
Version::parse(precise)
.with_context(|| format!("invalid version format for precise version `{precise}`"))?
} else if let Some(latest) = latest {
latest.clone()
} else {
// Breaking (not precise) upgrade did not find a latest version
trace!("skipping dependency `{name}` without any published versions");
return Ok(dependency);
};
if current.matches(&latest) {
if current.matches(&new_version) {
trace!("skipping dependency `{name}` without a breaking update available");
return Ok(dependency);
}
let Some((new_req_string, _)) = upgrade_requirement(&current.to_string(), latest)? else {
let Some((new_req_string, _)) = upgrade_requirement(&current.to_string(), &new_version)? else {
trace!("skipping dependency `{name}` because the version requirement didn't change");
return Ok(dependency);
};
@ -356,14 +374,36 @@ fn upgrade_dependency(
let upgrade_message = format!("{name} {current} -> {new_req_string}");
trace!(upgrade_message);
let old_version = semver::Version::new(
comparator.major,
comparator.minor.unwrap_or_default(),
comparator.patch.unwrap_or_default(),
);
let is_downgrade = new_version < old_version;
let status = if is_downgrade {
"Downgrading"
} else {
"Upgrading"
};
if upgrade_messages.insert(upgrade_message.clone()) {
gctx.shell()
.status_with_color("Upgrading", &upgrade_message, &style::GOOD)?;
.status_with_color(status, &upgrade_message, &style::WARN)?;
}
upgrades.insert((name.to_string(), dependency.source_id()), latest.clone());
upgrades.insert(
(name.to_string(), dependency.source_id()),
new_version.clone(),
);
let new_version_req = VersionReq::parse(&new_version.to_string())?;
let req = if precise.is_some() {
OptVersionReq::Precise(new_version, new_version_req)
} else {
OptVersionReq::Req(new_version_req)
};
let req = OptVersionReq::Req(VersionReq::parse(&latest.to_string())?);
let mut dep = dependency.clone();
dep.set_version_req(req);
Ok(dep)

View File

@ -117,9 +117,9 @@ impl OptVersionReq {
/// and we're not sure if this part of the functionality should be implemented in semver or cargo.
pub fn matches_prerelease(&self, version: &Version) -> bool {
if version.is_prerelease() {
let mut version = version.clone();
version.pre = semver::Prerelease::EMPTY;
return self.matches(&version);
let mut version_cleaned = version.clone();
version_cleaned.pre = semver::Prerelease::EMPTY;
return self.matches(&version) || self.matches(&version_cleaned);
}
self.matches(version)
}

View File

@ -1,5 +1,7 @@
use std::fmt::Display;
use anyhow::bail;
use crate::CargoResult;
/// Upgrade an existing requirement to a new version.
@ -27,15 +29,8 @@ pub(crate) fn upgrade_requirement(
new_req_text.remove(0);
}
// Validate contract
#[cfg(debug_assertions)]
{
assert!(
new_req.matches(version),
"New req {} is invalid, because {} does not match {}",
new_req_text,
new_req,
version
)
if !new_req.matches(version) {
bail!("New requirement {new_req_text} is invalid, because it doesn't match {version}")
}
if new_req_text == req_text {
Ok(None)

View File

@ -178,6 +178,7 @@ mod tree;
mod tree_graph_features;
mod unit_graph;
mod update;
mod update_duplicated_with_precise_breaking_feature;
mod vendor;
mod verify_project;
mod version;

View File

@ -65,8 +65,8 @@ fn update_pre_release() {
p.cargo("update my-dependency --precise 0.1.2-pre.0 -Zunstable-options")
.masquerade_as_nightly_cargo(&["precise-pre-release"])
.with_stderr_data(str![[r#"
[UPGRADING] my-dependency ^0.1.1 -> ^0.1.2-pre.0
[UPDATING] `dummy-registry` index
[UPDATING] my-dependency v0.1.1 -> v0.1.2-pre.0
"#]])
.run();
@ -98,8 +98,8 @@ fn update_pre_release_differ() {
p.cargo("update -p my-dependency --precise 0.1.2-pre.0 -Zunstable-options")
.masquerade_as_nightly_cargo(&["precise-pre-release"])
.with_stderr_data(str![[r#"
[DOWNGRADING] my-dependency ^0.1.2 -> ^0.1.2-pre.0
[UPDATING] `dummy-registry` index
[DOWNGRADING] my-dependency v0.1.2 -> v0.1.2-pre.0
"#]])
.run();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,703 @@
//! Duplicating tests for `cargo update --precise` with unstable-options
//! enabled. This will make sure we check backward compatibility when the
//! capability of making breaking changes has been implemented. When that
//! feature is stabilized, this file can be deleted.
use cargo_test_support::prelude::*;
use cargo_test_support::registry::Package;
use cargo_test_support::{basic_lib_manifest, git, project, str};
#[cargo_test]
fn update_precise_downgrade() {
Package::new("serde", "0.1.0").publish();
Package::new("serde", "0.2.1").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "bar"
version = "0.0.1"
edition = "2015"
authors = []
[dependencies]
serde = "0.2"
foo = { path = "foo" }
"#,
)
.file("src/lib.rs", "")
.file(
"foo/Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
edition = "2015"
authors = []
[dependencies]
serde = "0.1"
"#,
)
.file("foo/src/lib.rs", "")
.build();
p.cargo("check").run();
Package::new("serde", "0.2.0").publish();
p.cargo("update -Zunstable-options serde:0.2.1 --precise 0.2.0")
.masquerade_as_nightly_cargo(&["update-precise-breaking"])
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[DOWNGRADING] serde v0.2.1 -> v0.2.0
[NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest
"#]])
.run();
}
#[cargo_test]
fn update_precise_mismatched() {
Package::new("serde", "1.2.0").publish();
Package::new("serde", "1.2.1").publish();
Package::new("serde", "1.6.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "bar"
version = "0.0.1"
edition = "2015"
authors = []
[dependencies]
serde = "~1.2"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check").run();
// `1.6.0` does not match `"~1.2"`
p.cargo("update -Zunstable-options serde:1.2 --precise 1.6.0")
.masquerade_as_nightly_cargo(&["update-precise-breaking"])
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] failed to select a version for the requirement `serde = "~1.2"`
candidate versions found which didn't match: 1.6.0
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `bar v0.0.1 ([ROOT]/foo)`
perhaps a crate was updated and forgotten to be re-vendored?
"#]])
.with_status(101)
.run();
// `1.9.0` does not exist
p.cargo("update -Zunstable-options serde:1.2 --precise 1.9.0")
.masquerade_as_nightly_cargo(&["update-precise-breaking"])
// This terrible error message has been the same for a long time. A fix is more than welcome!
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] no matching package named `serde` found
location searched: registry `crates-io`
required by package `bar v0.0.1 ([ROOT]/foo)`
"#]])
.with_status(101)
.run();
}
#[cargo_test]
fn update_precise_build_metadata() {
Package::new("serde", "0.0.1+first").publish();
Package::new("serde", "0.0.1+second").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.0"
edition = "2015"
[dependencies]
serde = "0.0.1"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("generate-lockfile").run();
p.cargo("update -Zunstable-options serde --precise 0.0.1+first")
.masquerade_as_nightly_cargo(&["update-precise-breaking"])
.run();
p.cargo("update -Zunstable-options serde --precise 0.0.1+second")
.masquerade_as_nightly_cargo(&["update-precise-breaking"])
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[UPDATING] serde v0.0.1+first -> v0.0.1+second
"#]])
.run();
// This is not considered "Downgrading". Build metadata are not assumed to
// be ordered.
p.cargo("update -Zunstable-options serde --precise 0.0.1+first")
.masquerade_as_nightly_cargo(&["update-precise-breaking"])
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[UPDATING] serde v0.0.1+second -> v0.0.1+first
"#]])
.run();
}
#[cargo_test]
fn update_precise_do_not_force_update_deps() {
Package::new("log", "0.1.0").publish();
Package::new("serde", "0.2.1").dep("log", "0.1").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "bar"
version = "0.0.1"
edition = "2015"
authors = []
[dependencies]
serde = "0.2"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check").run();
Package::new("log", "0.1.1").publish();
Package::new("serde", "0.2.2").dep("log", "0.1").publish();
p.cargo("update -Zunstable-options serde:0.2.1 --precise 0.2.2")
.masquerade_as_nightly_cargo(&["update-precise-breaking"])
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[UPDATING] serde v0.2.1 -> v0.2.2
[NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest
"#]])
.run();
}
#[cargo_test]
fn update_recursive_conflicts_with_precise() {
Package::new("log", "0.1.0").publish();
Package::new("serde", "0.2.1").dep("log", "0.1").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "bar"
version = "0.0.1"
edition = "2015"
authors = []
[dependencies]
serde = "0.2"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check").run();
Package::new("log", "0.1.1").publish();
Package::new("serde", "0.2.2").dep("log", "0.1").publish();
p.cargo("update -Zunstable-options serde:0.2.1 --precise 0.2.2 --recursive")
.masquerade_as_nightly_cargo(&["update-precise-breaking"])
.with_status(1)
.with_stderr_data(str![[r#"
[ERROR] the argument '--precise <PRECISE>' cannot be used with '--recursive'
Usage: cargo[EXE] update -Z <FLAG> --precise <PRECISE> <SPEC|--package [<SPEC>]>
For more information, try '--help'.
"#]])
.run();
}
// cargo update should respect its arguments even without a lockfile.
// See issue "Running cargo update without a Cargo.lock ignores arguments"
// at <https://github.com/rust-lang/cargo/issues/6872>.
#[cargo_test]
fn update_precise_first_run() {
Package::new("serde", "0.1.0").publish();
Package::new("serde", "0.2.0").publish();
Package::new("serde", "0.2.1").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "bar"
version = "0.0.1"
edition = "2015"
[dependencies]
serde = "0.2"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("update -Zunstable-options serde --precise 0.2.0")
.masquerade_as_nightly_cargo(&["update-precise-breaking"])
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[DOWNGRADING] serde v0.2.1 -> v0.2.0
"#]])
.run();
// Assert `cargo metadata` shows serde 0.2.0
p.cargo("metadata")
.with_stdout_data(
str![[r#"
{
"metadata": null,
"packages": [
{
"authors": [],
"categories": [],
"default_run": null,
"dependencies": [
{
"features": [],
"kind": null,
"name": "serde",
"optional": false,
"registry": null,
"rename": null,
"req": "^0.2",
"source": "registry+https://github.com/rust-lang/crates.io-index",
"target": null,
"uses_default_features": true
}
],
"description": null,
"documentation": null,
"edition": "2015",
"features": {},
"homepage": null,
"id": "path+[ROOTURL]/foo#bar@0.0.1",
"keywords": [],
"license": null,
"license_file": null,
"links": null,
"manifest_path": "[ROOT]/foo/Cargo.toml",
"metadata": null,
"name": "bar",
"publish": null,
"readme": null,
"repository": null,
"rust_version": null,
"source": null,
"targets": [
{
"crate_types": [
"lib"
],
"doc": true,
"doctest": true,
"edition": "2015",
"kind": [
"lib"
],
"name": "bar",
"src_path": "[ROOT]/foo/src/lib.rs",
"test": true
}
],
"version": "0.0.1"
},
{
"authors": [],
"categories": [],
"default_run": null,
"dependencies": [],
"description": null,
"documentation": null,
"edition": "2015",
"features": {},
"homepage": null,
"id": "registry+https://github.com/rust-lang/crates.io-index#serde@0.2.0",
"keywords": [],
"license": null,
"license_file": null,
"links": null,
"manifest_path": "[ROOT]/home/.cargo/registry/src/-[HASH]/serde-0.2.0/Cargo.toml",
"metadata": null,
"name": "serde",
"publish": null,
"readme": null,
"repository": null,
"rust_version": null,
"source": "registry+https://github.com/rust-lang/crates.io-index",
"targets": [
{
"crate_types": [
"lib"
],
"doc": true,
"doctest": true,
"edition": "2015",
"kind": [
"lib"
],
"name": "serde",
"src_path": "[ROOT]/home/.cargo/registry/src/-[HASH]/serde-0.2.0/src/lib.rs",
"test": true
}
],
"version": "0.2.0"
}
],
"resolve": {
"nodes": [
{
"dependencies": [
"registry+https://github.com/rust-lang/crates.io-index#serde@0.2.0"
],
"deps": [
{
"dep_kinds": [
{
"kind": null,
"target": null
}
],
"name": "serde",
"pkg": "registry+https://github.com/rust-lang/crates.io-index#serde@0.2.0"
}
],
"features": [],
"id": "path+[ROOTURL]/foo#bar@0.0.1"
},
{
"dependencies": [],
"deps": [],
"features": [],
"id": "registry+https://github.com/rust-lang/crates.io-index#serde@0.2.0"
}
],
"root": "path+[ROOTURL]/foo#bar@0.0.1"
},
"target_directory": "[ROOT]/foo/target",
"version": 1,
"workspace_default_members": [
"path+[ROOTURL]/foo#bar@0.0.1"
],
"workspace_members": [
"path+[ROOTURL]/foo#bar@0.0.1"
],
"workspace_root": "[ROOT]/foo"
}
"#]]
.json(),
)
.run();
p.cargo("update -Zunstable-options serde --precise 0.2.0")
.masquerade_as_nightly_cargo(&["update-precise-breaking"])
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
"#]])
.run();
}
#[cargo_test]
fn precise_with_build_metadata() {
// +foo syntax shouldn't be necessary with --precise
Package::new("bar", "0.1.0+extra-stuff.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
edition = "2015"
[dependencies]
bar = "0.1"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("generate-lockfile").run();
Package::new("bar", "0.1.1+extra-stuff.1").publish();
Package::new("bar", "0.1.2+extra-stuff.2").publish();
p.cargo("update -Zunstable-options bar --precise 0.1")
.masquerade_as_nightly_cargo(&["update-precise-breaking"])
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] invalid version format for precise version `0.1`
Caused by:
unexpected end of input while parsing minor version number
"#]])
.run();
p.cargo("update -Zunstable-options bar --precise 0.1.1+does-not-match")
.masquerade_as_nightly_cargo(&["update-precise-breaking"])
.with_status(101)
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] no matching package named `bar` found
location searched: registry `crates-io`
required by package `foo v0.1.0 ([ROOT]/foo)`
"#]])
.run();
p.cargo("update -Zunstable-options bar --precise 0.1.1")
.masquerade_as_nightly_cargo(&["update-precise-breaking"])
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[UPDATING] bar v0.1.0+extra-stuff.0 -> v0.1.1+extra-stuff.1
"#]])
.run();
Package::new("bar", "0.1.3").publish();
p.cargo("update -Zunstable-options bar --precise 0.1.3+foo")
.masquerade_as_nightly_cargo(&["update-precise-breaking"])
.with_status(101)
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] no matching package named `bar` found
location searched: registry `crates-io`
required by package `foo v0.1.0 ([ROOT]/foo)`
"#]])
.run();
p.cargo("update -Zunstable-options bar --precise 0.1.3")
.masquerade_as_nightly_cargo(&["update-precise-breaking"])
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[UPDATING] bar v0.1.1+extra-stuff.1 -> v0.1.3
"#]])
.run();
}
#[cargo_test]
fn update_precise_git_revisions() {
let (git_project, git_repo) = git::new_repo("git", |p| {
p.file("Cargo.toml", &basic_lib_manifest("git"))
.file("src/lib.rs", "")
});
let tag_name = "Nazgûl";
git::tag(&git_repo, tag_name);
let tag_commit_id = git_repo.head().unwrap().target().unwrap().to_string();
git_project.change_file("src/lib.rs", "fn f() {}");
git::add(&git_repo);
let head_id = git::commit(&git_repo).to_string();
let short_id = &head_id[..8];
let url = git_project.url();
let p = project()
.file(
"Cargo.toml",
&format!(
r#"
[package]
name = "foo"
version = "0.1.0"
edition = "2015"
[dependencies]
git = {{ git = '{url}' }}
"#
),
)
.file("src/lib.rs", "")
.build();
p.cargo("fetch")
.with_stderr_data(str![[r#"
[UPDATING] git repository `[ROOTURL]/git`
[LOCKING] 2 packages to latest compatible versions
"#]])
.run();
assert!(p.read_lockfile().contains(&head_id));
p.cargo("update -Zunstable-options git --precise")
.masquerade_as_nightly_cargo(&["update-precise-breaking"])
.arg(tag_name)
.with_stderr_data(format!(
"\
[UPDATING] git repository `[ROOTURL]/git`
[UPDATING] git v0.5.0 ([ROOTURL]/git#[..]) -> #{}
",
&tag_commit_id[..8],
))
.run();
assert!(p.read_lockfile().contains(&tag_commit_id));
assert!(!p.read_lockfile().contains(&head_id));
p.cargo("update -Zunstable-options git --precise")
.masquerade_as_nightly_cargo(&["update-precise-breaking"])
.arg(short_id)
.with_stderr_data(format!(
"\
[UPDATING] git repository `[ROOTURL]/git`
[UPDATING] git v0.5.0 ([ROOTURL]/git[..]) -> #{short_id}
",
))
.run();
assert!(p.read_lockfile().contains(&head_id));
assert!(!p.read_lockfile().contains(&tag_commit_id));
// updating back to tag still requires a git fetch,
// as the ref may change over time.
p.cargo("update -Zunstable-options git --precise")
.masquerade_as_nightly_cargo(&["update-precise-breaking"])
.arg(tag_name)
.with_stderr_data(format!(
"\
[UPDATING] git repository `[ROOTURL]/git`
[UPDATING] git v0.5.0 ([ROOTURL]/git#[..]) -> #{}
",
&tag_commit_id[..8],
))
.run();
assert!(p.read_lockfile().contains(&tag_commit_id));
assert!(!p.read_lockfile().contains(&head_id));
// Now make a tag looks like an oid.
// It requires a git fetch, as the oid cannot be found in preexisting git db.
let arbitrary_tag: String = std::iter::repeat('a').take(head_id.len()).collect();
git::tag(&git_repo, &arbitrary_tag);
p.cargo("update -Zunstable-options git --precise")
.masquerade_as_nightly_cargo(&["update-precise-breaking"])
.arg(&arbitrary_tag)
.with_stderr_data(format!(
"\
[UPDATING] git repository `[ROOTURL]/git`
[UPDATING] git v0.5.0 ([ROOTURL]/git#[..]) -> #{}
",
&head_id[..8],
))
.run();
assert!(p.read_lockfile().contains(&head_id));
assert!(!p.read_lockfile().contains(&tag_commit_id));
}
#[cargo_test]
fn update_precise_yanked() {
Package::new("bar", "0.1.0").publish();
Package::new("bar", "0.1.1").yanked(true).publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
[dependencies]
bar = "0.1"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("generate-lockfile").run();
// Use non-yanked version.
let lockfile = p.read_lockfile();
assert!(lockfile.contains("\nname = \"bar\"\nversion = \"0.1.0\""));
p.cargo("update -Zunstable-options --precise 0.1.1 bar")
.masquerade_as_nightly_cargo(&["update-precise-breaking"])
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[WARNING] selected package `bar@0.1.1` was yanked by the author
[NOTE] if possible, try a compatible non-yanked version
[UPDATING] bar v0.1.0 -> v0.1.1
"#]])
.run();
// Use yanked version.
let lockfile = p.read_lockfile();
assert!(lockfile.contains("\nname = \"bar\"\nversion = \"0.1.1\""));
}
#[cargo_test]
fn update_precise_yanked_multiple_presence() {
Package::new("bar", "0.1.0").publish();
Package::new("bar", "0.1.1").yanked(true).publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
[dependencies]
bar = "0.1"
baz = { package = "bar", version = "0.1" }
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("generate-lockfile").run();
// Use non-yanked version.
let lockfile = p.read_lockfile();
assert!(lockfile.contains("\nname = \"bar\"\nversion = \"0.1.0\""));
p.cargo("update -Zunstable-options --precise 0.1.1 bar")
.masquerade_as_nightly_cargo(&["update-precise-breaking"])
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[WARNING] selected package `bar@0.1.1` was yanked by the author
[NOTE] if possible, try a compatible non-yanked version
[UPDATING] bar v0.1.0 -> v0.1.1
"#]])
.run();
// Use yanked version.
let lockfile = p.read_lockfile();
assert!(lockfile.contains("\nname = \"bar\"\nversion = \"0.1.1\""));
}