Skip to content

Commit 01c0895

Browse files
committed
Check gem edits in vendor dry runs instead
Move the #1761 fix to the gem vendored backend: its dry run now computes the Gemfile and lock edits the wet run would make and returns the same refusal (for example a declaration inside a `group` block). Every dry-run caller benefits, not just the eject: a plain `vendor --dry-run` and the takeover previews over restored text now report what the wet run would refuse. This replaces the eject-only gate added in the previous commit, which checked each gem pin against its own one-pin restore and hid earlier backend refusals. Assisted-by: Claude Code:claude-opus-5-5
1 parent 8de500d commit 01c0895

2 files changed

Lines changed: 27 additions & 15 deletions

File tree

  • crates

‎crates/socket-patch-cli/src/commands/vendor.rs‎

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1850,27 +1850,12 @@ async fn run_eject(args: &VendorArgs, pins: Vec<HostedPin>) -> i32 {
18501850
let installed_sites = vendor::pypi::InstalledSiteListings::default();
18511851
let vendored_at = now_rfc3339();
18521852
let restore_refused: HashSet<String> = refused.iter().map(|(p, _, _)| p.clone()).collect();
1853-
// The gem backend's dry run does not evaluate its manifest and Gemfile
1854-
// declaration gates, so ask them directly, as the per-purl takeover
1855-
// and `scan` / `get --mode vendored` do (#1761).
1856-
let mut gem_refusals = gem_takeover_refusals_for(
1857-
&common.cwd,
1858-
pins.iter().map(|pin| pin.purl.as_str()),
1859-
&pins,
1860-
common.offline,
1861-
origins.clone(),
1862-
)
1863-
.await;
18641853
for pin in &pins {
18651854
// A pin the restore already refused keeps its first, accurate
18661855
// reason; previewing it again would add a second failure event.
18671856
if restore_refused.contains(&pin.purl) {
18681857
continue;
18691858
}
1870-
if let Some((code, detail)) = gem_refusals.remove(&pin.purl) {
1871-
refused.push((pin.purl.clone(), code, detail));
1872-
continue;
1873-
}
18741859
if let Some((code, detail)) = takeover_dry_refusal(
18751860
&plan,
18761861
&pin.purl,

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,14 @@ pub async fn vendor_gem<'a>(
663663
}
664664

665665
if dry_run {
666+
// An unwired gem previews the Gemfile and lock edit refusals the wet
667+
// run raises (a declaration inside a `group` block, #1761), so a
668+
// takeover or eject preview over the restored text refuses too.
669+
if !lock_wired {
670+
if let Err(outcome) = gem_edits(purl, &prelude) {
671+
return *outcome;
672+
}
673+
}
666674
if let Err(outcome) =
667675
super::service_fetch::preview_service(service, record, extract_gem_data).await
668676
{
@@ -3663,6 +3671,25 @@ mod tests {
36633671
assert!(detail.contains("path:"), "{detail}");
36643672
}
36653673

3674+
/// #1761: a dry run raises the same declaration refusal as the wet run,
3675+
/// so a `vendor` eject preview does not report a refused gem as planned.
3676+
#[tokio::test]
3677+
async fn test_dry_run_previews_declaration_refusal() {
3678+
let grouped =
3679+
"source \"https://rubygems.org\"\n\ngroup :test do\n gem \"rack\", \"~> 3.1\"\nend\n";
3680+
for dry_run in [true, false] {
3681+
let (_tmp, root, installed, blobs, record) = fixture(grouped, LOCK_DIRECT).await;
3682+
let (code, detail) =
3683+
unwrap_refused(run_vendor(&root, &blobs, &installed, &record, dry_run).await);
3684+
assert_eq!(
3685+
code, "gemfile_declaration_not_editable",
3686+
"dry_run={dry_run}"
3687+
);
3688+
assert!(detail.contains("indented"), "dry_run={dry_run}: {detail}");
3689+
assert!(!root.join(".socket").exists(), "dry_run={dry_run}");
3690+
}
3691+
}
3692+
36663693
/// SECURITY: a traversal uuid (tampered manifest) must be refused before
36673694
/// any disk access.
36683695
#[tokio::test]

0 commit comments

Comments
 (0)