Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions crates/socket-patch-cli/src/commands/vendored_backend/repair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,67 @@ impl VendoredBackend<'_> {
continue;
}
}
// A stray file in the tree (a Finder `.DS_Store`) fails
// the Gradle build (#656); the index says what belongs
// there, so it goes offline too.
if vendor::jvm::apply::is_jvm_entry(&entry) {
let indexed = vendor::jvm::apply::recorded_tree_files(&entry);
let swept =
match vendor::jvm::apply::unindexed_tree_entries(&common.cwd, &indexed)
.and_then(|found| {
vendor::jvm::apply::refuse_linked(&found).map(|()| found)
}) {
Ok(stray) if stray.is_empty() || common.dry_run => Ok(stray),
Ok(stray) => vendor::jvm::apply::sweep_unindexed(&stray)
.await
.map(|()| stray),
Err(e) => Err(e),
};
match swept {
Ok(stray) if stray.is_empty() => {}
Ok(stray) if common.dry_run => {
env.record(
PatchEvent::new(PatchAction::Verified, purl.clone())
.with_details(serde_json::json!({
"vendorArtifact": true,
"wouldRemoveUnindexed": stray
.iter()
.map(|s| s.rel.as_str())
.collect::<Vec<_>>(),
})),
);
}
Ok(stray) => {
if !quiet {
for s in &stray {
println!("Removed unindexed vendored file {}", s.rel);
}
}
env.record(
PatchEvent::new(PatchAction::Rebuilt, purl.clone())
.with_details(serde_json::json!({
"path": entry.artifact.path,
"unindexedRemoved": stray
.iter()
.map(|s| s.rel.as_str())
.collect::<Vec<_>>(),
"artifactRebuilt": false,
})),
);
repaired += 1;
}
Err(e) => {
fail(
env,
common.json,
purl,
"vendor_artifact_unrepairable",
format!("cannot remove unindexed vendored files: {e}"),
);
continue;
}
}
}
// Dir-shaped gem artifacts from pre-inventory vendors:
// the health check could only verify the PATCHED members
// — unpatched-file drift is invisible until a re-vendor
Expand Down
38 changes: 38 additions & 0 deletions crates/socket-patch-cli/tests/e2e_vendor_gradle_build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,44 @@ fn gradle_vendor_428_subproject_refuses() {
assert_victim(&out, "pristine", None, "`cd app && gradle` still builds");
}

// ── #656 ────────────────────────────────────────────────────────────────

/// #656: a Finder `.DS_Store` in the vendored tree fails the build with
/// "unindexed files … re-run `socket-patch vendor`". That re-run, and
/// `repair`, now remove it, and the build consumes the patched jar again.
#[test]
#[ignore = "real Gradle (the fake Central); run with --ignored"]
fn gradle_vendor_656_stray_tree_file() {
const SUITE: &str = "e2e_vendor_gradle_build::656";
let Some(world) = World::new(SUITE) else {
return;
};
let proj = world.root.join("proj");
simple_project(&proj);
prepare(&world, &proj, "printRuntimeClasspath");
world.vendor(&proj);
let out = world.build(&proj, &["printRuntimeClasspath"]);
assert_victim(&out, "patched", Some(&proj), "vendored build");
let stray = proj
.join(tree_rel(VICTIM, VICTIM_VERSION))
.join(".DS_Store");
for remedy in [&["vendor"][..], &["repair"]] {
std::fs::write(&stray, b"\0\0\0\x01Bud1").unwrap();
let out = world.build(&proj, &["printRuntimeClasspath"]);
assert!(!ok(&out), "{remedy:?}: the stray file fails the build");
assert!(
dump(&out).contains("unindexed files"),
"{remedy:?}:\n{}",
dump(&out)
);
world.socket_ok(&proj, remedy);
assert!(!stray.exists(), "{remedy:?} removes the stray file");
world.socket_ok(&proj, &["vendor", "--check"]);
let out = world.build(&proj, &["printRuntimeClasspath"]);
assert_victim(&out, "patched", Some(&proj), &format!("after {remedy:?}"));
}
}

// ── #429 ────────────────────────────────────────────────────────────────

/// #429: a `core.autocrlf=true` clone of a vendored checkout (buildSrc
Expand Down
80 changes: 80 additions & 0 deletions crates/socket-patch-cli/tests/vendor_jvm_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,86 @@ fn cold_cache_rerun_vex_repair_and_revert() {
}
}

/// #656: a file the index does not list in the vendored tree (a Finder
/// `.DS_Store`) fails the Gradle build and `vendor --check`. Re-running
/// `vendor`, the remedy the build error names, and `repair` both sweep
/// it instead of reporting the tree in sync.
#[test]
fn stray_tree_files_are_swept_by_vendor_and_repair() {
for shape in [Shape::Reactor, Shape::Gradle] {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
fixture(root, shape, &[("foo", FOO_UUID)]);
ok(root, &["vendor"]);
let vendored = snapshot(root);
let tree = if shape == Shape::Reactor {
"proj/.socket/vendor/maven2/org/example/foo/1.0-socket.1d3c1fd2"
} else {
"proj/.socket/vendor/gradle/org/example/foo/1.0"
};
let stray = root.join(tree).join(".DS_Store");

std::fs::write(&stray, b"\0\0\0\x01Bud1").unwrap();
// A stray symlink goes, not its target: vendor deletes it
// directly, outside its group commit.
let target = root.join("proj/keep.txt");
std::fs::write(&target, b"keep").unwrap();
#[cfg(unix)]
std::os::unix::fs::symlink(&target, root.join(tree).join("link")).unwrap();
let (code, env) = socket(root, &["vendor", "--check"]);
assert_eq!(code, Some(1), "{env}");
let env = ok(root, &["vendor", "--dry-run"]);
assert!(
!events(&env).iter().any(|(_, _, c)| c == "already_vendored"),
"the dry run previews the sweep: {env}"
);
assert!(stray.exists(), "a dry run writes nothing");
let env = ok(root, &["vendor"]);
assert!(
!events(&env).iter().any(|(_, _, c)| c == "already_vendored"),
"{env}"
);
assert!(!stray.exists(), "vendor sweeps the stray file: {env}");
assert!(std::fs::symlink_metadata(root.join(tree).join("link")).is_err());
assert_eq!(std::fs::read(&target).unwrap(), b"keep");
std::fs::remove_file(&target).unwrap();
ok(root, &["vendor", "--check"]);
let env = ok(root, &["vendor"]);
assert_eq!(
events(&env),
[(
purl("foo"),
"skipped".to_string(),
"already_vendored".to_string()
)],
"in sync after the sweep: {env}"
);

std::fs::write(&stray, b"\0\0\0\x01Bud1").unwrap();
std::fs::create_dir_all(root.join(tree).join("backup")).unwrap();
std::fs::write(root.join(tree).join("backup/old.jar"), b"x").unwrap();
let env = ok(root, &["repair", "--dry-run"]);
assert_eq!(
env["events"][0]["details"]["wouldRemoveUnindexed"],
serde_json::json!([
format!("{}/.DS_Store", tree.strip_prefix("proj/").unwrap()),
format!("{}/backup", tree.strip_prefix("proj/").unwrap()),
]),
"{env}"
);
assert!(stray.exists(), "repair --dry-run writes nothing");
let env = ok(root, &["repair"]);
assert_eq!(env["events"][0]["action"], "rebuilt", "{env}");
assert!(!stray.exists() && !root.join(tree).join("backup").exists());
ok(root, &["vendor", "--check"]);
let mut repaired = snapshot(root);
let mut want = vendored.clone();
repaired.remove(".socket/vendor/state.json");
want.remove(".socket/vendor/state.json");
assert_eq!(repaired, want);
}
}

/// `rollback --preserve-state` keeps the tree and the ledger entry.
#[test]
fn preserve_state_keeps_the_tree() {
Expand Down
Loading
Loading