Skip to content

Commit 0f3ff91

Browse files
mikolalysenkoclaude
andcommitted
Refuse ignored vendor roots before takeover
Review follow-ups for #1061: - jvm_gate_preflight now also refuses vendor_artifact_gitignored when git ignores a JVM tree root. It runs before a hosted->vendored takeover restores upstream, so a Gradle pin (whose hosted index the group commit can't roll back) stays hosted instead of ending neither hosted nor vendored. - NuGet checks the ignored uuid dir before the empty-patch success return, so an empty patch can't report success under a .socket/ rule. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 9efff71 commit 0f3ff91

3 files changed

Lines changed: 53 additions & 22 deletions

File tree

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

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -562,8 +562,29 @@ pub async fn jvm_gate_preflight(
562562
}
563563
let shape = detect_shape(project_root);
564564
super::jvm::sbt_gate::for_shape(shape, project_root, &g, &a, &v)
565-
.map(|_| ())
566-
.map_err(|stop| stop.code_and_detail(purl))
565+
.map_err(|stop| stop.code_and_detail(purl))?;
566+
// Checked here too, so a hosted->vendored takeover keeps its pin
567+
// instead of restoring upstream and then refusing (#1061).
568+
match ignored_tree_root(shape, project_root).await {
569+
Some(refusal) => Err(refusal),
570+
None => Ok(()),
571+
}
572+
}
573+
574+
/// The `vendor_artifact_gitignored` refusal when git ignores a tree root
575+
/// `shape` writes into. Each tree root owns a `!*` `.gitignore` that
576+
/// re-includes file rules such as Java.gitignore's `*.jar` (#1061), but a
577+
/// rule ignoring the root itself (`.socket/`) can't be undone from inside.
578+
async fn ignored_tree_root(
579+
shape: super::jvm::Shape,
580+
project_root: &Path,
581+
) -> Option<(&'static str, String)> {
582+
for tree in super::jvm::shape_trees(shape) {
583+
if let Some(refusal) = super::npm_dir::ignored_root_refusal(project_root, tree).await {
584+
return Some(refusal);
585+
}
586+
}
587+
None
567588
}
568589

569590
/// The committed tree bytes for `record` (jar, upstream pom, module, and
@@ -697,13 +718,8 @@ async fn jvm_prelude(
697718
super::jvm::sbt_gate::for_shape(shape, project_root, &group_id, &artifact_id, &version)
698719
.map_err(|stop| stop.into_outcome(purl))?;
699720
// The tree must survive the commit the vendored workflow ends with.
700-
// Each tree root owns a `!*` `.gitignore` that re-includes file rules
701-
// such as Java.gitignore's `*.jar` (#1061), but a rule ignoring the
702-
// root itself (`.socket/`) can't be undone from inside it.
703-
for tree in super::jvm::shape_trees(shape) {
704-
if let Some(refusal) = super::npm_dir::ignored_root_refusal(project_root, tree).await {
705-
return Err(refusal);
706-
}
721+
if let Some((code, detail)) = ignored_tree_root(shape, project_root).await {
722+
return Err(refused(code, detail));
707723
}
708724
Ok(JvmPrelude {
709725
group_id,
@@ -2032,6 +2048,12 @@ mod tests {
20322048
before,
20332049
"{shape} {rule}: nothing written"
20342050
);
2051+
// The takeover gate refuses too, before any restore.
2052+
assert_eq!(
2053+
jvm_gate_preflight(root, PURL).await.map_err(|(c, _)| c),
2054+
Err("vendor_artifact_gitignored"),
2055+
"{shape} {rule}"
2056+
);
20352057
}
20362058
}
20372059
}

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -508,17 +508,18 @@ fn gitignored_refusal(rel_dir: &str, rules: &str) -> VendorOutcome {
508508
refused(GITIGNORED, gitignored_detail(rel_dir, rules))
509509
}
510510

511-
/// The `vendor_artifact_gitignored` refusal for a vendored artifact root
512-
/// (`dir_rel`, project-relative) that git ignores as a directory: a
513-
/// `.socket/` or `.socket/vendor/` rule no `.gitignore` inside the root can
514-
/// override (#831, #1061). `None` when git would look inside it, so the
515-
/// root's own `!*` `.gitignore` re-includes file rules such as `*.jar`.
511+
/// The `vendor_artifact_gitignored` refusal (code, detail) for a vendored
512+
/// artifact root (`dir_rel`, project-relative) that git ignores as a
513+
/// directory: a `.socket/` or `.socket/vendor/` rule no `.gitignore` inside
514+
/// the root can override (#831, #1061). `None` when git would look inside
515+
/// it, so the root's own `!*` `.gitignore` re-includes file rules such as
516+
/// `*.jar`.
516517
pub(crate) async fn ignored_root_refusal(
517518
project_root: &Path,
518519
dir_rel: &str,
519-
) -> Option<VendorOutcome> {
520+
) -> Option<(&'static str, String)> {
520521
let rules = gitignored(project_root, &[format!("{dir_rel}/")]).await?;
521-
Some(gitignored_refusal(dir_rel, &rules))
522+
Some((GITIGNORED, gitignored_detail(dir_rel, &rules)))
522523
}
523524

524525
pub(crate) const GITIGNORED: &str = "vendor_artifact_gitignored";

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,14 @@ async fn nuget_prelude(
221221
let nupkg_path = project_root.join(&copy_rel);
222222
let source_key = crate::patch::redirect::generation::hosted_pin_name(&record.uuid);
223223

224+
// The nupkg must survive the commit the vendored workflow ends with: a
225+
// rule ignoring the uuid dir itself can't be undone from inside it.
226+
if let Some((code, detail)) =
227+
super::npm_dir::ignored_root_refusal(project_root, &uuid_dir_rel).await
228+
{
229+
return Err(refused(code, detail));
230+
}
231+
224232
// A patch with no files is meaningless to vendor: no-op success, no edits.
225233
if record.files.is_empty() {
226234
return Err(done(
@@ -230,12 +238,6 @@ async fn nuget_prelude(
230238
));
231239
}
232240

233-
// The nupkg must survive the commit the vendored workflow ends with: a
234-
// rule ignoring the uuid dir itself can't be undone from inside it.
235-
if let Some(refusal) = super::npm_dir::ignored_root_refusal(project_root, &uuid_dir_rel).await {
236-
return Err(refusal);
237-
}
238-
239241
let config_path = existing_config_path(project_root).await;
240242
let config_text: Option<String> = match &config_path {
241243
Some(p) => match read_regular_to_string(p).await {
@@ -2501,6 +2503,12 @@ mod tests {
25012503
tokio::fs::read(root.join(PACKAGES_LOCK)).await.unwrap(),
25022504
lock_before
25032505
);
2506+
// An empty patch is refused too, never a calm success.
2507+
let mut empty = record.clone();
2508+
empty.files.clear();
2509+
let (code, _) =
2510+
unwrap_refused(run_vendor(root, &blobs, &installed, &empty, dry_run).await);
2511+
assert_eq!(code, "vendor_artifact_gitignored", "{rule}: empty patch");
25042512
}
25052513
}
25062514
}

0 commit comments

Comments
 (0)