mirror of https://github.com/astral-sh/uv
Ignore pyproject index username in lockfile comparison (#16995)
<!-- Thank you for contributing to uv! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? - Does this pull request include references to any relevant issues? --> ## Summary Pyproject.toml index url may contain a username while lockfile doesn't. Treat it as the same index to prevent unintended package updates Fixes #16436 --------- Co-authored-by: konstin <konstin@mailbox.org>
This commit is contained in:
parent
b58f543e5e
commit
af348c2a88
|
|
@ -163,7 +163,11 @@ impl PreferenceIndex {
|
||||||
match self {
|
match self {
|
||||||
Self::Any => true,
|
Self::Any => true,
|
||||||
Self::Implicit => false,
|
Self::Implicit => false,
|
||||||
Self::Explicit(preference) => preference == index,
|
Self::Explicit(preference) => {
|
||||||
|
// Preferences are stored in the lockfile without credentials, while the index URL
|
||||||
|
// in locations such as `pyproject.toml` may contain credentials.
|
||||||
|
*preference.url() == *index.without_credentials()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -381,3 +385,30 @@ impl From<Version> for Pin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
/// Test that [`PreferenceIndex::matches`] correctly ignores credentials when comparing URLs.
|
||||||
|
///
|
||||||
|
/// This is relevant for matching lockfile preferences (stored without credentials)
|
||||||
|
/// against index URLs from pyproject.toml (which may include usernames for auth).
|
||||||
|
#[test]
|
||||||
|
fn test_preference_index_matches_ignores_credentials() {
|
||||||
|
// URL without credentials (as stored in lockfile)
|
||||||
|
let index_without_creds = IndexUrl::from_str("https:/pypi_index.com/simple").unwrap();
|
||||||
|
|
||||||
|
// URL with username (as specified in pyproject.toml)
|
||||||
|
let index_with_username =
|
||||||
|
IndexUrl::from_str("https://username@pypi_index.com/simple").unwrap();
|
||||||
|
|
||||||
|
let preference = PreferenceIndex::Explicit(index_without_creds.clone());
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
preference.matches(&index_with_username),
|
||||||
|
"PreferenceIndex should match URLs that differ only in username"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9600,6 +9600,84 @@ fn lock_redact_https() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test that packages aren't unnecessarily updated when an index URL contains a username.
|
||||||
|
#[test]
|
||||||
|
fn lock_index_url_username_change_no_update() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
// Create initial lockfile with exact version constraint
|
||||||
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(
|
||||||
|
r#"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = ["anyio==4.0.0"]
|
||||||
|
|
||||||
|
[[tool.uv.index]]
|
||||||
|
name = "test-index"
|
||||||
|
url = "https://fakeuser@pypi.org/simple"
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
anyio = { index = "test-index" }
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.lock(), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 4 packages in [TIME]
|
||||||
|
"###);
|
||||||
|
|
||||||
|
let lock = context.read("uv.lock");
|
||||||
|
|
||||||
|
// Verify anyio 4.0.0 is locked
|
||||||
|
assert!(lock.contains("name = \"anyio\""));
|
||||||
|
assert!(lock.contains("version = \"4.0.0\""));
|
||||||
|
|
||||||
|
// Update pyproject.toml to simulate availability of newer package with more open but still compatible constraint
|
||||||
|
pyproject_toml.write_str(
|
||||||
|
r#"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = ["anyio>=4.0.0"]
|
||||||
|
|
||||||
|
[[tool.uv.index]]
|
||||||
|
name = "test-index"
|
||||||
|
url = "https://fakeuser@pypi.org/simple"
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
anyio = { index = "test-index" }
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Run `uv lock` to update the lockfile
|
||||||
|
// The package should stay at 4.0.0
|
||||||
|
uv_snapshot!(context.filters(), context.lock(), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 4 packages in [TIME]
|
||||||
|
"###);
|
||||||
|
|
||||||
|
let lock_after = context.read("uv.lock");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
lock_after.contains("version = \"4.0.0\""),
|
||||||
|
"anyio should remain at version 4.0.0, not update despite >=4.0.0 constraint"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "git")]
|
#[cfg(feature = "git")]
|
||||||
fn lock_redact_git_pep508() -> Result<()> {
|
fn lock_redact_git_pep508() -> Result<()> {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue