diff --git a/crates/uv-installer/src/plan.rs b/crates/uv-installer/src/plan.rs index d06403c0809e..f7155146cc36 100644 --- a/crates/uv-installer/src/plan.rs +++ b/crates/uv-installer/src/plan.rs @@ -440,7 +440,7 @@ fn installed_satisfies_requirement( // If the requirement comes from a direct URL, check by URL. Some(VersionOrUrl::Url(url)) => { if let InstalledDist::Url(installed) = &distribution { - if &installed.url == url.raw() { + if !installed.editable && &installed.url == url.raw() { // If the requirement came from a local path, check freshness. if let Some(archive) = (url.scheme() == "file") .then(|| url.to_file_path().ok()) diff --git a/crates/uv-installer/src/site_packages.rs b/crates/uv-installer/src/site_packages.rs index fb66dece78e6..dd32e91812ca 100644 --- a/crates/uv-installer/src/site_packages.rs +++ b/crates/uv-installer/src/site_packages.rs @@ -393,6 +393,10 @@ impl<'a> SitePackages<'a> { return Ok(false); }; + if installed.editable { + return Ok(false); + } + if &installed.url != url.raw() { return Ok(false); } @@ -438,6 +442,10 @@ impl<'a> SitePackages<'a> { return Ok(false); }; + if installed.editable { + return Ok(false); + } + if &installed.url != url.raw() { return Ok(false); } diff --git a/crates/uv/tests/pip_sync.rs b/crates/uv/tests/pip_sync.rs index 8322dc7746f8..8a686dbb86e0 100644 --- a/crates/uv/tests/pip_sync.rs +++ b/crates/uv/tests/pip_sync.rs @@ -2376,6 +2376,86 @@ fn sync_editable_and_registry() -> Result<()> { Ok(()) } +#[test] +fn sync_editable_and_local() -> Result<()> { + let context = TestContext::new("3.12"); + + // Copy the black test editable into the "current" directory + copy_dir_all( + context + .workspace_root + .join("scripts/packages/black_editable"), + context.temp_dir.join("black_editable"), + )?; + + // Install the editable version of Black. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str(indoc::indoc! {r" + -e file:./black_editable + " + })?; + + uv_snapshot!(context.filters(), command(&context) + .arg(requirements_txt.path()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Built 1 editable in [TIME] + Installed 1 package in [TIME] + + black==0.1.0 (from file://[TEMP_DIR]/black_editable) + "### + ); + + // Install the non-editable version of Black. This should replace the editable version. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str(indoc::indoc! {r" + black @ file:./black_editable + " + })?; + + uv_snapshot!(context.filters(), command(&context) + .arg(requirements_txt.path()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Uninstalled 1 package in [TIME] + Installed 1 package in [TIME] + - black==0.1.0 (from file://[TEMP_DIR]/black_editable) + + black==0.1.0 (from file://[TEMP_DIR]/black_editable) + "### + ); + + // Reinstall the editable version of Black. This should replace the non-editable version. + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str(indoc::indoc! {r" + -e file:./black_editable + " + })?; + + uv_snapshot!(context.filters(), command(&context) + .arg(requirements_txt.path()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Built 1 editable in [TIME] + Uninstalled 1 package in [TIME] + Installed 1 package in [TIME] + - black==0.1.0 (from file://[TEMP_DIR]/black_editable) + + black==0.1.0 (from file://[TEMP_DIR]/black_editable) + "### + ); + + Ok(()) +} + #[test] fn incompatible_wheel() -> Result<()> { let context = TestContext::new("3.12");