@@ -18,11 +18,16 @@ pub(super) async fn stale_install_warnings(
1818 common : & crate :: args:: GlobalArgs ,
1919 confirmed : & [ ( String , String ) ] ,
2020 pipenv_uuids : & BTreeSet < String > ,
21+ // The run's final Pipfile.lock text: the remedy's `pipenv sync`
22+ // arguments follow the categories that pin each package.
23+ pipenv_lock : Option < & str > ,
2124 // This run's fetched records MERGED with the ledger's persisted ones
2225 // (the caller hands the post-merge ledger map), looked up by uuid.
2326 records : & BTreeMap < String , PatchRecord > ,
2427) -> StaleInstallOutcome {
2528 let mut out = StaleInstallOutcome :: default ( ) ;
29+ let pipenv_lock: Option < serde_json:: Value > =
30+ pipenv_lock. and_then ( |text| serde_json:: from_str ( text. trim_start_matches ( '\u{feff}' ) ) . ok ( ) ) ;
2631 let candidates: Vec < _ > = confirmed
2732 . iter ( )
2833 . filter ( |( purl, _) | purl. starts_with ( "pkg:pypi/" ) )
@@ -111,15 +116,15 @@ pub(super) async fn stale_install_warnings(
111116 . and_then ( |rest| rest. split ( '@' ) . next ( ) )
112117 . unwrap_or ( "<package>" )
113118 . to_string ( ) ;
119+ let remedy = socket_patch_core:: vendor:: pypi_pipenv:: stale_install_remedy (
120+ pipenv_lock. as_ref ( ) ,
121+ & name,
122+ ) ;
114123 format ! (
115124 "Pipenv does not reinstall a release that is already present (`pipenv \
116125 install`, `pipenv install --deploy` and `pipenv sync` all keep those \
117126 bytes), so the rewritten Pipfile.lock only protects fresh installs. \
118- Reinstall it from the lock without touching the Pipfile: `pipenv run pip \
119- uninstall -y {name} && pipenv sync` (`pipenv install --deploy` before \
120- Pipenv 2018), or `pipenv --rm && pipenv sync` for a clean virtualenv — \
121- NOT `pipenv uninstall`, which rewrites the Pipfile and re-locks the \
122- patch away; then `socket-patch vex --product <purl>` re-verifies the \
127+ {remedy}; then `socket-patch vex --product <purl>` re-verifies the \
123128 installed files."
124129 )
125130 } else {
@@ -194,7 +199,8 @@ mod tests {
194199 ( "one" . into ( ) , record ( "first-uuid" , "first.py" , b"patched" ) ) ,
195200 ( "two" . into ( ) , record ( "second-uuid" , "second.py" , b"patched" ) ) ,
196201 ] ) ;
197- let out = stale_install_warnings ( & common, & confirmed, & BTreeSet :: new ( ) , & ledger) . await ;
202+ let out =
203+ stale_install_warnings ( & common, & confirmed, & BTreeSet :: new ( ) , None , & ledger) . await ;
198204 assert_eq ! ( out. stale_purls, BTreeSet :: from( [ first. to_string( ) ] ) ) ;
199205 assert_eq ! ( out. warnings. len( ) , 1 ) ;
200206 assert ! ( out. warnings[ 0 ] [ "detail" ]
@@ -209,7 +215,8 @@ mod tests {
209215 "variant" . into ( ) ,
210216 ) ) ;
211217 ledger. insert ( "three" . into ( ) , record ( "variant" , "first.py" , b"upstream" ) ) ;
212- let out = stale_install_warnings ( & common, & confirmed, & BTreeSet :: new ( ) , & ledger) . await ;
218+ let out =
219+ stale_install_warnings ( & common, & confirmed, & BTreeSet :: new ( ) , None , & ledger) . await ;
213220 assert ! ( out. stale_purls. is_empty( ) ) ;
214221 assert ! ( out. warnings. is_empty( ) ) ;
215222 }
@@ -233,8 +240,66 @@ mod tests {
233240 let purl = "pkg:pypi/six@1.16.0" ;
234241 let confirmed = vec ! [ ( purl. to_string( ) , "six-uuid" . to_string( ) ) ] ;
235242 let ledger = BTreeMap :: from ( [ ( "k" . into ( ) , record ( "six-uuid" , "six.py" , b"patched" ) ) ] ) ;
236- let out = stale_install_warnings ( & common, & confirmed, & BTreeSet :: new ( ) , & ledger) . await ;
243+ let out =
244+ stale_install_warnings ( & common, & confirmed, & BTreeSet :: new ( ) , None , & ledger) . await ;
237245 assert_eq ! ( out. stale_purls, BTreeSet :: from( [ purl. to_string( ) ] ) ) ;
238246 assert_eq ! ( out. warnings[ 0 ] [ "code" ] , "redirect_pypi_stale_install" ) ;
239247 }
248+
249+ /// #790: the Pipenv remedy names the lock category that pins the
250+ /// package. Plain `pipenv sync` installs only `default`, so for a
251+ /// `[dev-packages]` entry it uninstalled the package and left it
252+ /// uninstalled.
253+ #[ tokio:: test]
254+ async fn pipenv_remedy_resyncs_the_category_that_pins_the_package ( ) {
255+ let tmp = tempfile:: tempdir ( ) . unwrap ( ) ;
256+ let site = tmp. path ( ) . join ( "site-packages" ) ;
257+ std:: fs:: create_dir_all ( site. join ( "six-1.16.0.dist-info" ) ) . unwrap ( ) ;
258+ std:: fs:: write ( site. join ( "six.py" ) , b"upstream" ) . unwrap ( ) ;
259+ let common = crate :: args:: GlobalArgs {
260+ cwd : tmp. path ( ) . to_path_buf ( ) ,
261+ global_prefix : Some ( site. clone ( ) ) ,
262+ ..Default :: default ( )
263+ } ;
264+ let purl = "pkg:pypi/six@1.16.0" ;
265+ let confirmed = vec ! [ ( purl. to_string( ) , "six-uuid" . to_string( ) ) ] ;
266+ let ledger = BTreeMap :: from ( [ ( "k" . into ( ) , record ( "six-uuid" , "six.py" , b"patched" ) ) ] ) ;
267+ let pipenv = BTreeSet :: from ( [ "six-uuid" . to_string ( ) ] ) ;
268+ let detail = |lock : & str | {
269+ let lock = lock. to_string ( ) ;
270+ let ( common, confirmed, ledger, pipenv) = ( & common, & confirmed, & ledger, & pipenv) ;
271+ async move {
272+ let out =
273+ stale_install_warnings ( common, confirmed, pipenv, Some ( & lock) , ledger) . await ;
274+ assert_eq ! ( out. warnings. len( ) , 1 ) ;
275+ out. warnings [ 0 ] [ "detail" ] . as_str ( ) . unwrap ( ) . to_string ( )
276+ }
277+ } ;
278+
279+ let develop = detail (
280+ r#"{"_meta": {"pipfile-spec": 6}, "default": {}, "develop": {"six": {"file": "x"}}}"# ,
281+ )
282+ . await ;
283+ assert ! (
284+ develop. contains( "`pipenv run pip uninstall -y six && pipenv sync --dev`" ) ,
285+ "{develop}"
286+ ) ;
287+ assert ! (
288+ develop. contains( "`pipenv --rm && pipenv sync --dev`" ) ,
289+ "{develop}"
290+ ) ;
291+
292+ let docs = detail (
293+ r#"{"_meta": {"pipfile-spec": 6}, "default": {"requests": {}}, "docs": {"six": {}}}"# ,
294+ )
295+ . await ;
296+ assert ! (
297+ docs. contains( "-y six && pipenv sync --categories \" docs\" `" ) ,
298+ "{docs}"
299+ ) ;
300+ assert ! (
301+ docs. contains( "`pipenv --rm && pipenv sync --categories \" packages docs\" `" ) ,
302+ "{docs}"
303+ ) ;
304+ }
240305}
0 commit comments