Skip to content

Commit 68d99fc

Browse files
committed
feat(spec): Allow partial versions when unambigious
This was proposed in #12425 to help improve usability of the existing `cargo update` when dealing with the added workflows.
1 parent a4ad934 commit 68d99fc

File tree

6 files changed

+80
-34
lines changed

6 files changed

+80
-34
lines changed

src/bin/cargo/commands/remove.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ fn spec_has_match(
280280
}
281281

282282
let version_matches = match (spec.version(), dep.version()) {
283-
(Some(v), Some(vq)) => semver::VersionReq::parse(vq)?.matches(v),
283+
(Some(v), Some(vq)) => semver::VersionReq::parse(vq)?.matches(&v),
284284
(Some(_), None) => false,
285285
(None, None | Some(_)) => true,
286286
};

src/cargo/core/package_id_spec.rs

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ use crate::core::PackageId;
1010
use crate::util::edit_distance;
1111
use crate::util::errors::CargoResult;
1212
use crate::util::interning::InternedString;
13-
use crate::util::{validate_package_name, IntoUrl, ToSemver};
13+
use crate::util::PartialVersion;
14+
use crate::util::{validate_package_name, IntoUrl};
1415

1516
/// Some or all of the data required to identify a package:
1617
///
@@ -24,7 +25,7 @@ use crate::util::{validate_package_name, IntoUrl, ToSemver};
2425
#[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)]
2526
pub struct PackageIdSpec {
2627
name: InternedString,
27-
version: Option<Version>,
28+
version: Option<PartialVersion>,
2829
url: Option<Url>,
2930
}
3031

@@ -70,7 +71,7 @@ impl PackageIdSpec {
7071
let mut parts = spec.splitn(2, [':', '@']);
7172
let name = parts.next().unwrap();
7273
let version = match parts.next() {
73-
Some(version) => Some(version.to_semver()?),
74+
Some(version) => Some(version.parse::<PartialVersion>()?),
7475
None => None,
7576
};
7677
validate_package_name(name, "pkgid", "")?;
@@ -94,12 +95,12 @@ impl PackageIdSpec {
9495
spec.query(i)
9596
}
9697

97-
/// Convert a `PackageId` to a `PackageIdSpec`, which will have both the `Version` and `Url`
98+
/// Convert a `PackageId` to a `PackageIdSpec`, which will have both the `PartialVersion` and `Url`
9899
/// fields filled in.
99100
pub fn from_package_id(package_id: PackageId) -> PackageIdSpec {
100101
PackageIdSpec {
101102
name: package_id.name(),
102-
version: Some(package_id.version().clone()),
103+
version: Some(package_id.version().clone().into()),
103104
url: Some(package_id.source_id().url().clone()),
104105
}
105106
}
@@ -128,14 +129,14 @@ impl PackageIdSpec {
128129
let name_or_version = parts.next().unwrap();
129130
match parts.next() {
130131
Some(part) => {
131-
let version = part.to_semver()?;
132+
let version = part.parse::<PartialVersion>()?;
132133
(InternedString::new(name_or_version), Some(version))
133134
}
134135
None => {
135136
if name_or_version.chars().next().unwrap().is_alphabetic() {
136137
(InternedString::new(name_or_version), None)
137138
} else {
138-
let version = name_or_version.to_semver()?;
139+
let version = name_or_version.parse::<PartialVersion>()?;
139140
(InternedString::new(path_name), Some(version))
140141
}
141142
}
@@ -155,8 +156,9 @@ impl PackageIdSpec {
155156
self.name
156157
}
157158

158-
pub fn version(&self) -> Option<&Version> {
159-
self.version.as_ref()
159+
/// Full `semver::Version`, if present
160+
pub fn version(&self) -> Option<Version> {
161+
self.version.as_ref().and_then(|v| v.version())
160162
}
161163

162164
pub fn url(&self) -> Option<&Url> {
@@ -174,7 +176,8 @@ impl PackageIdSpec {
174176
}
175177

176178
if let Some(ref v) = self.version {
177-
if v != package_id.version() {
179+
let req = v.exact_req();
180+
if !req.matches(package_id.version()) {
178181
return false;
179182
}
180183
}
@@ -326,7 +329,6 @@ mod tests {
326329
use super::PackageIdSpec;
327330
use crate::core::{PackageId, SourceId};
328331
use crate::util::interning::InternedString;
329-
use crate::util::ToSemver;
330332
use url::Url;
331333

332334
#[test]
@@ -351,16 +353,25 @@ mod tests {
351353
"https://crates.io/foo#1.2.3",
352354
PackageIdSpec {
353355
name: InternedString::new("foo"),
354-
version: Some("1.2.3".to_semver().unwrap()),
356+
version: Some("1.2.3".parse().unwrap()),
355357
url: Some(Url::parse("https://crates.io/foo").unwrap()),
356358
},
357359
"https://crates.io/foo#1.2.3",
358360
);
361+
ok(
362+
"https://crates.io/foo#1.2",
363+
PackageIdSpec {
364+
name: InternedString::new("foo"),
365+
version: Some("1.2".parse().unwrap()),
366+
url: Some(Url::parse("https://crates.io/foo").unwrap()),
367+
},
368+
"https://crates.io/foo#1.2",
369+
);
359370
ok(
360371
"https://crates.io/foo#bar:1.2.3",
361372
PackageIdSpec {
362373
name: InternedString::new("bar"),
363-
version: Some("1.2.3".to_semver().unwrap()),
374+
version: Some("1.2.3".parse().unwrap()),
364375
url: Some(Url::parse("https://crates.io/foo").unwrap()),
365376
},
366377
"https://crates.io/foo#[email protected]",
@@ -369,11 +380,20 @@ mod tests {
369380
"https://crates.io/foo#[email protected]",
370381
PackageIdSpec {
371382
name: InternedString::new("bar"),
372-
version: Some("1.2.3".to_semver().unwrap()),
383+
version: Some("1.2.3".parse().unwrap()),
373384
url: Some(Url::parse("https://crates.io/foo").unwrap()),
374385
},
375386
"https://crates.io/foo#[email protected]",
376387
);
388+
ok(
389+
"https://crates.io/foo#[email protected]",
390+
PackageIdSpec {
391+
name: InternedString::new("bar"),
392+
version: Some("1.2".parse().unwrap()),
393+
url: Some(Url::parse("https://crates.io/foo").unwrap()),
394+
},
395+
"https://crates.io/foo#[email protected]",
396+
);
377397
ok(
378398
"foo",
379399
PackageIdSpec {
@@ -387,7 +407,7 @@ mod tests {
387407
"foo:1.2.3",
388408
PackageIdSpec {
389409
name: InternedString::new("foo"),
390-
version: Some("1.2.3".to_semver().unwrap()),
410+
version: Some("1.2.3".parse().unwrap()),
391411
url: None,
392412
},
393413
@@ -396,21 +416,29 @@ mod tests {
396416
397417
PackageIdSpec {
398418
name: InternedString::new("foo"),
399-
version: Some("1.2.3".to_semver().unwrap()),
419+
version: Some("1.2.3".parse().unwrap()),
400420
url: None,
401421
},
402422
403423
);
424+
ok(
425+
426+
PackageIdSpec {
427+
name: InternedString::new("foo"),
428+
version: Some("1.2".parse().unwrap()),
429+
url: None,
430+
},
431+
432+
);
404433
}
405434

406435
#[test]
407436
fn bad_parsing() {
408437
assert!(PackageIdSpec::parse("baz:").is_err());
409438
assert!(PackageIdSpec::parse("baz:*").is_err());
410-
assert!(PackageIdSpec::parse("baz:1.0").is_err());
411439
assert!(PackageIdSpec::parse("baz@").is_err());
412440
assert!(PackageIdSpec::parse("baz@*").is_err());
413-
assert!(PackageIdSpec::parse("[email protected]").is_err());
441+
assert!(PackageIdSpec::parse("baz@^1.0").is_err());
414442
assert!(PackageIdSpec::parse("https://baz:1.0").is_err());
415443
assert!(PackageIdSpec::parse("https://#baz:1.0").is_err());
416444
}
@@ -428,5 +456,6 @@ mod tests {
428456
assert!(!PackageIdSpec::parse("foo:1.2.2").unwrap().matches(foo));
429457
assert!(PackageIdSpec::parse("[email protected]").unwrap().matches(foo));
430458
assert!(!PackageIdSpec::parse("[email protected]").unwrap().matches(foo));
459+
assert!(PackageIdSpec::parse("[email protected]").unwrap().matches(foo));
431460
}
432461
}

src/cargo/util/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pub use self::progress::{Progress, ProgressStyle};
2222
pub use self::queue::Queue;
2323
pub use self::restricted_names::validate_package_name;
2424
pub use self::rustc::Rustc;
25-
pub use self::semver_ext::{OptVersionReq, RustVersion, VersionExt, VersionReqExt};
25+
pub use self::semver_ext::{OptVersionReq, PartialVersion, RustVersion, VersionExt, VersionReqExt};
2626
pub use self::to_semver::ToSemver;
2727
pub use self::vcs::{existing_vcs_repo, FossilRepo, GitRepo, HgRepo, PijulRepo};
2828
pub use self::workspace::{

src/cargo/util/semver_ext.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,16 @@ pub struct PartialVersion {
164164
}
165165

166166
impl PartialVersion {
167+
pub fn version(&self) -> Option<Version> {
168+
Some(Version {
169+
major: self.major,
170+
minor: self.minor?,
171+
patch: self.patch?,
172+
pre: self.pre.clone().unwrap_or_default(),
173+
build: self.build.clone().unwrap_or_default(),
174+
})
175+
}
176+
167177
pub fn caret_req(&self) -> VersionReq {
168178
VersionReq {
169179
comparators: vec![Comparator {
@@ -175,6 +185,18 @@ impl PartialVersion {
175185
}],
176186
}
177187
}
188+
189+
pub fn exact_req(&self) -> VersionReq {
190+
VersionReq {
191+
comparators: vec![Comparator {
192+
op: semver::Op::Exact,
193+
major: self.major,
194+
minor: self.minor,
195+
patch: self.patch,
196+
pre: self.pre.as_ref().cloned().unwrap_or_default(),
197+
}],
198+
}
199+
}
178200
}
179201

180202
impl From<semver::Version> for PartialVersion {

src/cargo/util/toml/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2653,8 +2653,8 @@ impl TomlManifest {
26532653
replacement.unused_keys(),
26542654
&mut cx.warnings,
26552655
);
2656-
dep.set_version_req(VersionReq::exact(version))
2657-
.lock_version(version);
2656+
dep.set_version_req(VersionReq::exact(&version))
2657+
.lock_version(&version);
26582658
replace.push((spec, dep));
26592659
}
26602660
Ok(replace)

tests/testsuite/pkgid.rs

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -151,25 +151,20 @@ fn multiple_versions() {
151151
.with_status(101)
152152
.with_stderr(
153153
"\
154-
error: invalid package ID specification: `two-ver@0`
155-
156-
<tab>Did you mean `two-ver`?
157-
158-
Caused by:
159-
cannot parse '0' as a SemVer version
154+
error: There are multiple `two-ver` packages in your project, and the specification `two-ver@0` is ambiguous.
155+
Please re-run this command with `-p <spec>` where `<spec>` is one of the following:
156+
157+
160158
",
161159
)
162160
.run();
163161

164162
// Incomplete version.
165163
p.cargo("pkgid [email protected]")
166-
.with_status(101)
167-
.with_stderr(
164+
.with_status(0)
165+
.with_stdout(
168166
"\
169-
error: invalid package ID specification: `[email protected]`
170-
171-
Caused by:
172-
cannot parse '0.2' as a SemVer version
167+
https://github.com/rust-lang/crates.io-index#[email protected]
173168
",
174169
)
175170
.run();

0 commit comments

Comments
 (0)