Skip to content

Commit d597b38

Browse files
committed
Fix pipenv_reads_pylock to honor higher-priority tool locks
Pipenv should not read a pylock.toml when uv.lock or poetry.lock (higher- priority locks in the PyPI precedence order) are present. Previously, pipenv_reads_pylock only checked for Pipfile and the absence of Pipfile.lock, causing it to refuse the pylock even when a governing uv.lock or poetry.lock existed. This poisoned sibling confirmation by inserting uuids into refused_python_lock_uuids, making hosted and vendored disagree about whether files were redirected. The fix adds checks for uv.lock and poetry.lock presence, aligning the hosted behavior with the vendored router which already filters Pipenv pylocks when a higher-ranked tool lock governs.
1 parent 69a3aca commit d597b38

13 files changed

Lines changed: 69 additions & 33 deletions

File tree

‎crates/socket-patch-core/src/crawlers/cargo_crawler.rs‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,7 @@ fn read_crate_cargo_toml(crate_path: &Path, dir_name: &str) -> Option<(String, S
300300
let content = crate::utils::fs::read_regular_to_string_sync(&cargo_toml_path).ok()?;
301301

302302
// Fallback: parse directory name as <name>-<version>
303-
package_name_version(&content)
304-
.or_else(|| CargoCrawler::parse_dir_name_version(dir_name))
303+
package_name_version(&content).or_else(|| CargoCrawler::parse_dir_name_version(dir_name))
305304
}
306305

307306
impl Default for CargoCrawler {

‎crates/socket-patch-core/src/formats/mod.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
pub(crate) mod bun;
3030
pub mod cargo;
3131
pub mod composer;
32-
pub mod governing_locks;
3332
pub mod gem;
33+
pub mod governing_locks;
3434
pub(crate) mod maven;
3535
pub(crate) mod nuget;
3636
pub mod pnpm;

‎crates/socket-patch-core/src/hosted/memory/mod.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ use crate::rollout::stage::{
6666
classify, lookup_incomplete, mark_pinned, offers_from_results, Offers, RecordedIndex, Row,
6767
Stage, ROLLOUT_DEFERRED,
6868
};
69+
use crate::utils::purl_key::PurlKey;
6970
use discover::Provider;
7071
use stages::{Planned, RewriteRefused, Rewritten, StageOptions};
71-
use crate::utils::purl_key::PurlKey;
7272

7373
/// `"<crate version>+<git sha or 'unknown'>"`; the sha comes from the
7474
/// `SOCKET_PATCH_GIT_SHA` build-time variable.

‎crates/socket-patch-core/src/patch/redirect/mod.rs‎

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,9 @@ use crate::formats::yarn::patterns::{
7979
use crate::formats::yarn::source::{classic_copy_source, CopySource};
8080
use crate::formats::yarn::stanzas::{stanza_key, BerryStanzas};
8181
#[cfg(test)]
82-
mod pnpm_equivalence_tests;
83-
#[cfg(test)]
8482
mod platform_wheel_tests;
83+
#[cfg(test)]
84+
mod pnpm_equivalence_tests;
8585
mod poetry;
8686
mod pypi_takeover;
8787
pub use pypi_takeover::preflight_pypi_takeover;
@@ -567,7 +567,7 @@ pub fn rewrite_registry_redirect_with_pipenv_version(
567567
bun_lockb_present,
568568
&std::collections::BTreeSet::new(),
569569
&std::collections::BTreeSet::new(),
570-
&yarnrc::OuterYarnMirror::default(),
570+
&yarnrc::OuterYarnMirror::default(),
571571
)
572572
}
573573

@@ -6803,8 +6803,10 @@ fn rewrite_maven_pom(
68036803
// One pass over the pom's repositories: `(id, url)` of each, which also
68046804
// answers the per-dep URL-refresh check below while the pom is still
68056805
// unchanged (a no-op rescan then never re-scans the pom per dep).
6806-
let original_repos: Vec<(String, Option<String>)> =
6807-
pom.as_deref().map(maven_repository_ids_and_urls).unwrap_or_default();
6806+
let original_repos: Vec<(String, Option<String>)> = pom
6807+
.as_deref()
6808+
.map(maven_repository_ids_and_urls)
6809+
.unwrap_or_default();
68086810
let hosted_repo_generations: std::collections::BTreeSet<String> = original_repos
68096811
.iter()
68106812
.filter_map(|(id, _)| generation::pin_name_uuid(id, false).map(str::to_string))
@@ -10876,7 +10878,10 @@ mod tests {
1087610878
&[(YARNRC_REL, "yarn-offline-mirror: false\n")],
1087710879
&[(YARNRC_REL, "yarn-offline-mirror:\n")],
1087810880
&[(YARNRC_REL, "yarn-offline-mirror \"\"\n")],
10879-
&[(YARNRC_REL, "yarn-offline-mirror-pruning true\n# yarn-offline-mirror ./m\n")],
10881+
&[(
10882+
YARNRC_REL,
10883+
"yarn-offline-mirror-pruning true\n# yarn-offline-mirror ./m\n",
10884+
)],
1088010885
&[(npmrc::NPMRC_REL, "[scope]\nyarn-offline-mirror=./m\n")],
1088110886
&[
1088210887
(YARNRC_REL, "yarn-offline-mirror false\n"),
@@ -10892,7 +10897,10 @@ mod tests {
1089210897
let mut r = RewriteResult::default();
1089310898
rewrite_yarn_classic(&files, std::slice::from_ref(&ovr), &mut r);
1089410899
assert!(r.warnings.is_empty(), "{rcs:?}: {:?}", r.warnings);
10895-
assert!(r.files["yarn.lock"].contains("http://p.test/lp.tgz"), "{rcs:?}");
10900+
assert!(
10901+
r.files["yarn.lock"].contains("http://p.test/lp.tgz"),
10902+
"{rcs:?}"
10903+
);
1089610904
assert!(r.refused_yarn_classic_uuids.is_empty(), "{rcs:?}");
1089710905
}
1089810906
}
@@ -10917,7 +10925,10 @@ mod tests {
1091710925
rewrite_yarn_classic(&files, std::slice::from_ref(&other), &mut r);
1091810926
assert!(r.refused_yarn_classic_uuids.is_empty());
1091910927
assert_eq!(
10920-
r.warnings.iter().map(|w| w.code.as_str()).collect::<Vec<_>>(),
10928+
r.warnings
10929+
.iter()
10930+
.map(|w| w.code.as_str())
10931+
.collect::<Vec<_>>(),
1092110932
["redirect_yarn_classic_entry_not_found"]
1092210933
);
1092310934
}

‎crates/socket-patch-core/src/patch/redirect/poetry.rs‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ pub(super) fn rewrite_poetry(
8989
new: Some(Value::String(new)),
9090
});
9191
}
92-
result.confirmed_python_lock_uuids.insert(dep.patch_uuid.clone());
92+
result
93+
.confirmed_python_lock_uuids
94+
.insert(dep.patch_uuid.clone());
9395
if !stale_warned {
9496
if let Some(format) =
9597
*writer_format.get_or_insert_with(|| pre_1_4_writer(&content))
@@ -124,14 +126,18 @@ pub(super) fn rewrite_poetry(
124126
}
125127
// Already redirected to this artifact (idempotent re-scan).
126128
LockStep::Unchanged => {
127-
result.confirmed_python_lock_uuids.insert(dep.patch_uuid.clone());
129+
result
130+
.confirmed_python_lock_uuids
131+
.insert(dep.patch_uuid.clone());
128132
}
129133
LockStep::NotFound => result.warnings.push(RewriteWarning {
130134
code: "redirect_poetry_entry_not_found".into(),
131135
detail: format!("no {path} entry for {}@{}", dep.name, dep.version),
132136
}),
133137
LockStep::Refused(detail) => {
134-
result.refused_python_lock_uuids.insert(dep.patch_uuid.clone());
138+
result
139+
.refused_python_lock_uuids
140+
.insert(dep.patch_uuid.clone());
135141
result.warnings.push(RewriteWarning {
136142
code: "redirect_poetry_lock_unsupported".into(),
137143
detail: format!("{path}: {detail}"),

‎crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,9 @@ mod tests {
489489
let other = format!("log = {{ version = \"0.4.20\", registry = \"socket-patch-{C}\" }}\n");
490490
let (outcome, after) = restore_b(&manifest(&other), &config).await;
491491
assert_eq!(outcome.restored().count(), 1, "{:?}", outcome.pins);
492-
assert_eq!(after.as_deref().map(str::trim_start), Some(block(C).as_str()));
492+
assert_eq!(
493+
after.as_deref().map(str::trim_start),
494+
Some(block(C).as_str())
495+
);
493496
}
494497
}

‎crates/socket-patch-core/src/utils/group_commit.rs‎

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,7 +1335,10 @@ mod tests {
13351335
(".socket/vendor/.gitattributes", true),
13361336
(".socket/vendor/gradle/g/a/maven-metadata.xml", true),
13371337
(".socket/vendor/gradle/g/a/1/a-1.jar", false),
1338-
(".socket/vendor/gradle/g/a/1/socket-patch.vendor.json", false),
1338+
(
1339+
".socket/vendor/gradle/g/a/1/socket-patch.vendor.json",
1340+
false,
1341+
),
13391342
(".socket/vendor/npm/u/left-pad-1.3.0.tgz", false),
13401343
(".socket/manifest.json", false),
13411344
("packages/a/.socket/vendor/npm/u/a.tgz", false),
@@ -1437,10 +1440,16 @@ mod tests {
14371440
if keep {
14381441
group.rollback_to(savepoint);
14391442
group.commit().await.unwrap();
1440-
assert!(unit.join("a.tgz").exists(), "a rolled-back removal is forgotten");
1443+
assert!(
1444+
unit.join("a.tgz").exists(),
1445+
"a rolled-back removal is forgotten"
1446+
);
14411447
} else {
14421448
drop(group);
1443-
assert!(unit.join("a.tgz").exists(), "a dropped group deletes nothing");
1449+
assert!(
1450+
unit.join("a.tgz").exists(),
1451+
"a dropped group deletes nothing"
1452+
);
14441453
}
14451454
}
14461455

@@ -1449,8 +1458,14 @@ mod tests {
14491458
remove_tree_and_prune(&unit, &socket).await.unwrap();
14501459
group.commit().await.unwrap();
14511460
assert!(!unit.exists());
1452-
assert!(!socket.join("vendor").exists(), "the emptied levels are pruned");
1453-
assert!(socket.join("apply.lock").exists(), "`.socket/` itself stays");
1461+
assert!(
1462+
!socket.join("vendor").exists(),
1463+
"the emptied levels are pruned"
1464+
);
1465+
assert!(
1466+
socket.join("apply.lock").exists(),
1467+
"`.socket/` itself stays"
1468+
);
14541469
}
14551470

14561471
/// A journal the commit had to create `.socket/vendor/` for (a hosted

‎crates/socket-patch-core/src/utils/python_lock.rs‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,8 @@ pub fn is_pep751_lock_name(name: &str) -> bool {
186186
/// #912 / #1122: whether Pipenv installs from the PEP 751 lock `rel` (a
187187
/// project-relative path). A `Pipfile` beside the lock makes Pipenv the
188188
/// installer, and Pipenv reads a pylock only when that directory has no
189-
/// `Pipfile.lock`. Its reader (`PylockFile.convert_to_pipenv_lockfile`)
189+
/// `Pipfile.lock` and no higher-priority tool lock (`uv.lock` or
190+
/// `poetry.lock`). Its reader (`PylockFile.convert_to_pipenv_lockfile`)
190191
/// keeps a package's version, marker and wheel / sdist hashes and nothing
191192
/// else, so no hosted or vendored `archive` entry survives it: such a lock
192193
/// can't carry a patch. `exists` answers for project-relative paths.
@@ -198,6 +199,8 @@ pub fn pipenv_reads_pylock(rel: &str, exists: impl Fn(&str) -> bool) -> bool {
198199
is_pep751_lock_name(name)
199200
&& exists(&format!("{dir}Pipfile"))
200201
&& !exists(&format!("{dir}Pipfile.lock"))
202+
&& !exists(&format!("{dir}uv.lock"))
203+
&& !exists(&format!("{dir}poetry.lock"))
201204
}
202205

203206
pub fn python_lock_paths(root: &Path) -> std::io::Result<Vec<String>> {

‎crates/socket-patch-core/src/vendor/lock_inventory/tests.rs‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -999,7 +999,10 @@ async fn headerless_yarn_classic_lock_is_inventoried_as_classic_by_the_fallback(
999999
.filter(|l| !l.starts_with('#'))
10001000
.collect::<Vec<_>>()
10011001
.join("\n");
1002-
assert!(!headerless.contains("lockfile v1"), "fixture drops the header");
1002+
assert!(
1003+
!headerless.contains("lockfile v1"),
1004+
"fixture drops the header"
1005+
);
10031006
write(tmp.path(), "yarn.lock", &headerless).await;
10041007
let (flavor, entries) = inventory_npm_lock(tmp.path()).await.unwrap().unwrap();
10051008
assert_eq!(flavor, NpmLockFlavor::YarnClassic);

‎crates/socket-patch-core/src/vendor/mod.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,8 @@ pub use verify::{
129129
};
130130
// The hosted→vendored takeover refuses a berry project the backend would
131131
// refuse BEFORE it reverts the hosted redirect.
132-
pub use npm_lock::npm_lock_vendor_preflight;
133132
pub use npm_common::npm_tarball_gitignore_preflight;
133+
pub use npm_lock::npm_lock_vendor_preflight;
134134
pub use yarn_berry_lock::{yarn_berry_vendor_preflight, yarn_berry_vendor_target_preflight};
135135

136136
use std::collections::{HashMap, HashSet};

0 commit comments

Comments
 (0)