Skip to content

Commit 48a79c9

Browse files
committed
Auto merge of #12849 - Eh2406:Precise, r=epage
Make the precise field of a source an Enum ### What does this PR try to resolve? The precise field of a source_id is a stringly typed untagged union for storing various kinds of data. This is a series of changes untangle what this field is used for. Eventually leading to it being stored as an Enum with dedicated helpers for setting or getting the values different use cases need. ### How should we test and review this PR? This is an internal re-factor, and all the tests still pass. This can be reviewed one PR at a time. The first three look at patterns and how precise is used the throughout the code base and make dedicated helpers for each common use case. The last PR switches to an Enum and make helpers for the one-off use cases. ### Additional information I remember having an issue to do this work, but I cannot find it now. It's not clear to me if I went overboard on making helpers, API design iteration is welcome. It would be nice if the `precise` was entirely an internal of the source, this PR does not get us all the way there. This PR makes the representation of the field and internally detail, but its existence still matters to many other parts of the code. Suggestions are welcome.
2 parents b227fe1 + fa94b11 commit 48a79c9

13 files changed

+133
-77
lines changed

src/cargo/core/dependency.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -352,9 +352,7 @@ impl Dependency {
352352
// Only update the `precise` of this source to preserve other
353353
// information about dependency's source which may not otherwise be
354354
// tested during equality/hashing.
355-
me.source_id = me
356-
.source_id
357-
.with_precise(id.source_id().precise().map(|s| s.to_string()));
355+
me.source_id = me.source_id.with_precise_from(id.source_id());
358356
self
359357
}
360358

src/cargo/core/package_id.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -160,14 +160,6 @@ impl PackageId {
160160
self.inner.source_id
161161
}
162162

163-
pub fn with_precise(self, precise: Option<String>) -> PackageId {
164-
PackageId::pure(
165-
self.inner.name,
166-
self.inner.version.clone(),
167-
self.inner.source_id.with_precise(precise),
168-
)
169-
}
170-
171163
pub fn with_source_id(self, source: SourceId) -> PackageId {
172164
PackageId::pure(self.inner.name, self.inner.version.clone(), source)
173165
}

src/cargo/core/registry.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ impl<'cfg> PackageRegistry<'cfg> {
161161

162162
// If the previous source was not a precise source, then we can be
163163
// sure that it's already been updated if we've already loaded it.
164-
Some((previous, _)) if previous.precise().is_none() => {
164+
Some((previous, _)) if !previous.has_precise() => {
165165
debug!("load/precise {}", namespace);
166166
return Ok(());
167167
}
@@ -170,7 +170,7 @@ impl<'cfg> PackageRegistry<'cfg> {
170170
// then we're done, otherwise we need to need to move forward
171171
// updating this source.
172172
Some((previous, _)) => {
173-
if previous.precise() == namespace.precise() {
173+
if previous.has_same_precise_as(namespace) {
174174
debug!("load/match {}", namespace);
175175
return Ok(());
176176
}
@@ -471,9 +471,9 @@ impl<'cfg> PackageRegistry<'cfg> {
471471
//
472472
// If we have a precise version, then we'll update lazily during the
473473
// querying phase. Note that precise in this case is only
474-
// `Some("locked")` as other `Some` values indicate a `cargo update
474+
// `"locked"` as other values indicate a `cargo update
475475
// --precise` request
476-
if source_id.precise() != Some("locked") {
476+
if !source_id.has_locked_precise() {
477477
self.sources.get_mut(source_id).unwrap().invalidate_cache();
478478
} else {
479479
debug!("skipping update due to locked registry");

src/cargo/core/resolver/encode.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,7 @@ pub fn encodable_package_id(
776776
}
777777
}
778778
}
779-
let mut source = encodable_source_id(id_to_encode.with_precise(None), resolve_version);
779+
let mut source = encodable_source_id(id_to_encode.without_precise(), resolve_version);
780780
if let Some(counts) = &state.counts {
781781
let version_counts = &counts[&id.name()];
782782
if version_counts[&id.version()] == 1 {

src/cargo/core/source_id.rs

Lines changed: 108 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ use crate::sources::registry::CRATES_IO_HTTP_INDEX;
33
use crate::sources::source::Source;
44
use crate::sources::{DirectorySource, CRATES_IO_DOMAIN, CRATES_IO_INDEX, CRATES_IO_REGISTRY};
55
use crate::sources::{GitSource, PathSource, RegistrySource};
6-
use crate::util::{config, CanonicalUrl, CargoResult, Config, IntoUrl, ToSemver};
6+
use crate::util::interning::InternedString;
7+
use crate::util::{config, CanonicalUrl, CargoResult, Config, IntoUrl};
78
use anyhow::Context;
89
use serde::de;
910
use serde::ser;
@@ -50,14 +51,37 @@ struct SourceIdInner {
5051
/// The source kind.
5152
kind: SourceKind,
5253
/// For example, the exact Git revision of the specified branch for a Git Source.
53-
precise: Option<String>,
54+
precise: Option<Precise>,
5455
/// Name of the remote registry.
5556
///
5657
/// WARNING: this is not always set when the name is not known,
5758
/// e.g. registry coming from `--index` or Cargo.lock
5859
registry_key: Option<KeyOf>,
5960
}
6061

62+
#[derive(Eq, PartialEq, Clone, Debug, Hash)]
63+
enum Precise {
64+
Locked,
65+
Updated {
66+
name: InternedString,
67+
from: semver::Version,
68+
to: semver::Version,
69+
},
70+
GitUrlFragment(String),
71+
}
72+
73+
impl fmt::Display for Precise {
74+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
75+
match self {
76+
Precise::Locked => "locked".fmt(f),
77+
Precise::Updated { name, from, to } => {
78+
write!(f, "{name}={from}->{to}")
79+
}
80+
Precise::GitUrlFragment(s) => s.fmt(f),
81+
}
82+
}
83+
}
84+
6185
/// The possible kinds of code source.
6286
/// Along with [`SourceIdInner`], this fully defines the source.
6387
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -178,17 +202,15 @@ impl SourceId {
178202
let precise = url.fragment().map(|s| s.to_owned());
179203
url.set_fragment(None);
180204
url.set_query(None);
181-
Ok(SourceId::for_git(&url, reference)?.with_precise(precise))
205+
Ok(SourceId::for_git(&url, reference)?.with_git_precise(precise))
182206
}
183207
"registry" => {
184208
let url = url.into_url()?;
185-
Ok(SourceId::new(SourceKind::Registry, url, None)?
186-
.with_precise(Some("locked".to_string())))
209+
Ok(SourceId::new(SourceKind::Registry, url, None)?.with_locked_precise())
187210
}
188211
"sparse" => {
189212
let url = string.into_url()?;
190-
Ok(SourceId::new(SourceKind::SparseRegistry, url, None)?
191-
.with_precise(Some("locked".to_string())))
213+
Ok(SourceId::new(SourceKind::SparseRegistry, url, None)?.with_locked_precise())
192214
}
193215
"path" => {
194216
let url = url.into_url()?;
@@ -332,10 +354,10 @@ impl SourceId {
332354
pub fn display_registry_name(self) -> String {
333355
if let Some(key) = self.inner.registry_key.as_ref().map(|k| k.key()) {
334356
key.into()
335-
} else if self.precise().is_some() {
357+
} else if self.has_precise() {
336358
// We remove `precise` here to retrieve an permissive version of
337359
// `SourceIdInner`, which may contain the registry name.
338-
self.with_precise(None).display_registry_name()
360+
self.without_precise().display_registry_name()
339361
} else {
340362
url_display(self.url())
341363
}
@@ -444,37 +466,81 @@ impl SourceId {
444466
}
445467
}
446468

447-
/// Gets the value of the precise field.
448-
pub fn precise(self) -> Option<&'static str> {
449-
self.inner.precise.as_deref()
469+
/// Check if the precise data field has bean set
470+
pub fn has_precise(self) -> bool {
471+
self.inner.precise.is_some()
472+
}
473+
474+
/// Check if the precise data field has bean set to "locked"
475+
pub fn has_locked_precise(self) -> bool {
476+
self.inner.precise == Some(Precise::Locked)
477+
}
478+
479+
/// Check if two sources have the same precise data field
480+
pub fn has_same_precise_as(self, other: Self) -> bool {
481+
self.inner.precise == other.inner.precise
450482
}
451483

452484
/// Check if the precise data field stores information for this `name`
453485
/// from a call to [SourceId::with_precise_registry_version].
454486
///
455487
/// If so return the version currently in the lock file and the version to be updated to.
456-
/// If specified, our own source will have a precise version listed of the form
457-
// `<pkg>=<p_req>-><f_req>` where `<pkg>` is the name of a crate on
458-
// this source, `<p_req>` is the version installed and `<f_req>` is the
459-
// version requested (argument to `--precise`).
460488
pub fn precise_registry_version(
461489
self,
462-
name: &str,
463-
) -> Option<(semver::Version, semver::Version)> {
464-
self.inner
465-
.precise
466-
.as_deref()
467-
.and_then(|p| p.strip_prefix(name)?.strip_prefix('='))
468-
.map(|p| {
469-
let (current, requested) = p.split_once("->").unwrap();
470-
(current.to_semver().unwrap(), requested.to_semver().unwrap())
471-
})
490+
pkg: &str,
491+
) -> Option<(&semver::Version, &semver::Version)> {
492+
match &self.inner.precise {
493+
Some(Precise::Updated { name, from, to }) if name == pkg => Some((from, to)),
494+
_ => None,
495+
}
496+
}
497+
498+
pub fn precise_git_fragment(self) -> Option<&'static str> {
499+
match &self.inner.precise {
500+
Some(Precise::GitUrlFragment(s)) => Some(&s[..8]),
501+
_ => None,
502+
}
503+
}
504+
505+
pub fn precise_git_oid(self) -> CargoResult<Option<git2::Oid>> {
506+
Ok(match self.inner.precise.as_ref() {
507+
Some(Precise::GitUrlFragment(s)) => {
508+
Some(git2::Oid::from_str(s).with_context(|| {
509+
format!("precise value for git is not a git revision: {}", s)
510+
})?)
511+
}
512+
_ => None,
513+
})
472514
}
473515

474516
/// Creates a new `SourceId` from this source with the given `precise`.
475-
pub fn with_precise(self, v: Option<String>) -> SourceId {
517+
pub fn with_git_precise(self, fragment: Option<String>) -> SourceId {
476518
SourceId::wrap(SourceIdInner {
477-
precise: v,
519+
precise: fragment.map(|f| Precise::GitUrlFragment(f)),
520+
..(*self.inner).clone()
521+
})
522+
}
523+
524+
/// Creates a new `SourceId` from this source without a `precise`.
525+
pub fn without_precise(self) -> SourceId {
526+
SourceId::wrap(SourceIdInner {
527+
precise: None,
528+
..(*self.inner).clone()
529+
})
530+
}
531+
532+
/// Creates a new `SourceId` from this source without a `precise`.
533+
pub fn with_locked_precise(self) -> SourceId {
534+
SourceId::wrap(SourceIdInner {
535+
precise: Some(Precise::Locked),
536+
..(*self.inner).clone()
537+
})
538+
}
539+
540+
/// Creates a new `SourceId` from this source with the `precise` from some other `SourceId`.
541+
pub fn with_precise_from(self, v: Self) -> SourceId {
542+
SourceId::wrap(SourceIdInner {
543+
precise: v.inner.precise.clone(),
478544
..(*self.inner).clone()
479545
})
480546
}
@@ -487,13 +553,21 @@ impl SourceId {
487553
/// The data can be read with [SourceId::precise_registry_version]
488554
pub fn with_precise_registry_version(
489555
self,
490-
name: impl fmt::Display,
491-
version: &semver::Version,
556+
name: InternedString,
557+
version: semver::Version,
492558
precise: &str,
493559
) -> CargoResult<SourceId> {
494-
semver::Version::parse(precise)
560+
let precise = semver::Version::parse(precise)
495561
.with_context(|| format!("invalid version format for precise version `{precise}`"))?;
496-
Ok(self.with_precise(Some(format!("{}={}->{}", name, version, precise))))
562+
563+
Ok(SourceId::wrap(SourceIdInner {
564+
precise: Some(Precise::Updated {
565+
name,
566+
from: version,
567+
to: precise,
568+
}),
569+
..(*self.inner).clone()
570+
}))
497571
}
498572

499573
/// Returns `true` if the remote registry is the standard <https://crates.io>.
@@ -625,7 +699,8 @@ impl fmt::Display for SourceId {
625699
write!(f, "?{}", pretty)?;
626700
}
627701

628-
if let Some(ref s) = self.inner.precise {
702+
if let Some(s) = &self.inner.precise {
703+
let s = s.to_string();
629704
let len = cmp::min(s.len(), 8);
630705
write!(f, "#{}", &s[..len])?;
631706
}

src/cargo/ops/cargo_generate_lockfile.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,14 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
105105
if pid.source_id().is_registry() {
106106
pid.source_id().with_precise_registry_version(
107107
pid.name(),
108-
pid.version(),
108+
pid.version().clone(),
109109
precise,
110110
)?
111111
} else {
112-
pid.source_id().with_precise(Some(precise.to_string()))
112+
pid.source_id().with_git_precise(Some(precise.to_string()))
113113
}
114114
}
115-
None => pid.source_id().with_precise(None),
115+
None => pid.source_id().without_precise(),
116116
});
117117
}
118118
if let Ok(unused_id) =
@@ -147,7 +147,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
147147
format!(
148148
"{} -> #{}",
149149
removed[0],
150-
&added[0].source_id().precise().unwrap()[..8]
150+
&added[0].source_id().precise_git_fragment().unwrap()
151151
)
152152
} else {
153153
format!("{} -> v{}", removed[0], added[0].version())
@@ -230,7 +230,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
230230
b[i..]
231231
.iter()
232232
.take_while(|b| a == b)
233-
.all(|b| a.source_id().precise() != b.source_id().precise())
233+
.all(|b| !a.source_id().has_same_precise_as(b.source_id()))
234234
})
235235
.cloned()
236236
.collect()

src/cargo/ops/common_for_install_and_uninstall.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ impl InstallTracker {
215215
let precise_equal = if source_id.is_git() {
216216
// Git sources must have the exact same hash to be
217217
// considered "fresh".
218-
dupe_pkg_id.source_id().precise() == source_id.precise()
218+
dupe_pkg_id.source_id().has_same_precise_as(source_id)
219219
} else {
220220
true
221221
};

src/cargo/ops/resolve.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -389,12 +389,8 @@ pub fn resolve_with_previous<'cfg>(
389389
}) {
390390
Some(id_using_default) => {
391391
let id_using_master = id_using_default.with_source_id(
392-
dep.source_id().with_precise(
393-
id_using_default
394-
.source_id()
395-
.precise()
396-
.map(|s| s.to_string()),
397-
),
392+
dep.source_id()
393+
.with_precise_from(id_using_default.source_id()),
398394
);
399395

400396
let mut locked_dep = dep.clone();
@@ -796,7 +792,7 @@ fn master_branch_git_source(id: PackageId, resolve: &Resolve) -> Option<PackageI
796792
let new_source =
797793
SourceId::for_git(source.url(), GitReference::Branch("master".to_string()))
798794
.unwrap()
799-
.with_precise(source.precise().map(|s| s.to_string()));
795+
.with_precise_from(source);
800796
return Some(id.with_source_id(new_source));
801797
}
802798
}

src/cargo/ops/vendor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ fn sync(
258258
} else {
259259
// Remove `precise` since that makes the source name very long,
260260
// and isn't needed to disambiguate multiple sources.
261-
source_id.with_precise(None).as_url().to_string()
261+
source_id.without_precise().as_url().to_string()
262262
};
263263

264264
let source = if source_id.is_crates_io() {

src/cargo/sources/config.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ impl<'cfg> SourceConfigMap<'cfg> {
145145
// Attempt to interpret the source name as an alt registry name
146146
if let Ok(alt_id) = SourceId::alt_registry(self.config, name) {
147147
debug!("following pointer to registry {}", name);
148-
break alt_id.with_precise(id.precise().map(str::to_string));
148+
break alt_id.with_precise_from(id);
149149
}
150150
bail!(
151151
"could not find a configured source with the \
@@ -163,7 +163,7 @@ impl<'cfg> SourceConfigMap<'cfg> {
163163
}
164164
None if id == cfg.id => return id.load(self.config, yanked_whitelist),
165165
None => {
166-
break cfg.id.with_precise(id.precise().map(|s| s.to_string()));
166+
break cfg.id.with_precise_from(id);
167167
}
168168
}
169169
debug!("following pointer to {}", name);
@@ -199,7 +199,7 @@ a lock file compatible with `{orig}` cannot be generated in this situation
199199
);
200200
}
201201

202-
if old_src.requires_precise() && id.precise().is_none() {
202+
if old_src.requires_precise() && !id.has_precise() {
203203
bail!(
204204
"\
205205
the source {orig} requires a lock file to be present first before it can be

0 commit comments

Comments
 (0)