This commit is contained in:
Aria Desires 2025-07-05 15:55:07 +03:00 committed by GitHub
commit 5397741b6d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 125 additions and 12 deletions

View file

@ -127,6 +127,10 @@ impl OperationDiagnostic {
native_tls_hint(err);
None
}
pip::operations::Error::OutdatedEnvironment => {
anstream::eprint!("{}", err);
None
}
err => Some(err),
}
}

View file

@ -233,6 +233,18 @@ pub(crate) async fn lock(
Ok(ExitStatus::Success)
}
Err(ProjectError::LockMismatch(previous, lock)) => {
// we're --locked and there was a mismatch, show all changes and exit as Failure
for event in LockEvent::detect_changes(previous.as_deref(), &lock, dry_run) {
writeln!(printer.stderr(), "{event}")?;
}
writeln!(
printer.stderr(),
"{}",
"The lockfile is outdated; run `uv lock` to update it".bold()
)?;
Ok(ExitStatus::Failure)
}
Err(ProjectError::Operation(err)) => {
diagnostics::OperationDiagnostic::native_tls(network_settings.native_tls)
.report(err)
@ -345,8 +357,11 @@ impl<'env> LockOperation<'env> {
.await?;
// If the lockfile changed, return an error.
if matches!(result, LockResult::Changed(_, _)) {
return Err(ProjectError::LockMismatch(Box::new(result.into_lock())));
if let LockResult::Changed(prev, cur) = result {
return Err(ProjectError::LockMismatch(
prev.map(Box::new),
Box::new(cur),
));
}
Ok(result)

View file

@ -75,7 +75,7 @@ pub(crate) enum ProjectError {
#[error(
"The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`."
)]
LockMismatch(Box<Lock>),
LockMismatch(Option<Box<Lock>>, Box<Lock>),
#[error(
"Unable to find lockfile at `uv.lock`. To create a lockfile, run `uv lock` or `uv sync`."

View file

@ -430,10 +430,10 @@ pub(crate) async fn sync(
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()));
}
Err(ProjectError::LockMismatch(lock)) if dry_run.enabled() => {
Err(ProjectError::LockMismatch(prev, cur)) if dry_run.enabled() => {
// The lockfile is mismatched, but we're in dry-run mode. We should proceed with the
// sync operation, but exit with a non-zero status.
Outcome::LockMismatch(lock)
Outcome::LockMismatch(prev, cur)
}
Err(err) => return Err(err.into()),
};
@ -478,7 +478,7 @@ pub(crate) async fn sync(
match outcome {
Outcome::Success(..) => Ok(ExitStatus::Success),
Outcome::LockMismatch(lock) => Err(ProjectError::LockMismatch(lock).into()),
Outcome::LockMismatch(prev, cur) => Err(ProjectError::LockMismatch(prev, cur).into()),
}
}
@ -489,7 +489,7 @@ enum Outcome {
/// The `lock` operation was successful.
Success(Lock),
/// The `lock` operation successfully resolved, but failed due to a mismatch (e.g., with `--locked`).
LockMismatch(Box<Lock>),
LockMismatch(Option<Box<Lock>>, Box<Lock>),
}
impl Outcome {
@ -497,7 +497,7 @@ impl Outcome {
fn lock(&self) -> &Lock {
match self {
Self::Success(lock) => lock,
Self::LockMismatch(lock) => lock,
Self::LockMismatch(_prev, cur) => cur,
}
}
}

View file

@ -11811,6 +11811,100 @@ fn unconditional_overlapping_marker_disjoint_version_constraints() -> Result<()>
Ok(())
}
/// Checks the output of `uv lock --check` when there isn't a lock
#[test]
fn check_no_lock() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "myproject"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["sortedcollections"]
"#,
)?;
uv_snapshot!(context.filters(), context.lock()
.arg("--check"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Unable to find lockfile at `uv.lock`. To create a lockfile, run `uv lock` or `uv sync`.
");
Ok(())
}
/// Checks the output of `uv lock --check` when the lock is outdated
#[test]
fn check_outdated_lock() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "myproject"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["sortedcollections"]
"#,
)?;
// Generate the lock
uv_snapshot!(context.filters(), context.lock(), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
");
// Check the --check returns fine
uv_snapshot!(context.filters(), context.lock()
.arg("--check"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
");
// Edit dependencies so the lock is invalid
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["iniconfig"]
"#,
)?;
uv_snapshot!(context.filters(), context.lock()
.arg("--check"), @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Added iniconfig v2.0.0
Removed myproject v0.1.0
Added project v0.1.0
Removed sortedcollections v2.1.0
Removed sortedcontainers v2.4.0
The lockfile is outdated; run `uv lock` to update it
");
Ok(())
}
/// This checks that markers that normalize to 'false', which are serialized
/// to the lockfile as `python_full_version < '0'`, get read back as false.
/// Otherwise `uv lock --check` will always fail.

View file

@ -660,9 +660,9 @@ fn check() -> Result<()> {
)?;
// Running `uv sync --check` should fail.
uv_snapshot!(context.filters(), context.sync().arg("--check"), @r###"
uv_snapshot!(context.filters(), context.sync().arg("--check"), @r"
success: false
exit_code: 2
exit_code: 1
----- stdout -----
----- stderr -----
@ -672,8 +672,8 @@ fn check() -> Result<()> {
Would download 1 package
Would install 1 package
+ iniconfig==2.0.0
error: The environment is outdated; run `uv sync` to update the environment
"###);
The environment is outdated; run `uv sync` to update the environment
");
// Sync the environment.
uv_snapshot!(context.filters(), context.sync(), @r###"