fix: Respect --frozen everywhere --offline or --locked is accepted (#15263)

### What does this PR try to resolve?

Fixes #15239

This also makes `--frozen` be respected in more situations, including

* `cargo add`
* `cargo install`
* Git fetches
* HTTP registry access

### How should we test and review this PR?

To prevent this from happening again, I removed `offline()` and
`locked()` accessors. To maintain the quality of error messages, I added
`offline_flag()` and `locked_flag()` that will pick up `--frozen`, if
present.

### Additional information
This commit is contained in:
Weihang Lo 2025-03-04 20:25:07 +00:00 committed by GitHub
commit c14c1c0e19
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 65 additions and 82 deletions

View File

@ -393,13 +393,13 @@ pub(super) fn activation_error(
);
if let Some(gctx) = gctx {
if gctx.offline() {
if let Some(offline_flag) = gctx.offline_flag() {
let _ = write!(
&mut hints,
"\nAs a reminder, you're using offline mode (--offline) \
"\nAs a reminder, you're using offline mode ({offline_flag}) \
which can sometimes cause surprising resolution failures, \
if this error is too confusing you may wish to retry \
without the offline flag.",
without `{offline_flag}`.",
);
}
}

View File

@ -251,11 +251,11 @@ pub fn add(workspace: &Workspace<'_>, options: &AddOptions<'_>) -> CargoResult<(
}
}
if options.gctx.locked() {
if let Some(locked_flag) = options.gctx.locked_flag() {
let new_raw_manifest = manifest.to_string();
if original_raw_manifest != new_raw_manifest {
anyhow::bail!(
"the manifest file {} needs to be updated but --locked was passed to prevent this",
"the manifest file {} needs to be updated but {locked_flag} was passed to prevent this",
manifest.path.display()
);
}

View File

@ -189,7 +189,7 @@ impl<'gctx> InstallablePackage<'gctx> {
lockfile_path.clone(),
)?;
if gctx.locked() {
if !gctx.lock_update_allowed() {
// When --lockfile-path is set, check that passed lock file exists
// (unlike the usual flag behavior, lockfile won't be created as we imply --locked)
if let Some(requested_lockfile_path) = ws.requested_lockfile_path() {

View File

@ -50,19 +50,14 @@ pub fn write_pkg_lockfile(ws: &Workspace<'_>, resolve: &mut Resolve) -> CargoRes
}
}
if !ws.gctx().lock_update_allowed() {
let flag = if ws.gctx().locked() {
"--locked"
} else {
"--frozen"
};
if let Some(locked_flag) = ws.gctx().locked_flag() {
anyhow::bail!(
"the lock file {} needs to be updated but {} was passed to prevent this\n\
If you want to try to generate the lock file without accessing the network, \
remove the {} flag and use --offline instead.",
lock_root.as_path_unlocked().join(LOCKFILE_NAME).display(),
flag,
flag
locked_flag,
locked_flag
);
}

View File

@ -198,12 +198,8 @@ fn validate_locked_and_frozen_options(
) -> Result<(), anyhow::Error> {
// Only in workspace, we can use --frozen or --locked.
if !in_workspace {
if gctx.locked() {
bail!("the option `--locked` can only be used within a workspace");
}
if gctx.frozen() {
bail!("the option `--frozen` can only be used within a workspace");
if let Some(locked_flag) = gctx.locked_flag() {
bail!("the option `{locked_flag}` can only be used within a workspace");
}
}
Ok(())

View File

@ -299,10 +299,16 @@ impl<'gctx> Source for GitSource<'gctx> {
// If we're in offline mode, we're not locked, and we have a
// database, then try to resolve our reference with the preexisting
// repository.
(Revision::Deferred(git_ref), Some(db)) if self.gctx.offline() => {
(Revision::Deferred(git_ref), Some(db)) if !self.gctx.network_allowed() => {
let offline_flag = self
.gctx
.offline_flag()
.expect("always present when `!network_allowed`");
let rev = db.resolve(&git_ref).with_context(|| {
"failed to lookup reference in preexisting repository, and \
can't check for updates in offline mode (--offline)"
format!(
"failed to lookup reference in preexisting repository, and \
can't check for updates in offline mode ({offline_flag})"
)
})?;
(db, rev)
}
@ -312,9 +318,9 @@ impl<'gctx> Source for GitSource<'gctx> {
// situation that we have a locked revision but the database
// doesn't have it.
(locked_rev, db) => {
if self.gctx.offline() {
if let Some(offline_flag) = self.gctx.offline_flag() {
anyhow::bail!(
"can't checkout from '{}': you are in the offline mode (--offline)",
"can't checkout from '{}': you are in the offline mode ({offline_flag})",
self.remote.url()
);
}

View File

@ -955,15 +955,12 @@ pub fn fetch(
gctx: &GlobalContext,
remote_kind: RemoteKind,
) -> CargoResult<()> {
if gctx.frozen() {
if let Some(offline_flag) = gctx.offline_flag() {
anyhow::bail!(
"attempting to update a git repository, but --frozen \
"attempting to update a git repository, but {offline_flag} \
was specified"
)
}
if !gctx.network_allowed() {
anyhow::bail!("can't update a git repository in the offline mode")
}
let shallow = remote_kind.to_shallow_setting(repo.is_shallow(), gctx);

View File

@ -376,7 +376,7 @@ impl<'gctx> HttpRegistry<'gctx> {
} else if self.gctx.cli_unstable().no_index_update {
trace!("using local {} in no_index_update mode", path.display());
true
} else if self.gctx.offline() {
} else if !self.gctx.network_allowed() {
trace!("using local {} in offline mode", path.display());
true
} else if self.fresh.contains(path) {
@ -511,7 +511,7 @@ impl<'gctx> RegistryData for HttpRegistry<'gctx> {
return Poll::Ready(Ok(LoadResponse::NotFound));
}
if self.gctx.offline() || self.gctx.cli_unstable().no_index_update {
if !self.gctx.network_allowed() || self.gctx.cli_unstable().no_index_update {
// Return NotFound in offline mode when the file doesn't exist in the cache.
// If this results in resolution failure, the resolver will suggest
// removing the --offline flag.

View File

@ -482,7 +482,7 @@ impl<'gctx> RegistryIndex<'gctx> {
load: &mut dyn RegistryData,
f: &mut dyn FnMut(IndexSummary),
) -> Poll<CargoResult<()>> {
if self.gctx.offline() {
if !self.gctx.network_allowed() {
// This should only return `Poll::Ready(Ok(()))` if there is at least 1 match.
//
// If there are 0 matches it should fall through and try again with online.

View File

@ -345,7 +345,7 @@ impl<'gctx> RegistryData for RemoteRegistry<'gctx> {
}
self.mark_updated();
if self.gctx.offline() {
if !self.gctx.network_allowed() {
return Ok(());
}
if self.gctx.cli_unstable().no_index_update {

View File

@ -1161,19 +1161,17 @@ impl GlobalContext {
}
pub fn network_allowed(&self) -> bool {
!self.frozen() && !self.offline()
!self.offline_flag().is_some()
}
pub fn offline(&self) -> bool {
self.offline
}
pub fn frozen(&self) -> bool {
self.frozen
}
pub fn locked(&self) -> bool {
self.locked
pub fn offline_flag(&self) -> Option<&'static str> {
if self.frozen {
Some("--frozen")
} else if self.offline {
Some("--offline")
} else {
None
}
}
pub fn set_locked(&mut self, locked: bool) {
@ -1181,7 +1179,17 @@ impl GlobalContext {
}
pub fn lock_update_allowed(&self) -> bool {
!self.frozen && !self.locked
!self.locked_flag().is_some()
}
pub fn locked_flag(&self) -> Option<&'static str> {
if self.frozen {
Some("--frozen")
} else if self.locked {
Some("--locked")
} else {
None
}
}
/// Loads configuration from the filesystem.

View File

@ -25,15 +25,9 @@ pub fn http_handle(gctx: &GlobalContext) -> CargoResult<Easy> {
}
pub fn http_handle_and_timeout(gctx: &GlobalContext) -> CargoResult<(Easy, HttpTimeout)> {
if gctx.frozen() {
if let Some(offline_flag) = gctx.offline_flag() {
bail!(
"attempting to make an HTTP request, but --frozen was \
specified"
)
}
if gctx.offline() {
bail!(
"attempting to make an HTTP request, but --offline was \
"attempting to make an HTTP request, but {offline_flag} was \
specified"
)
}

View File

@ -184,7 +184,7 @@ fn cargo_compile_offline_not_try_update() {
[ERROR] no matching package named `not_cached_dep` found
location searched: crates.io index
required by package `bar v0.1.0 ([ROOT]/bar)`
As a reminder, you're using offline mode (--offline) which can sometimes cause surprising resolution failures, if this error is too confusing you may wish to retry without the offline flag.
As a reminder, you're using offline mode (--offline) which can sometimes cause surprising resolution failures, if this error is too confusing you may wish to retry without `--offline`.
"#]])
.run();
@ -195,7 +195,7 @@ As a reminder, you're using offline mode (--offline) which can sometimes cause s
[ERROR] no matching package named `not_cached_dep` found
location searched: crates.io index
required by package `bar v0.1.0 ([ROOT]/bar)`
As a reminder, you're using offline mode (--offline) which can sometimes cause surprising resolution failures, if this error is too confusing you may wish to retry without the offline flag.
As a reminder, you're using offline mode (--offline) which can sometimes cause surprising resolution failures, if this error is too confusing you may wish to retry without `--offline`.
"#]]).run();
}
@ -384,13 +384,13 @@ fn update_offline_not_cached() {
p.cargo("update --offline")
.with_status(101)
.with_stderr_data(str![["
.with_stderr_data(str![[r#"
[ERROR] no matching package named `bar` found
location searched: [..]
required by package `foo v0.0.1 ([ROOT]/foo)`
As a reminder, you're using offline mode (--offline) which can sometimes cause surprising resolution failures, if this error is too confusing you may wish to retry without the offline flag.
As a reminder, you're using offline mode (--offline) which can sometimes cause surprising resolution failures, if this error is too confusing you may wish to retry without `--offline`.
"]])
"#]])
.run();
}
@ -607,7 +607,7 @@ candidate versions found which didn't match: 1.0.0
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `foo v0.1.0 ([ROOT]/foo)`
perhaps a crate was updated and forgotten to be re-vendored?
As a reminder, you're using offline mode (--offline) which can sometimes cause surprising resolution failures, if this error is too confusing you may wish to retry without the offline flag.
As a reminder, you're using offline mode (--offline) which can sometimes cause surprising resolution failures, if this error is too confusing you may wish to retry without `--offline`.
"#]]
)
@ -754,7 +754,7 @@ fn main(){
[ERROR] no matching package named `present_dep` found
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `foo v0.1.0 ([ROOT]/foo)`
As a reminder, you're using offline mode (--offline) which can sometimes cause surprising resolution failures, if this error is too confusing you may wish to retry without the offline flag.
As a reminder, you're using offline mode (--offline) which can sometimes cause surprising resolution failures, if this error is too confusing you may wish to retry without `--offline`.
"#]]
)

View File

@ -2431,14 +2431,10 @@ fn disallow_network_http() {
p.cargo("check --frozen")
.with_status(101)
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[ERROR] failed to get `foo` as a dependency of package `bar v0.5.0 ([ROOT]/foo)`
Caused by:
failed to query replaced source registry `crates-io`
Caused by:
attempting to make an HTTP request, but --frozen was specified
[ERROR] no matching package named `foo` found
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `bar v0.5.0 ([ROOT]/foo)`
As a reminder, you're using offline mode (--frozen) which can sometimes cause surprising resolution failures, if this error is too confusing you may wish to retry without `--frozen`.
"#]])
.run();
@ -2467,19 +2463,10 @@ fn disallow_network_git() {
p.cargo("check --frozen")
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] failed to get `foo` as a dependency of package `bar v0.5.0 ([ROOT]/foo)`
Caused by:
failed to load source for dependency `foo`
Caused by:
Unable to update registry `crates-io`
Caused by:
failed to update replaced source registry `crates-io`
Caused by:
attempting to make an HTTP request, but --frozen was specified
[ERROR] no matching package named `foo` found
location searched: `dummy-registry` index (which is replacing registry `crates-io`)
required by package `bar v0.5.0 ([ROOT]/foo)`
As a reminder, you're using offline mode (--frozen) which can sometimes cause surprising resolution failures, if this error is too confusing you may wish to retry without `--frozen`.
"#]])
.run();